Skip to main content

Checkout Live Online payments gated

The point where a cart becomes a StoreOrder. Three payment methods, two fulfillment types, server-enforced rules.

1. Overview

Two entry points: (a) Cart page → "Proceed to Checkout" (multi-product), (b) Store page → in-line "Place Order" (single-store). Both POST to /api/store-orders with the same payload shape. Server validates fulfillment + payment compatibility, calculates 10% commission, creates StoreOrder + StoreOrderItem rows, notifies seller, sends order-confirmation email to consumer.

2. User journey

  1. Cart → Proceed to Checkout
  2. Enter delivery address (or select Pickup)
  3. Enter contact phone
  4. Pick payment method (currently auto-defaults — see Business Logic)
  5. Tap Place Order
  6. See success state, order ID
  7. Receive order confirmation email + push (seller calls phone to verify)

3. Web ↔ Mobile parity

CapabilityWeb cart checkoutWeb store checkoutMobile store checkout
Delivery option
Pickup options (immediate / scheduled)
COD / Pay-at-Pickup
Online (UPI / PhonePe)🟡 Gated🟡 Gated🟡 Gated
Address autocomplete✅ Nominatim✅ Nominatim✅ Saved + recent

4. Key components

  • Web (cart): src/app/(consumer)/cart/page.tsx — inline form per store
  • Web (store): src/app/(consumer)/stores/[id]/page.tsx — has pickup support
  • Mobile: mobile/app/store/[id].tsx, mobile/app/cart.tsx

5. APIs

EndpointMethodPurpose
/api/store-ordersPOSTPlace order. Validates flag, auto-defaults pickup payment, creates rows.
/api/payments/initiate-storePOSTOnline payment session — returns 503 when PAYMENTS_ONLINE_ENABLED=false
/api/payments/upi-configGETReturns current paymentEnabled flag for UI gating
/api/payments/callbackPOSTPhonePe webhook (gateway flow)

6. Database touchpoints

  • StoreOrder (created on success) — paymentMethod, paymentStatus, fulfillmentType, pickupTime, cancellationReason, all status timestamps
  • StoreOrderItem (one row per cart item)
  • Contribution (created in background, 0.5% of total → charity ledger)
  • ReelEarning (PENDING row created if order came from a tagged reel)

7. Business logic

Server is single source of truth

The /api/store-orders POST builds resolvedPaymentMethod server-side:

  • If fulfillmentType is pickup → forced to pay_at_pickup (client cannot override)
  • If paymentMethod=online and PAYMENTS_ONLINE_ENABLED=false → 400 with helpful error
  • If paymentMethod=online and flag on → must use /api/payments/initiate-store instead (rejects from this endpoint)
  • Otherwise → cod (delivery default)

Commission

  • 10% default (Store.commissionRate)
  • Stored on StoreOrder.platformFee
  • Seller gets subtotal - platformFeeStoreOrder.sellerAmount

Order confirmation email

  • Sent on POST success (best-effort, fire-and-forget, never blocks order placement)
  • Uses central email lib (sendOrderConfirmation)
  • Includes itemized list + total + Track Order CTA

8. Feature flags / env vars

  • PAYMENTS_ONLINE_ENABLED — primary gate
  • PLATFORM_UPI_ID — direct UPI (vs PhonePe gateway)
  • PHONEPE_* — gateway config (optional)

9. Tests

  • tests/offline-only-launch.test.ts — 31 tests around the flag behavior
  • tests/store-product-flow.test.ts
  • tests/build-integrity.test.ts — Store Order API surface

10. Known gotchas

  • paymentStatus mirrors paymentMethod by default — cod / pay_at_pickup until handover, then transitions to paid / refunded. Don't confuse them.
  • Multi-store checkout is NOT supported — one POST = one store. UI splits cart accordingly.
  • Online payment orders take a different code path — they're created in payments/callback after the gateway confirms, NOT in /api/store-orders POST. So the order ID exists only after payment success.
  • Seller's "Call to Confirm" badge appears on pending orders so they verify the customer phone before accepting.