Payment Razorpay Module — Storefront
HTTP surface for the customer-side payment-verify callback used by the Razorpay Checkout SDK after the user completes payment. Verifies the HMAC signature, pins to the place-time…
HTTP surface for the customer-side payment-verify callback used by the Razorpay Checkout SDK after the user completes payment. Verifies the HMAC signature, pins to the place-time order_payment row, and funnels into the shared reconciliation path. Idempotent — the webhook may win the race and the verify call still returns 200 with the current order view.
Source:
api-modules/payment-razorpay/src/controllers/store-payment-verify.controller.ts.Razorpay is one of several payment providers. Admin enable/disable + key management and the webhook ingest surface live in
docs/separated/admin/payment-razorpay.mdanddocs/separated/webhooks/payment-razorpay.md. This file documents the customer-callable SDK-handler endpoint only.
Conventions
Authentication
| Endpoint | Auth |
|---|---|
POST /store/orders/:id/payment-verify | required (customer session) |
The order must belong to the calling customer — cross-customer ids return 404, never 403. A valid Razorpay signature only proves the payment came from a real Razorpay session against this merchant account; it does not prove the session was created for this order. The endpoint also pins to the order_payment row written at place-order (the place-time razorpay_order_id) so a leaked-and-replayed signature for a different order is rejected.
Response envelope
{
"data": <OrderResponse>,
"message": "Success",
"statusCode": 200
}Error envelope
statusCode | errorCode examples |
|---|---|
| 400 | BAD_REQUEST, VALIDATION_ERROR (provider mismatch, razorpay_order_id mismatch) |
| 401 | UNAUTHORIZED (invalid Razorpay signature) |
| 404 | NOT_FOUND (order not yours, or does not exist) |
| 500 | INTERNAL_SERVER_ERROR |
Endpoint
POST /store/orders/:id/payment-verify — Confirm a Razorpay-driven payment
Called by the Razorpay Checkout SDK's handler callback. The endpoint:
- Looks up the order by
:idand rejects (404) if it doesn't belong to the caller. - Rejects (400) if the order's
paymentProvider !== "razorpay". - Looks up the place-time
order_paymentrow by(orderId, "razorpay", body.razorpay_order_id). If absent → 400razorpay_order_id does not match this order(cross-order replay protection). - Verifies the HMAC
(razorpay_order_id|razorpay_payment_id, key_secret)matchesbody.razorpay_signature. If not → 401. - If the order is already
paid(webhook reconciled first), returns the current view as 200. - Otherwise drives
reconcileSuccess— transitions the parent toconfirmed/paid, stampspaidAt, emitsorder.paid, and commits the inventory reservation.
Path params
| Name | Notes |
|---|---|
id | Internal order id (not the Razorpay order id) |
Body
{
"razorpay_payment_id": "pay_29QQoUBi66xm2f",
"razorpay_order_id": "order_DBJOWzybf0sJbb",
"razorpay_signature": "<hmac-sha256-hex>"
}| Field | Constraints |
|---|---|
razorpay_payment_id | trimmed, 1..100 chars |
razorpay_order_id | trimmed, 1..100 chars |
razorpay_signature | trimmed, 1..200 chars |
Response 200 — OrderResponse (see order.md for the full shape). The response reflects the post-reconciliation state: paymentStatus: "paid", paidAt set, pendingClientAction: null.
Errors
| Status | Code | When |
|---|---|---|
| 400 | VALIDATION_ERROR | Body fails zod |
| 400 | BAD_REQUEST | Order was not placed via the Razorpay provider |
| 400 | BAD_REQUEST | razorpay_order_id does not match the place-time row for this order |
| 401 | UNAUTHORIZED | Invalid Razorpay signature |
| 404 | NOT_FOUND | Order does not exist or belongs to another customer |
Idempotency — safe to retry. If the webhook reconciled first the second verify call returns the current view (200) without re-running reconcileSuccess. If two verifies race the row-level transition in reconcileSuccess serializes them.
Related modules
order—pendingClientActionreturned byPOST /store/checkout/place-orderis the SDK bootstrap data the storefront feeds to Razorpay Checkout; this endpoint is the back-end of that callback. Seeorder.md.webhooks/payment-razorpay— the asynchronous webhook ingest path that may reconcile before or after this verify call.
Order Module — Storefront
HTTP surface for the customer-side order lifecycle — payment provider discovery, place-order, list/detail, customer-initiated cancel, and the customer-side return flow…
Reviews Module — Storefront
HTTP surface for product reviews on the storefront — anonymous list and aggregate reads of approved reviews, plus authenticated submission. Status filters are hardcoded…