Skip to main content

Ecommerce payments

Ecommerce is the only channel that uses Razorpay. COD and Razorpay are toggled in admin ecommerce settings (payment_cod_enabled / payment_razorpay_enabled on EcommerceSettings (backend-main/ecommerce/models.py)). Keys live in backend env only — see docs/RAZORPAY_SETUP.md.

Implementation: ecommerce/views.py (backend-main/ecommerce/views.py) (EcommerceCheckoutView, RazorpayPaymentVerifyView, RazorpayWebhookView), ecommerce/checkout_orders.py (backend-main/ecommerce/checkout_orders.py), ecommerce/payments.py (backend-main/ecommerce/payments.py).

Method selection​

POST /api/ecommerce/checkout/ (EcommerceCheckoutSerializer):

paymentMethodGateImmediate result
cod (default)payment_cod_enabledStock decremented → POSOrder created
razorpaypayment_razorpay_enabled + keys configuredPendingRazorpayCheckout + Razorpay order; no POSOrder yet

Admin cannot enable Razorpay in manage settings if RAZORPAY_KEY_ID / RAZORPAY_KEY_SECRET are missing (ecommerce/serializers.py (backend-main/ecommerce/serializers.py)).

COD path​

flowchart LR
Checkout[POST /checkout] --> Prep[Validate items / prices]
Prep --> Stock[allocate_and_decrement_stock]
Stock --> Order["create_cod_order → POSOrder"]

create_cod_order (backend-main/ecommerce/checkout_orders.py) sets:

  • order_source=ecommerce
  • payment_method=cod
  • payment_status=not_required
  • payment_done_later=True

Processing is allowed without online verification (PAYMENT_STATUSES_ALLOW_PROCESSING includes not_required — see Order lifecycle).

Razorpay path: pending → verify / webhook​

flowchart TD
Checkout[POST /checkout razorpay] --> Start[start_razorpay_checkout]
Start --> Pend["PendingRazorpayCheckout status=open<br/>snapshot + stock_items JSON"]
Start --> Rzp[create_razorpay_order]
Rzp --> Client[Return key_id / order_id / amount to storefront]
Client --> Pay[Customer pays in Razorpay Checkout]
Pay --> Verify[POST …/razorpay/verify/]
Pay --> Hook[POST …/razorpay/webhook/]
Verify --> Complete[complete_razorpay_payment]
Hook --> Complete
Complete --> Fulfilled["POSOrder payment_status=verified<br/>pending → fulfilled"]

Start (start_razorpay_checkout)​

  1. Build cart snapshot (build_checkout_snapshot) including line items and stock_items.
  2. Create PendingRazorpayCheckout (STATUS_OPEN).
  3. Create Razorpay order (payment_capture: 1) with receipt pb{pending.pk} and notes (pending_id, email).
  4. Store razorpay_order_id on the pending row.
  5. Return checkout_payload (key_id, order_id, amount in paise, prefill). Response also echoes payment_status: pending — this is intent metadata, not a POSOrder yet.

Stock is not reserved at start. See Stock rules and Known limitations.

Client verify (RazorpayPaymentVerifyView)​

Body: razorpay_order_id, razorpay_payment_id, razorpay_signature.

  1. verify_checkout_signature — HMAC of order_id|payment_id with RAZORPAY_KEY_SECRET (backend-main/ecommerce/payments.py).
  2. complete_razorpay_payment(…).
  3. Returns serialized POSOrder, or 404 if nothing matched.

Purpose: let the success page continue immediately. Setup doc: webhook remains the source of truth for capture.

Webhook (RazorpayWebhookView)​

  1. Verify X-Razorpay-Signature over raw body with RAZORPAY_WEBHOOK_SECRET.
  2. Handle only payment.captured and order.paid (parse_webhook_payment).
  3. Call the same complete_razorpay_payment.
  4. Other events → { "status": "ignored" }.

Fulfillment (complete_razorpay_payment / _create_order_from_pending)​

Inside transaction.atomic() + select_for_update on the pending row:

  • If pending already has pos_order → idempotent return (optionally attach razorpay_payment_id).
  • Else decrement snapshot stock_items (_decrement_snapshot_stock), then create_ecommerce_pos_order with payment_method=razorpay, payment_status=verified, Razorpay ids set, payment_verified_at stamped.
  • Mark pending STATUS_FULFILLED.

If stock decrement fails for some lines, the verified order is still created and a system comment lists missing stock — documented under Known limitations.

Legacy fallback: if no pending row exists, mark_razorpay_order_paid flips an existing Razorpay POSOrder to verified.

Env and ops​

VariableRole
RAZORPAY_KEY_IDPublic key returned to checkout UI
RAZORPAY_KEY_SECRETOrder create + checkout signature verify
RAZORPAY_WEBHOOK_SECRETWebhook HMAC

Production webhook URL and admin enable steps: docs/RAZORPAY_SETUP.md.