Skip to content

verify() & refund()

Checks the current status of a transaction. Always call this server-side before fulfilling an order — never trust the return URL alone.

zirzir.verify(txRef: string): Promise<Transaction>
ParameterTypeDescription
txRefstringYour transaction reference passed to charge()
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
}
tx = client.verify("invoice_2024_001")
if tx.status == "success":
fulfill_order(tx.tx_ref)
elif tx.status == "pending":
pass # wait for webhook
else:
raise PaymentFailedError(tx.provider_message)

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')
}

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.
zirzir.refund(txRef: string, params?: RefundParams): Promise<Refund>
ParameterTypeRequiredDescription
txRefstringYesPayment to refund: your reference, or the zirzir payment id in server mode
params.amountnumberNoPartial amount in major units (12.5). Omit for a full refund. Sent as an exact decimal; extra decimals are rejected, not rounded
params.amountMinornumberNoServer mode: partial amount in minor units (1250). Use instead of amount
params.reasonstringNoReason for the refund. Passed to the provider if supported
params.idempotencyKeystringServer modeRequired in server mode. Retries with the same key return the same refund (replayed: true); derive it from your records, e.g. refund-${orderId}
params.manualbooleanNoServer mode: refund in the provider’s dashboard yourself, then confirm on the server
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).

// Full refund
const 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.
}
from zirzir.types import RefundParams
# Full refund
refund = await client.refund("invoice_2024_001", RefundParams(idempotency_key="refund-inv-1"))
# Partial refund
refund = await client.refund(
"invoice_2024_001",
RefundParams(amount=250, reason="Customer returned half the order", idempotency_key="refund-inv-1-half"),
)
ProviderServer modeStandalone mode
ChapaRefund API (full and partial)Refund API
Telebirr, CBE Birr, SantimPay, Arifpay, LakiPayManual: refund in the provider portal, then confirmNot supported
ErrorDescription
ZirzirRefundNotSupportedErrorProvider doesn’t support programmatic refunds
ZirzirRefundAmountExceededErrorRefund amount exceeds original transaction amount
ZirzirTransactionNotRefundableErrorTransaction is not in a refundable state