# UnoPay > One typed API for Iranian Internet Payment Gateways. `createPayment` to start, > `verifyCallback` to confirm. Verified against unopay 1.0.0 at commit 4a04be5. UnoPay is a strictly-typed TypeScript SDK with zero runtime dependencies, built on standard `fetch`. One adapter per gateway, one calling convention for all of them. - Package: https://www.npmjs.com/package/unopay - Source: https://github.com/unopay-sdk/unopay-typescript - License: ISC (per the README; the repository at 4a04be5 has no LICENSE file) - Verified against: commit 4a04be5 ## Public API Exactly 15 symbols are re-exported by `src/index.ts`, plus two methods on `UnoPay`. `src/http.ts` exports `request` and `HttpOptions` but is never re-exported, so both are internal. ### Classes - `UnoPay>` - `ZarinpalAdapter implements GatewayAdapter` ### Interfaces - `PaymentRequest` — amount, currency, callbackUrl, metadata? - `PaymentResult` — redirectUrl, transactionId, providerToken - `CallbackRequest` — method, query, body - `VerificationResult` — isSuccessful, transactionId, settledAmount, errorReason? - `PaymentLogger` — info, error, warn, debug - `GatewayAdapter` — createPayment, verifyCallback - `ZarinpalConfig` — merchantId, sandbox?, logger? ### Functions - `toRials(amount: number, currency: string): number` ### Error classes - `PaymentError` — base. Properties: code, cause. Also thrown directly with `code: 'ADAPTER_NOT_FOUND'`. - `PaymentValidationError` — code `'VALIDATION_ERROR'` - `GatewayNetworkError` — code `'NETWORK_ERROR'` - `InvalidSignatureError` — code `'INVALID_SIGNATURE'` — exported but never thrown in 1.0.0 - `GatewayProviderError` — code `'PROVIDER_ERROR'` ### Client methods - `createPayment(provider, request): Promise` - `verifyCallback(provider, request): Promise` ## Minimal integration ```ts import { UnoPay, ZarinpalAdapter } from 'unopay'; const unopay = new UnoPay({ zarinpal: new ZarinpalAdapter({ merchantId: process.env.ZARINPAL_MERCHANT_ID! }), }); const payment = await unopay.createPayment('zarinpal', { amount: 50000, currency: 'IRT', callbackUrl: 'https://yoursite.com/api/payment/callback', }); // redirect the payer to payment.redirectUrl const result = await unopay.verifyCallback('zarinpal', { method: req.method, query: req.query as Record, body: {}, }); // result.isSuccessful === false is a normal return, not an exception ``` ## Verified behaviours that contradict the SDK README These were found by reading `src/`. The site documents the source. 1. `CallbackRequest.body` is `Record` at `src/types.ts:17`. The README advertises zero `any` types. This is the only one in the public surface. 2. The README says `PaymentValidationError` covers `amount <= 0`. There is no amount validation in `createPayment`. A zero or negative amount reaches the gateway and returns `GatewayProviderError`. `PaymentValidationError` has exactly three throw sites: the `ZarinpalAdapter` constructor for a missing `merchantId`, and `verifyCallback` for a missing `Authority` or a non-numeric `amount`. 3. There is no retry, and no configurable timeout. `src/http.ts` sets a hardcoded `10000` ms `AbortController`. `HttpOptions.timeoutMs` is unreachable because `src/index.ts` never re-exports that module. 4. `transactionId` means the authority before verification and `ref_id` after. `settledAmount` echoes the callback amount; it is not a gateway-confirmed figure. 5. The repository has no `LICENSE` file and no `license` field in `package.json`, though the README links to one. ## Lifecycle events Emitted by the client, not by adapters. Pass a logger as the second constructor argument. | Event | Level | Context | |---|---|---| | `payment_started` | info | `{ provider, request }` | | `provider_request_sent` | info | `{ provider, request }` | | `payment_failed` | error | `{ provider, error }` | | `verification_completed` | info | `{ provider, result }` | | `verification_failed` | error | `{ provider, error }` | There is no event for a successful response, and `warn`/`debug` are never called in 1.0.0. The adapter-not-found check runs before `payment_started`, so a wrong provider key rejects with no log output at all. ## Currency `toRials` is the entire currency layer: - `IRT` or `TOMAN` (case-insensitive) → amount × 10 - anything else → amount unchanged, treated as IRR No rounding. An unknown code never throws — `'TOMN'` silently behaves as IRR. ## Gateways Eleven gateways in the SDK's `docs/README.md` comparison matrix. One implemented, three named as planned in the README, seven specification only. | Gateway | فارسی | Type | Protocol | Sandbox | SDK | |---|---|---|---|---|---| | ZarinPal | زرین‌پال | Payment facilitator | REST (JSON) | yes | `ZarinpalAdapter` | | IDPay | آیدی‌پی | Payment facilitator | REST (JSON) | yes (`X-SANDBOX`) | `IdpayAdapter` planned | | Behpardakht Mellat | به‌پرداخت ملت | Bank PSP | SOAP 1.1 (WSDL) | no | `BehpardakhtAdapter` planned | | Saman SEP | سامان کیش | Bank PSP | REST / SOAP, token | no | `SamanAdapter` planned | | Sadad Melli | سداد بانک ملی | Bank PSP | REST / SOAP | no | spec only | | Parsian PEC | تجارت الکترونیک پارسیان | Bank PSP | SOAP / token REST | no | spec only | | Pasargad PEP | پرداخت الکترونیک پاسارگاد | Bank PSP | REST / Redirect | no | spec only | | Asan Pardakht | آسان پرداخت | Bank PSP | REST / SOAP | no | spec only | | Pardakht Novin | پرداخت نوین آرین | Bank PSP | REST / SOAP | no | spec only | | IranKish | ایران‌کیش | Bank PSP | REST / SOAP | no | spec only | | SizPay | سیزپی | Payment facilitator | REST (JSON) | yes | spec only | Two further document sets ship in `docs/` with no matrix row: Fanava Card (فن‌آوا کارت) and Sarmayeh Bank (بانک سرمایه). ## ZarinPal specifics - Base URL: `sandbox.zarinpal.com/pg` when `sandbox: true`, else `payment.zarinpal.com/pg`. - Authority is 36 characters. Sandbox begins `S`, production begins `A`. - `request.json` must answer code `100` or the adapter throws `GatewayProviderError`. - `verify.json` answers `100` first time and `101` on a repeat; both map to `isSuccessful: true`, which makes duplicate callbacks safe at the gateway level. - The callback is a GET with `Authority` and `Status` (`OK` / `NOK`). - `createPayment` sends `description` from `metadata.description`, defaulting to `"Payment"`. ## Runtime support Node 18+, Bun, Deno, Cloudflare Workers, Vercel Edge. No Node built-ins are used; the only platform APIs are `fetch`, `AbortController`, `setTimeout` and `JSON`. There is no retry and the timeout is fixed at 10 seconds, so budget for that on short request lifetimes. ## Site map Every route is a real URL under the site root, prerendered to static HTML. - `/` home - `/quickstart` - `/concepts/client-and-adapters`, `/concepts/payment-lifecycle`, `/concepts/currency`, `/concepts/callback-verification`, `/concepts/errors-and-retries`, `/concepts/logging` - `/api`, `/api/uno-pay`, `/api/gateway-adapter`, `/api/create-payment`, `/api/verify-callback`, `/api/types`, `/api/zarinpal-adapter`, `/api/errors` - `/gateways`, `/gateways/` - `/errors` - `/guides/failed-payments`, `/guides/signatures`, `/guides/currency-pitfalls`, `/guides/edge-runtimes`, `/guides/sandbox-testing`, `/guides/migrating-gateways`, `/guides/webhooks-and-idempotency` - `/changelog`, `/contributing`, `/design-system`, `/inventory` Persian only (فارسی) with RTL layout. Every page is prerendered, so the content and its code samples are readable without JavaScript.