cart ready → get_payment_options → place-order (paymentMethod …) → PENDING_PAYMENT { paasId, orderId, bridgeUrl, pollingIntervalInMs, maxTimeToPollForInMs }
→ check_payment_status (long-poll ~19 s) → confirm_order → PLACED → track
Place-order tools: Food place_food_order, Instamart checkout, Dineout book_table (paid deal, after create_cart). The three payment tools have the same name and shape on every server.
Rules
get_payment_optionsis the only source of methods. Offer exactly whatdata.allMethods(orplatforms.mobile/desktop.methods,cod) returns; never invent a method. Food: pass the sameaddressIdused forget_food_cartso the picker can place directly.- Never ask which device the user is on and never ask for a UPI ID/VPA. The picker/QR handle it; UPI Collect and saved VPAs are not surfaced (NPCI compliance).
- Pass the user's pick byte-for-byte. UPI app →
paymentMethod: "UPI"+intentApp: <method.id>. Desktop scan-QR →paymentMethod: "UPI"+generateUPIQR: true, nointentApp. Cash →paymentMethod: "Cash"(only whencod.available). Swiggy Money →paymentMethod: "SwiggyPay"only if a method with that group is returned.paymentMethodis always the group name, never an app id. PENDING_PAYMENTis not an order. Say "Complete the payment in your UPI app — I'll confirm your order once payment succeeds." Never say placed/confirmed/successful untilconfirm_ordersucceeds (orcheck_payment_statusreportsconfirmed: true). Only then use the brandedmessage("Swiggy order placed successfully").- Widget hosts (Claude Desktop, ChatGPT, Cursor…): do not poll. The confirmation widget polls
check_payment_statusand auto-finalizes. Call it once only if the user asks "did my payment go through?". Callconfirm_orderyourself only if the result says auto-confirm could not run. - Headless clients (no widget): you own the loop. Give the user
data.bridgeUrl(opens a scan-or-tap page: QR on desktop, app button on mobile; present for both intent and QR choices — read it fromdata, do not parsemessage). Then pollcheck_payment_statuseverypollingIntervalInMs, capped atmaxTimeToPollForInMs. Never tight-loop: it is a long-poll and hammering it stresses the payment cache. - Echo identifiers; never reconstruct them.
| Server | check_payment_status args |
confirm_order args |
|---|---|---|
| Food | paasId, orderId, addressId, cartId, lat, lng (all echoed from place_food_order; without them the order stays pending) |
orderId, addressId, lat, lng (+cartId) — no paasId |
| Instamart | paasId, orderId |
orderId, paasId (+transactionId) |
| Dineout | paasId, orderId |
orderId, paasId (+transactionId) |
paasId is the payment transaction id from the place-order response — never "UPI", "PayWithQR" or an app id. If you do not have a real paasId, do not call the tool.
Branch on check_payment_status.data
status / flags |
Then |
|---|---|
success / paid (isTerminalSuccess) |
if confirmed is true → done; else confirm_order once |
failed (isTerminalFailure) |
do not confirm; re-show get_payment_options and place again (fresh transaction, same cart) |
cancelled |
order cancelled; any debit is refunded; do not retry on your own |
cart_changed |
not a payment failure: price/stock changed, order NOT placed; review the cart with the user, then place again |
refund-initiated |
debited and refund underway; tell the user; stop |
pending, terminal: false |
keep waiting on the cadence; at the cap call confirm_order once (backend marks it failed if still unpaid; a late success reconciles server-side) |
confirm_order is idempotent and never places an unpaid order; a duplicate call is harmless, a call after a terminal failure is pointless.
Cash
No payment leg: place with paymentMethod: "Cash" and go straight to tracking. Cash is the automatic fallback when UPI is unavailable for the cart (platforms omitted, only cod).
Swiggy Money
Announced for MCP on 4 Sept 2026: the user adds money once and the agent pays without a per-order payment step. It surfaces as a SwiggyPay group in get_payment_options when enabled for the account and cart; treat it like Cash (no pending leg) and never pass it when it is not listed.