Payment (DOKU)
Payment is DOKU. The backend creates the payment and verifies the callback; the storefront opens DOKU's checkout widget.
How this page was verified
Read on 2026-09-15 from the backend payments module and the storefront DOKU hook.
Backend
The payments module has no entities of its own — payment data lives on the order. It is REST-only (not in the GraphQL include).
PaymentsService(src/payments/services/payments.service.ts) is the DOKU client:postPaymentRequestandcheckPaymentStatus. Base URLDOKU_BASEURL; credentialsDOKU_CLIENT_IDandDOKU_SECRET_KEY; each request is signed HMAC-SHA256 (HMACSHA256=<base64>). The payment window isDOKU_PAYMENT_DUE_DATE_MINUTE.- Callback URLs are built from
APP_FRONTEND_STOREFRONT_BASE_URL; DOKU's notify goes to${APP_BACKEND_BASE_URL}/webhooks/payments. webhook-payments.service.tshandleEvent: ontransaction.status === 'SUCCESS'it moves the order toprocessing. The backend is created withrawBody: trueso it can verify the DOKU signature.
The order is created with a payment link during createOrder() — see Cart and checkout.
Storefront
src/hooks/useDokuCheckoutPayment.tsx loads DOKU's Jokul Checkout script (sandbox vs production URL by NEXT_PUBLIC_DOKU_ENV) and calls window.loadJokulCheckout(paymentUrl) with the paymentLinkUrl returned by frontValidateMemberCartCheckout. On close the customer lands on /user/my-order?status=pending_payment. The same hook powers "pay again" from an order detail (src/modules/my-order/components/order-item-detail.tsx).
Unpaid orders
An order left pending_payment past its window is cancelled by the order expiry cron, which re-checks DOKU first.
Gotchas
- Signature needs the raw body. The backend keeps
rawBody: truefor exactly this — do not strip it. - In dev, DOKU is the simulator. The simulator can force DOKU to error; a dev payment failure may be the toggle, not the code.
- The inbound production callback path fans out through the webhook receiver → SNS, not straight to the backend.