OBHPayments
↑↓ navigate↵ openSearches titles and topics
Documentation

Getting started

Quickstart

From an empty project to a verified test payment. This takes about ten minutes and needs one server-side credential — no provider account, no dashboard setup.

Before you begin

RequirementWhy
Node.js 20 or newerThe package uses modern fetch and Web Crypto APIs.
A service credentialA key shaped like svc_<id>.<secret>, generated automatically when your service is created in OBH Admin.
A return URLAn absolute URL registered in the service’s exact allowlist or set as its default return URL.
A server runtimeA Node.js process, Next.js route handler, server action, worker, or similar. Never browser code.

No credential yet?

A service and its TEST credential are created by an OBH administrator in OBH Admin. The SDK cannot create credentials, and there is no self-service signup.

1. Install the package

Inside this monorepo the package is available through the workspace. From a standalone project, install it with your package manager.

Terminalbash
# In this monorepo, add the workspace dependencypnpm --filter web add @obhco/payments@workspace:* # The package is not on npm yet, so a public install is not possible# pnpm add @obhco/payments

The package is ESM-only and declares "type": "module". A CommonJS project can still use it via dynamic import(), but native ESM is the supported path.

2. Add your credentials

Keep the service key in server-side environment configuration. The secret must never reach a browser bundle, a client component, or a log.

.env.localbash
# Server-only. Do not prefix with NEXT_PUBLIC_.OBH_SERVICE_KEY="svc_2f4a9c18e0d14b2a.example-secret-value"# Optional: set to "true" in local/test environments to use the OBH Sadad simulatorOBH_SIMULATOR="true"
lib/obh.tsts
import { OBHPayments } from "@obhco/payments" // Requests automatically target https://obhco.ir/api/v1export const obh = new OBHPayments({  secretKey: process.env.OBH_SERVICE_KEY!,  timeoutMs: 15_000,})

The browser guard is not a secret manager

The SDK throws if it detects window, which catches accidents early. It cannot prevent a secret that was bundled for the client from being read. Store the key in server-only configuration.

3. Create the client

The constructor validates the credential prefix, the base URL, the timeout, and the fetch implementation immediately. Misconfiguration fails at startup instead of during a checkout.

TypeScriptts
import { OBHPayments } from "@obhco/payments" const obh = new OBHPayments({  secretKey: "svc_2f4a9c18e0d14b2a.example-secret-value",})// Uses https://obhco.ir/api/v1 by default and reads process.env.OBH_SIMULATOR

The client is stateless apart from configuration, so you can reuse a single instance for the lifetime of the process. See Configuration for every option and the exact URL rules.

4. Create a payment

A payment needs an order id from your system, an amount, and a description. The return URL tells OBH where the customer should land after checkout.

app/checkout/route.tsts
import { NextResponse } from "next/server" import { obh } from "@/lib/obh" export async function POST(request: Request) {  const form = await request.formData()  const orderId = String(form.get("orderId"))   const payment = await obh.startPayment({    externalOrderId: orderId,    amount: "245000000",    currency: "IRR",    description: "Annual design workspace",    returnUrl: "https://igraphical.ir/payments/return",    metadata: { plan: "annual" },    // Persist this key with the order before sending the request.    idempotencyKey: `order-${orderId}-attempt-1`,  })   // Save payment.reference next to the order, then send the customer  // to the provider's checkout page.  await saveReference(orderId, payment.reference)   if (payment.checkoutUrl) {    return NextResponse.redirect(payment.checkoutUrl)  }   return NextResponse.json({ payment })}

The response contains a reference that belongs to OBH, and usually a checkoutUrl. Some adapters complete the payment without a redirect, in which case checkoutUrl is null — always branch on it rather than assuming a redirect exists.

5. Verify the result

When the customer comes back, do not read the URL for a status. Look up the payment you stored and ask OBH.

app/payments/return/page.tsxtsx
import { notFound } from "next/navigation" import { obh } from "@/lib/obh" export default async function ReturnPage({  searchParams,}: {  searchParams: Promise<{ reference?: string }>}) {  const { reference } = await searchParams   if (!reference) {    notFound()  }   const payment = await obh.getPayment(reference)   if (payment.status === "completed") {    return <p>Thanks — your payment is confirmed.</p>  }   if (payment.status === "pending") {    return <p>Still confirming. This page will update shortly.</p>  }   return <p>We could not confirm this payment ({payment.status}).</p>}

Never fulfil on a redirect alone

A customer can edit a return URL, replay it, or close the browser before the provider confirms anything. Only status === "completed" from getPayment() (or a verified webhook followed by a retrieve) is proof of payment.

Checklist

The credential lives only in server-side configuration.
The base URL is HTTPS outside localhost.
The return URL is registered in the service allowlist.
The payment reference is stored with your order.
Fulfilment happens only after a retrieved status is completed.
An idempotency key is stable for a given order attempt.

Common mistakes

1

Importing the SDK in a client component

A file marked "use client" is built for the browser, so the constructor throws. Do the call in a route handler, server action, or a module marked server-only.

2

Using http:// in production

Only localhost, 127.0.0.1, and [::1] may use plain HTTP. Anything else must be https://.

3

Sending numbers instead of strings

Amounts are decimal strings: "245000000", not 245000000. This avoids floating-point rounding at the boundary.

4

Generating a new idempotency key on retry

If a request times out and you retry with a fresh key, OBH may create a second payment. Derive the key once and persist it. See Idempotency & retries.

Prefer a complete framework example? Read the Next.js integration guide.