Documents 04 and 05 explained how QuickCart finds the best price, stock and ETA for a single product across five platforms. This document explains what happens when a shopper wants several products at once โ and those products live on different platforms. The answer is the piece that turns QuickCart from a comparison tool into a marketplace: a cross-platform cart, one internal order, and an orchestrator that procures and delivers everything under a single QuickCart receipt.
The model: QuickCart is the merchant of record
A price-comparison app stops at "Blinkit is cheapest โ tap to open Blinkit." QuickCart goes further: the shopper transacts once, with QuickCart. Behind that single order, QuickCart procures each line item from whichever platform is cheapest and in stock, then delivers the whole basket. The shopper never creates five accounts, pays five times, or tracks five parcels.
Procurement of any given line happens in one of two modes โ the same two modes introduced in the Architecture document (ยง9, Payments):
Managed checkout
Where a platform exposes a partner commerce API (programmatic cart + checkout),
QuickCart places the sub-order automatically via the mcp-adaptor layer. Fast,
hands-off, fully tracked. Realistically available on marketplace sources (Amazon/Flipkart via
seller/affiliate flows) and on quick-commerce sources only where a partner agreement is in place.
Assisted fulfillment
Where a source is handoff-only (no programmatic checkout โ see Doc 04), a QuickCart picker/agent buys the item in-store or in-app on the shopper's behalf, or the line is re-sourced to a different platform that can be placed programmatically and is still in stock at a comparable price.
Building a cross-platform basket
A QuickCart cart is a flat list of lines. Every line references a
canonical SKU (the platform-independent product from the Catalogue, Doc 03 ยง6)
plus a chosen sourceId. By default the source is the ranked winner from the Price
Engine (Doc 05); the shopper can override it ("I'd rather get the butter from Zepto โ it's faster").
Because lines carry their own source, one cart naturally spans multiple platforms.
{
"cartId": "crt_9f2a1c",
"userId": "usr_512",
"pincode": "560103",
"lines": [
{
"lineId": "ln_1",
"canonicalSku": "AMUL-BUTTER-500G",
"sourceId": "bigbasket",
"qty": 1,
"substitutable": true,
"priceSnapshot": { "mrp": 295, "effectivePrice": 268, "etaMin": 14, "deliveryFee": 0 }
},
{
"lineId": "ln_2",
"canonicalSku": "MAGGI-MASALA-70G-PACK12",
"sourceId": "blinkit",
"qty": 1,
"substitutable": true,
"priceSnapshot": { "mrp": 168, "effectivePrice": 152, "etaMin": 9, "deliveryFee": 15 }
},
{
"lineId": "ln_3",
"canonicalSku": "BOAT-AIRDOPES-311PRO",
"sourceId": "amazon",
"qty": 1,
"substitutable": false,
"priceSnapshot": { "mrp": 4490, "effectivePrice": 1299, "etaMin": 1440, "deliveryFee": 0 }
}
],
"reoptimiseAtCheckout": true
}
| Cart-line field | Meaning |
|---|---|
canonicalSku | Platform-independent product id from the Catalogue โ the thing the shopper actually wants. |
sourceId | Chosen platform for this line. Defaults to the Price-Engine winner; user-overridable. |
qty | Units requested. |
substitutable | May QuickCart re-source or substitute if this source goes out of stock? Shopper controls it per line. |
priceSnapshot | Price/ETA captured when added โ advisory. Re-verified at checkout (Doc 05). |
reoptimiseAtCheckout | Cart-level flag: re-run ranking at checkout so a line auto-moves to a better source if one appeared. |
From cart to one internal order
At checkout, QuickCart authorises a single payment hold for the basket total, creates one Order, and hands it to the Order Orchestrator. The orchestrator splits the order into sub-orders โ one per source โ and routes each sub-order by what that source can actually do.
authorise payment (hold)"] CO --> ORD["Create ONE Order
(merchant of record)"] ORD --> ORCH{{"Order Orchestrator"}} ORCH --> SUB["Split into per-source
sub-orders"] SUB --> DEC{"Source supports
programmatic placement?"} DEC -->|"Yes ยท commerce API"| AUTO["Auto-place sub-order
(Managed checkout)"] DEC -->|"No ยท handoff-only"| ALT{"In stock &
substitutable?"} ALT -->|"Re-source"| AUTO ALT -->|"Keep source"| PICK["Assisted: picker buys item"] AUTO --> CONF["Procurement confirmed"] PICK --> CONF CONF --> CONS["Consolidate items"] CONS --> LM["Last-mile delivery"] LM --> DONE["โ Delivered"] DONE --> SET[("Settle to each source")]
Order & sub-order data model
One ORDER owns one PAYMENT and fans out to many SUB_ORDERs โ
exactly one per source. Each sub-order owns its items and, once fulfilled, a per-source
SETTLEMENT. This shape lets QuickCart confirm, cancel, refund and settle a
single source without touching the rest of the basket.
combined_eta_min is the max of its sub-order ETAs when the
basket is delivered together (pooled). If a slow line (e.g. an Amazon item at 1–2 days) would
hold up fast grocery lines, QuickCart offers a split delivery instead โ see ยง7.
Orchestration as a saga
The hard part: five sources are independent systems with no shared database and no shared transaction. QuickCart cannot "BEGIN โฆ COMMIT" across Blinkit and Amazon. So the orchestrator runs a saga โ a sequence of local steps, each with a compensating action that undoes it if a later step fails.
- Authorise, don't capture. Place a hold on the full basket total. Compensation: void the hold.
- Re-verify per source. Right before placing, re-check stock & price for each sub-order (a fast line can vanish between "added to cart" and "checkout").
- Place each sub-order โ managed (commerce API) or assisted (picker task). Compensation: cancel the placed sub-order / cancel the picker task where still possible.
- On failure, compensate the failed line only โ re-source it to an in-stock alternative if it's substitutable, otherwise mark that line for refund. The rest of the basket proceeds.
- Capture the confirmed subtotal. Money actually taken = value of confirmed lines + delivery fee; the held remainder is released.
// Saga: authorise โ re-verify โ place per source โ compensate on failure โ capture
const { mcp } = require('../mcp-adaptor/client');
const payment = require('../payment/psp');
const pickerQueue = require('../fulfillment/picker-queue');
// Sources that expose a partner commerce API (programmatic placement).
// Quick-commerce sources are assisted-only unless a partner agreement is enabled.
const PLACEABLE = new Set(['amazon', 'flipkart']);
async function orchestrate(order) {
const saga = new Saga();
try {
// 1) hold funds for the whole basket (authorise, do NOT capture yet)
const auth = await payment.authorise(order.paymentId, order.totalAuthorised);
saga.add(() => payment.void(auth.id)); // compensation
// 2) one sub-order per source
const subOrders = groupBySource(order.lines);
// 3) place sub-orders in parallel, isolated from each other
const settled = await Promise.allSettled(
subOrders.map((so) => placeSubOrder(so, order, saga))
);
const confirmed = settled.filter(r => r.status === 'fulfilled').map(r => r.value);
const failed = subOrders.filter((_, i) => settled[i].status === 'rejected');
// 4) re-source or refund each failed line โ never fail the whole basket
for (const so of failed) {
const alt = await resourceLines(so.lines, order.pincode); // find in-stock alternative
if (alt) confirmed.push(await placeSubOrder(alt, order, saga));
else order.markLinesRefunded(so.lines);
}
// 5) capture ONLY what was actually confirmed
const captureAmount = subtotal(confirmed) + deliveryFee(order);
await payment.capture(auth.id, captureAmount);
order.status = failed.length ? 'PARTIALLY_CONFIRMED' : 'CONFIRMED';
await dispatchToLastMile(order, confirmed); // pooled / direct / split โ see ยง7
return order;
} catch (err) {
await saga.compensate(); // void hold + cancel any placed sub-orders
order.status = 'CANCELLED';
throw err;
}
}
async function placeSubOrder(so, order, saga) {
// stock + price re-check immediately before placing (Doc 05)
const stock = await mcp.call(so.sourceId, 'check_stock', { items: so.lines, location: order.pincode });
if (!stock.allAvailable) throw new OutOfStock(so.sourceId, stock.missing);
if (PLACEABLE.has(so.sourceId)) {
const cart = await mcp.call(so.sourceId, 'create_cart', { items: so.lines });
const placed = await mcp.call(so.sourceId, 'checkout', { cartId: cart.cartId, address: order.address });
saga.add(() => mcp.call(so.sourceId, 'cancel_order', { ref: placed.ref }));
return { sourceId: so.sourceId, mode: 'managed', ref: placed.ref, lines: so.lines, etaMin: placed.etaMin };
}
// handoff-only source โ create an assisted picker task
const task = await pickerQueue.enqueue({ sourceId: so.sourceId, lines: so.lines, pincode: order.pincode });
saga.add(() => pickerQueue.cancel(task.id));
return { sourceId: so.sourceId, mode: 'assisted', taskId: task.id, lines: so.lines, etaMin: task.etaMin };
}
class Saga {
constructor() { this.undos = []; }
add(fn) { this.undos.push(fn); }
async compensate() { for (const undo of this.undos.reverse()) { try { await undo(); } catch (_) {} } }
}
module.exports = { orchestrate };
Idempotency-Key (Doc 03 ยง3). A retried submit returns the same
order instead of double-charging or double-placing โ essential when a flaky mobile network makes the
app resend a checkout.
Fulfillment & delivery models
Once sub-orders are confirmed, the items still have to physically reach the shopper. QuickCart picks one of three delivery shapes per order, based on geography, source type and the ETA promise.
Direct
The source delivers straight to the shopper (its own rider), and QuickCart just relays live tracking. Best when a single source covers most of the basket, or for marketplace parcels (Amazon/Flipkart) on their own logistics.
Pooled / consolidated
A QuickCart rider does a multi-pickup run across the sources' dark stores, optionally via a consolidation hub, then makes one drop. Best for several quick-commerce lines going to the same address within minutes of each other.
Split
Items arrive in separate deliveries when combining would make everything slower โ e.g. groceries now, the Amazon gadget tomorrow. The shopper sees each parcel tracked under the same order.
BigBasket dark store"] P1 --> P2["Pickup 2
Blinkit store"] P2 --> HUB["Consolidation hub
(optional)"] HUB --> CUST["๐ Customer
single drop"]
batchable sub-orders by pickup proximity and ETA spread. If all
confirmed lines are within a tight radius and time window โ pooled. If one line is
far slower โ offer split. If one source dominates and delivers itself โ
direct.
Order lifecycle state machine
The order moves through a single state machine; each sub-order runs its own parallel mini-lifecycle
(PENDING โ PLACED โ PICKED โ HANDED_OVER, or โฆ โ FAILED โ RESOURCED). The
order aggregates them: it is CONFIRMED only when every line is sourced, and
PARTIALLY_CONFIRMED when some lines were dropped and refunded.
Money flow
One shopper-facing charge; many supplier payouts. The hold-then-capture pattern is what makes partial baskets safe: QuickCart never captures money for a line it couldn't actually source.
| Event | What happens to money |
|---|---|
| Checkout | Authorise (hold) the full basket total on the shopper's UPI/card via the PSP. No funds move yet. |
| All lines confirmed | Capture the full total. Order โ CONFIRMED. |
| Some lines dropped | Capture only the confirmed subtotal + delivery fee; the held remainder is released automatically. Order โ PARTIALLY_CONFIRMED. |
| Per-source procurement | QuickCart pays each platform through its own supplier account; recorded as a SETTLEMENT row per sub-order for reconciliation. |
| Cancel before pickup | Void the hold (if not captured) or issue a full refund (if captured). Cancel placed sub-orders where the source allows it. |
| Single-line refund | Partial refund of just that line's effectivePrice; other lines and the delivery fee are unaffected. |
| Delivery fee | One fee per order (not per source). Pooled deliveries absorb multi-pickup cost; QuickCart's margin sits between supplier price and shopper price + fee. |
Edge cases we handle
Out of stock at fulfillment
Re-source the line to an in-stock alternative (if substitutable), otherwise drop & refund just that line. Basket proceeds.
Partial availability
Shopper chooses the policy up front: all-or-nothing (cancel if anything is missing) or best-effort (ship what's available).
One source is slow
Offer a split delivery so fast grocery lines aren't held hostage by a 1–2 day marketplace parcel.
Price moved up at checkout
Re-confirm with the shopper before capture; re-rank the line in case another source is now cheaper.
Handoff-only, no picker coverage
If the pincode has no assisted-fulfillment coverage, re-source the line; if impossible, disallow that source for the address before checkout.
Duplicate submit
Idempotency-Key returns the same order โ no double charge, no double placement.
Full cancellation
Run saga compensation: void/refund payment and cancel every placed sub-order or picker task where the source permits.
Single-line refund post-delivery
Damaged/wrong item โ refund that line only, reconcile the dispute against the specific source's settlement.
Source outage mid-order
Circuit breaker (Doc 05) trips the source; substitutable lines re-source, others refund โ order still completes.