Payment PhonePe Module — Storefront
The customer-side verify endpoint called after PhonePe returns the shopper, from web and mobile alike. Takes no body — PhonePe's client callback carries no payment status, so the server reads the gateway itself.
The customer-side verify endpoint, called after PhonePe hands the shopper back — from web checkout and from the mobile SDK alike. It takes no body, because PhonePe's client-side callback carries no payment status: the server reads the Order Status API itself and reconciles.
Source:
api-modules/payment-phonepe/src/controllers/store-phonepe-verify.controller.ts.PhonePe is one of several payment providers. Configuration, the place-order integration and refunds live in
common/payment-phonepe.md; the webhook ingest surface is inwebhooks/payment-phonepe.md. This file documents the customer-callable endpoint only.
Conventions
Authentication
| Endpoint | Auth |
|---|---|
POST /store/orders/:id/phonepe/verify | required (customer session) |
The order must belong to the caller — cross-customer ids return 404, never 403, so another shopper's orders cannot be probed for existence. The endpoint also pins to the order_payment row written at place-order, so a gateway response for a different order cannot move this one.
Response envelope
{ "data": <OrderResponse>, "message": "Success", "statusCode": 200 }Endpoint
POST /store/orders/:id/phonepe/verify — Settle a PhonePe payment
Call this when PhonePe returns the customer:
- web — after the redirect back to your return URL, or when the checkout script's callback fires with
CONCLUDED; - mobile — when the SDK's activity result arrives.
Neither of those tells you whether the payment succeeded, which is why this endpoint exists. It:
- Looks up the order and rejects (404) if it isn't the caller's.
- Rejects (400) if the order wasn't placed via PhonePe.
- Returns the current order view if it is already
paid— the webhook may have won the race. - Otherwise reads
GET /checkout/v2/order/{merchantOrderId}/status, requires the rootstateto beCOMPLETEDwith an amount matching the order total, then transitions the order toconfirmed/paid, commits the inventory reservation, stampspaidAtand emitsorder.paid.
Path params
| Name | Notes |
|---|---|
id | Internal order id (not the PhonePe OMO… id) |
Body — none.
Response 200 — OrderResponse (see order.md). Reflects the post-reconciliation state: paymentStatus: "paid", paidAt set, pendingClientAction: null.
Errors
| Status | Code | When |
|---|---|---|
| 400 | BAD_REQUEST | Order was not placed via the PhonePe provider |
| 400 | BAD_REQUEST | Gateway order id doesn't match the place-time row, or the amount doesn't match the order total |
| 404 | NOT_FOUND | Order does not exist or belongs to another customer |
| 409 | PAYMENT_NOT_YET_CAPTURED | PhonePe still reports PENDING — the customer may not have finished. Retry shortly rather than treating checkout as failed |
Idempotency — safe to retry. If the webhook reconciled first, this returns the same view without re-running the transition.
If it is never called — because the browser closed or the app crashed — the webhook reconciles independently, and if that is also lost the stale-pending sweep asks PhonePe directly before cancelling. A paid order is not auto-cancelled by a missed webhook.
Related modules
order—pendingClientActionon the place-order response is the checkout bootstrap data (aredirectUrlon web, SDK order id + token on mobile); this endpoint is the back end of that flow. Seeorder.md.webhooks/payment-phonepe— the asynchronous path that may reconcile before or after this 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…
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.