Your First Payment
This guide walks you through charging a customer, verifying the transaction, and handling the result. We’ll use Chapa for the example — it has the most straightforward sandbox setup.
1. Initialize the Client
Section titled “1. Initialize the Client”import { Zirzir } from '@zirzir/sdk'
const zirzir = new Zirzir({ provider: 'chapa', credentials: { secretKey: process.env.CHAPA_SECRET_KEY! }})2. Initiate a Charge
Section titled “2. Initiate a Charge”const transaction = await zirzir.charge({ amount: 500, // Amount in ETB (no decimals for Ethiopian Birr) currency: 'ETB', email: 'abebe@example.com', firstName: 'Abebe', lastName: 'Kebede', txRef: 'order_123', // Your own unique reference callbackUrl: 'https://yourapp.com/payment/callback', returnUrl: 'https://yourapp.com/payment/success',})
console.log(transaction.checkoutUrl) // Redirect customer hereThe charge() method returns a normalized Transaction object:
{ id: 'zz_tx_01HX...', // Zirzir's transaction ID externalId: 'chapa_tx_abc123', // Chapa's transaction ID status: 'pending', amount: 500, currency: 'ETB', provider: 'chapa', checkoutUrl: 'https://checkout.chapa.co/...', txRef: 'order_123', createdAt: '2024-01-15T10:30:00Z', metadata: { /* raw provider response */ }}3. Redirect the Customer
Section titled “3. Redirect the Customer”After calling charge(), redirect the customer to transaction.checkoutUrl. They’ll complete payment on the provider’s page and be sent back to your returnUrl.
// Express exampleapp.post('/checkout', async (req, res) => { const transaction = await zirzir.charge({ amount: req.body.amount, currency: 'ETB', email: req.body.email, txRef: `order_${Date.now()}`, returnUrl: 'https://yourapp.com/payment/success', callbackUrl: 'https://yourapp.com/webhooks/zirzir', })
res.redirect(transaction.checkoutUrl)})4. Verify the Transaction
Section titled “4. Verify the Transaction”Never trust the return URL alone — always verify server-side.
app.get('/payment/success', async (req, res) => { const { txRef } = req.query
const transaction = await zirzir.verify(txRef as string)
if (transaction.status === 'success') { // Fulfill the order await fulfillOrder(transaction.txRef) res.render('success', { transaction }) } else { res.render('failed', { transaction }) }})Transaction Statuses
Section titled “Transaction Statuses”| Status | Meaning |
|---|---|
pending | Payment initiated, awaiting customer action |
success | Payment confirmed and settled |
failed | Payment failed or was declined |
cancelled | Customer cancelled on the checkout page |
refunded | Payment was refunded |
5. Handle Webhooks
Section titled “5. Handle Webhooks”Return URLs can fail — the customer closes the tab, their internet drops. Webhooks are the reliable signal. Set up a webhook endpoint:
app.post('/webhooks/zirzir', async (req, res) => { // Verify webhook signature const isValid = zirzir.webhooks.verify( req.rawBody, req.headers['x-zirzir-signature'] as string )
if (!isValid) { return res.status(401).send('Invalid signature') }
const event = req.body
if (event.type === 'transaction.success') { await fulfillOrder(event.data.txRef) }
res.status(200).send('OK')})See Webhooks for the full webhook reference including event types, retry behavior, and signature verification.
Full Example
Section titled “Full Example”import express from 'express'import { Zirzir } from '@zirzir/sdk'
const app = express()const zirzir = new Zirzir({ provider: 'chapa', credentials: { secretKey: process.env.CHAPA_SECRET_KEY! }})
// Initiate paymentapp.post('/pay', async (req, res) => { try { const tx = await zirzir.charge({ amount: req.body.amount, currency: 'ETB', email: req.body.email, txRef: `order_${Date.now()}`, returnUrl: `${process.env.APP_URL}/payment/success`, callbackUrl: `${process.env.APP_URL}/webhooks/zirzir`, }) res.redirect(tx.checkoutUrl) } catch (err) { res.status(500).json({ error: err.message }) }})
// Verify on returnapp.get('/payment/success', async (req, res) => { const tx = await zirzir.verify(req.query.txRef as string) res.json({ status: tx.status, amount: tx.amount })})
// Webhook handlerapp.post('/webhooks/zirzir', express.raw({ type: 'application/json' }), async (req, res) => { const valid = zirzir.webhooks.verify(req.body, req.headers['x-zirzir-signature'] as string) if (!valid) return res.sendStatus(401)
const event = JSON.parse(req.body.toString()) if (event.type === 'transaction.success') { await fulfillOrder(event.data.txRef) } res.sendStatus(200)})
app.listen(3000)Next Steps
Section titled “Next Steps”- verify() & refund() — how to issue full and partial refunds
- Webhooks — reliable event delivery
- Provider Guides — provider-specific quirks and sandbox setup