Supercommerce API Docs
Store API

Razorpay Magic Checkout — Storefront

Start and status endpoints for Razorpay's hosted one-click checkout: the storefront creates a one-click Razorpay order from the cart; Razorpay collects address + OTP + coupons in its modal and the webhook places the internal order on completion.

Entry point for Razorpay Magic Checkout, Razorpay's hosted one-click checkout. Unlike standard checkout, Razorpay's own modal collects the customer's phone (OTP login), shipping/billing address, and coupons — the storefront does not collect an address. The server creates a one-click Razorpay order from the cart's line items; the internal order is placed later, by the webhook, once Razorpay has captured payment and collected the address.

Source: api-modules/payment-razorpay-magic/src/controllers/store-razorpay-magic.controller.ts

This module is a removable plugin mounted by RazorpayMagicPaymentModule.forRoot() in apps/api/src/app.module.ts. It reuses the standard Razorpay account/credentials (payment.razorpay settings).

Enablement

The start endpoint operates only when all hold:

  1. razorpay-magic is in payment.enabled_providers for the cart's platform — web and/or app (admin settings, group payment), AND
  2. group payment.razorpay_magic, key enabled is true, AND
  3. the shared Razorpay credentials (payment.razorpaykey_id / key_secret) are non-empty.

Otherwise the endpoint returns 403 FORBIDDEN. Magic Checkout serves both web (checkout.js) and app (Razorpay Android / iOS / Flutter SDKs) — the platform is taken from the cart, so enable razorpay-magic in the enabled_providers list(s) for the platform(s) you ship.

Operators configure the toggles under Admin → Plugins → Razorpay → Configuration.

The publishable Razorpay keyId is available without an admin token from GET /store/settings/payment (and the payment group of GET /store/settings) — handy for initialising the mobile SDK at app startup. See Store Settings.

GET /store/payment/razorpay-magic/config

Anonymous. What a client calls before rendering a Quick Checkout entry point.

enabled folds together all three conditions above into one boolean, so the client never reassembles the rule and can't drift from what start will accept. keyId is the publishable key (empty when disabled), so this one call both decides whether to show the button and boots the SDK when it's clicked.

Auth: none. Header: x-platform: WEB | APP (defaults to WEB).

{
  "data": {
    "enabled": true,
    "keyId": "rzp_test_xxxxxxxx"
  }
}

With the plugin unmounted this route 404s — clients should treat that as disabled rather than an error.

POST /store/cart/:cartId/magic-checkout/start

Starts a Magic Checkout for the caller's cart. Requires a session (the cart's anonymous or registered owner). Reserves inventory, creates the one-click Razorpay order from the cart's line items, persists a razorpay_magic_checkout session row, and returns the bootstrap data for checkout.js.

Auth: BetterAuthGuard — the session user must own the cart (cross-user cart ids return 404, never 403).

Path paramDescription
cartIdThe cart to check out. Must belong to the session user.

Request body: none.

Response 200 OK (standard { data, message, statusCode } envelope):

{
  "data": {
    "razorpayOrderId": "order_NXg...",
    "keyId": "rzp_test_xxxxxxxx",
    "amount": 124900,          // items total in subunits (paise)
    "currency": "INR",
    "oneClickCheckout": true
  }
}

The client opens checkout.js with { key: keyId, order_id: razorpayOrderId, one_click_checkout: true }. Razorpay then drives the modal (address, OTP, coupons, payment). The order amount excludes shipping and coupons — Razorpay adds shipping via the serviceability callback and subtracts coupons via the apply-promotion callback (see the webhooks doc).

StatusCause
403 FORBIDDENMagic Checkout disabled (see Enablement).
404 NOT_FOUNDCart not found, not owned by the caller, or empty.

GET /store/cart/:cartId/magic-checkout/status

Polls the outcome of a Magic Checkout. The internal order is placed asynchronously by the completion webhook, so after the Razorpay SDK / checkout.js reports a successful payment the client has only the razorpayOrderId — it must poll here until the order is placed.

Auth: BetterAuthGuard — the session user must own the cart. Returns the most recent Magic session for the cart.

Response 200 OK:

{
  "data": {
    "status": "paid",                  // created | processing | paid | failed
    "orderId": "01J9...",              // internal order id once paid, else null
    "orderNumber": "ORD-2026-00000123" // displayId (reserved at start)
  }
}

Client handling: created / processing → keep polling; paid → route to the order via orderId / orderNumber; failed → show failure.

StatusCause
404 NOT_FOUNDCart not found / not owned, or no Magic Checkout exists for it.

Lifecycle

  1. Start → this endpoint pre-allocates the order's human number (displayId, e.g. ORD-2026-00000123), uses it as the Razorpay order receipt, and creates the Razorpay order + magic-session. The internal order placed at completion carries that same number, so the Razorpay dashboard and the order's displayId always match. (Standard Razorpay checkout does the same — receipt = the order's displayId.)
  2. Modal → Razorpay calls our coupon + shipping callbacks as the customer fills the form.
  3. Completion → the webhook fires (payment.captured for prepaid, payment.pending + cod for COD); we fetch the collected address, place the internal order, and confirm it.
  4. Confirmation → the order's ORDER_PLACED event drives the guest confirmation email + tracking link (when the guest-checkout plugin is mounted). A later sign-up with the same email migrates the order via GuestConversionService.

The customer is the cart's anonymous user; Razorpay's contact (email/name/phone) is stored on the guest contact row, exactly as standard guest checkout.

On this page