Building
Gateways & installments
OBH offers more than one payment gateway. Call listGateways() to show them on your checkout page, then pass the customer's choice to startPayment(). Requires @obhco/payments 0.2.0.
Available gateways
| Code | Kind | What the customer does |
|---|---|---|
sadad | card (default) | Pays the full amount with any Shetab bank card through Sadad (Bank Melli Iran). |
zarinpal | card | Pays the full amount with any Shetab bank card through Zarinpal. Minimum 10,000 R. |
mellat | card | Pays the full amount with any Shetab bank card through Bank Mellat (Behpardakht). |
digipay | installment | Buys now and pays later in installments with Digipay credit (BNPL), or pays with a Digipay wallet or card. Requires the customer's mobile number. |
Do not hard-code this list
OBH lists only the gateways that are currently enabled. A gateway without LIVE credentials disappears from the live list, and new gateways appear without an SDK change.
List gateways
class OBHPayments { listGateways(options?: { simulator?: boolean }): Promise<PaymentGateway[]>}/api/v1/gateways?simulator=false| Property | Type | Description |
|---|---|---|
| coderequired | "sadad" | "zarinpal" | "mellat" | "digipay" | string | Pass this to startPayment as gateway. |
| displayNamerequired | string | Persian label ready for a checkout page, e.g. خرید اقساطی با دیجیپی. |
| kindrequired | "card" | "installment" | Use it to group or label options. |
| requiresCustomerMobilerequired | boolean | When true, collect the customer's mobile number and pass customerMobile. |
| isDefaultrequired | boolean | The gateway used when you omit gateway. |
| minAmountrequired | string | null | Minimum whole Rial amount, or null when there is none. |
{ "data": [ { "code": "sadad", "name": "Sadad (Bank Melli Iran)", "displayName": "پرداخت با کارت بانکی (سداد)", "kind": "card", "requiresCustomerMobile": false, "minAmount": null, "isDefault": true, "environment": "live" }, { "code": "zarinpal", "name": "Zarinpal", "displayName": "پرداخت با زرینپال", "kind": "card", "requiresCustomerMobile": false, "minAmount": "10000", "isDefault": false, "environment": "live" }, { "code": "mellat", "name": "Mellat (Behpardakht)", "displayName": "پرداخت با کارت بانکی (بهپرداخت ملت)", "kind": "card", "requiresCustomerMobile": false, "minAmount": null, "isDefault": false, "environment": "live" }, { "code": "digipay", "name": "Digipay (Installments / BNPL)", "displayName": "خرید اقساطی با دیجیپی", "kind": "installment", "requiresCustomerMobile": true, "minAmount": null, "isDefault": false, "environment": "live" } ]}Build a checkout picker
Load the gateways on your server
const gateways = await obh.listGateways()Let the customer choose
Render one option per gateway using displayName. If the chosen gateway has requiresCustomerMobile, ask for the customer's mobile number.
Start the payment with that gateway
"use server" import { redirect } from "next/navigation" import { obh } from "@/lib/obh" export async function checkout(form: FormData) { const gateways = await obh.listGateways() const chosen = gateways.find((g) => g.code === form.get("gateway")) if (!chosen) throw new Error("Unknown gateway") const payment = await obh.startPayment({ externalOrderId: String(form.get("orderId")), amount: "15000000", description: "Pro subscription (yearly)", gateway: chosen.code, customerMobile: chosen.requiresCustomerMobile ? String(form.get("mobile")) : undefined, idempotencyKey: `order-${form.get("orderId")}-${chosen.code}`, }) redirect(payment.checkoutUrl!)}Verify the return the same way as always
verifyReturn() and getPayment() work for every gateway. Trust only payment.status. payment.gateway tells you which gateway processed the payment.
A new gateway is a new request
The gateway and mobile number are part of the idempotency fingerprint. If the customer switches gateway, use a new idempotency key (for example, include the gateway code). Reusing the old key returns 409 IDEMPOTENCY_CONFLICT.
Digipay installments
With Digipay the customer pays the first installment at checkout and the rest later. For your service nothing changes: once Digipay verifies the purchase, the payment is completed for the full amount, and you can fulfil the order immediately. Digipay owes the remaining installments to OBH, not your customer to you.
customerMobileis required.09…,+989…and989…forms are accepted.- Receipt fields map to Digipay values:
referenceNumberis the DigipaytrackingCodeandorderNumberis theproviderId. - A customer may also choose a Digipay wallet or card on Digipay's page. Those are full payments, but the result looks the same to you.
Testing with the simulator
With simulator: true, listGateways() returns every gateway, and Digipay payments open the OBH Digipay simulator at /pay/digipay/<ticket>. There you can simulate a BNPL, credit, wallet or card purchase, or a failure. Zarinpal payments open /pay/zarinpal/<authority> and Mellat payments open /pay/mellat/<refId>.
Errors
| Code | Meaning |
|---|---|
INVALID_ARGUMENT | The SDK rejected a malformed gateway or customerMobile before calling the API. |
GATEWAY_NOT_SUPPORTED | 422 — the gateway code is not one OBH offers. |
CUSTOMER_MOBILE_REQUIRED | 422 — the gateway needs the customer's mobile number. |
GATEWAY_NOT_AVAILABLE | 503 — the gateway is not enabled for LIVE payments. Offer another one from listGateways(). |
Everything else behaves as described in Create a payment and Errors.