verify() & refund()
verify()
Section titled “verify()”Checks the current status of a transaction. Always call this server-side before fulfilling an order — never trust the return URL alone.
Signature
Section titled “Signature”zirzir.verify(txRef: string): Promise<Transaction>Parameters
Section titled “Parameters”| Parameter | Type | Description |
|---|---|---|
txRef | string | Your transaction reference passed to charge() |
Example
Section titled “Example”TypeScript
Section titled “TypeScript”const tx = await zirzir.verify('invoice_2024_001')
switch (tx.status) { case 'success': await fulfillOrder(tx.txRef) break case 'pending': // Payment still in progress — check again or wait for webhook break case 'failed': // Payment failed — show error to customer break}Python
Section titled “Python”tx = client.verify("invoice_2024_001")
if tx.status == "success": fulfill_order(tx.tx_ref)elif tx.status == "pending": pass # wait for webhookelse: raise PaymentFailedError(tx.provider_message)Polling vs Webhooks
Section titled “Polling vs Webhooks”For USSD-based providers (Telebirr, CBEBirr, M-Pesa), charge() returns immediately while the customer completes payment on their phone. You have two options:
Option A: Webhook (Recommended)
In server mode, pass callbackUrl to charge() and handle the signed payment.completed / payment.failed / payment.cancelled event (requires server.public_url and ZIRZIR_WEBHOOK_SECRET on the server). See Webhooks. Telebirr and CBE Birr payments are not settled automatically; reconcile them in the dashboard.
Option B: Poll
Call verify() every few seconds until status changes from pending. Keep polling intervals reasonable (5-10 seconds) and set a timeout.
async function pollUntilComplete(txRef: string, maxAttempts = 12): Promise<Transaction> { for (let i = 0; i < maxAttempts; i++) { const tx = await zirzir.verify(txRef) if (tx.status !== 'pending') return tx await sleep(5000) // 5 second intervals } throw new Error('Payment timed out')}refund()
Section titled “refund()”Issues a full or partial refund for a completed transaction.
- Server mode (recommended): the refund goes through your Zirzir server. It works for every provider: Chapa refunds through its API, and other providers become a manual refund you confirm after refunding in their dashboard. The ledger guarantees you never refund more than the payment or more than your balance. See the Refunds guide.
- Standalone mode: the SDK calls the provider directly, so only providers with a refund API work (Chapa). Others throw
ZirzirRefundNotSupportedError.
Signature
Section titled “Signature”zirzir.refund(txRef: string, params?: RefundParams): Promise<Refund>Parameters
Section titled “Parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
txRef | string | Yes | Payment to refund: your reference, or the zirzir payment id in server mode |
params.amount | number | No | Partial amount in major units (12.5). Omit for a full refund. Sent as an exact decimal; extra decimals are rejected, not rounded |
params.amountMinor | number | No | Server mode: partial amount in minor units (1250). Use instead of amount |
params.reason | string | No | Reason for the refund. Passed to the provider if supported |
params.idempotencyKey | string | Server mode | Required in server mode. Retries with the same key return the same refund (replayed: true); derive it from your records, e.g. refund-${orderId} |
params.manual | boolean | No | Server mode: refund in the provider’s dashboard yourself, then confirm on the server |
Return Value
Section titled “Return Value”interface Refund { id: string transactionId: string txRef: string amount: number // Amount refunded, major units amountMinor?: number // Server mode: minor units currency: string status: 'pending' | 'processing' | 'requires_action' | 'success' | 'failed' | 'cancelled' provider: string reason: string | null method?: 'provider' | 'manual' // Server mode error?: string | null // Server mode: why it failed or needs action replayed?: boolean // Server mode: the key matched an existing refund createdAt: string}processing, requires_action and cancelled only occur in server mode. A requires_action refund is finished with POST /api/v1/refunds/{id}/confirm or /cancel (see the Refunds guide).
Examples
Section titled “Examples”TypeScript
Section titled “TypeScript”// Full refundconst refund = await zirzir.refund('invoice_2024_001', { idempotencyKey: 'refund-invoice-2024-001' })
// Partial refund (250 ETB of a 500 ETB transaction)const partial = await zirzir.refund('invoice_2024_001', { amount: 250, reason: 'Customer returned half the order', idempotencyKey: 'refund-invoice-2024-001-half',})
if (partial.status === 'requires_action') { // Manual provider: refund in its dashboard, then confirm on the server.}Python
Section titled “Python”from zirzir.types import RefundParams
# Full refundrefund = await client.refund("invoice_2024_001", RefundParams(idempotency_key="refund-inv-1"))
# Partial refundrefund = await client.refund( "invoice_2024_001", RefundParams(amount=250, reason="Customer returned half the order", idempotency_key="refund-inv-1-half"),)Provider Refund Support
Section titled “Provider Refund Support”| Provider | Server mode | Standalone mode |
|---|---|---|
| Chapa | Refund API (full and partial) | Refund API |
| Telebirr, CBE Birr, SantimPay, Arifpay, LakiPay | Manual: refund in the provider portal, then confirm | Not supported |
Errors
Section titled “Errors”| Error | Description |
|---|---|
ZirzirRefundNotSupportedError | Provider doesn’t support programmatic refunds |
ZirzirRefundAmountExceededError | Refund amount exceeds original transaction amount |
ZirzirTransactionNotRefundableError | Transaction is not in a refundable state |