Accepting Payments
Learn the reviewed public payment flow in the current repo: create charges, verify outcomes, refund when needed, and reuse saved payment methods for recurring billing.
Some older payment docs still reference a hosted-checkout initialize flow. The live reviewed public contract in this repo is the charges, refunds, and tokens surface documented here.
What You’ll Learn
create customer-present charges
verify charge status
process refunds
tokenize payment methods safely
run recurring merchant-initiated renewals
handle webhooks for real-time updates
Prerequisites
Before starting, ensure you have:
Zentra developer account (Create account )
your API keys from the dashboard
a webhook endpoint configured
a stable idempotency strategy in your backend
Payment Flow Overview
customer present -> your backend -> /api/v1/payments/charges
-> your backend -> /api/v1/payments/charges/{reference}/verify
-> your backend -> business state update
-> later renewals -> /api/v1/payments/charges (merchant_initiated)
Step 1: Tokenize a Payment Method
Tokenize the payment method so your backend does not need to store raw payment credentials.
Reviewed public token routes:
POST /api/v1/payments/tokens/card
POST /api/v1/payments/tokens/bank-account
GET /api/v1/payments/customers/{customer_id}/tokens
Step 2: Create a Customer-Present Charge
Create the initial charge with a stable reference and an idempotency key.
create-charge.js
create_charge.py
async function createCharge ( order , paymentTokenId ) {
return client . payments . charge ({
amount_minor: order . total_minor ,
customer_id: order . customer_id ,
currency: "NGN" ,
payment_token_id: paymentTokenId ,
capture_mode: "customer_action_required" ,
reference: `ORD_ ${ order . id } ` ,
idempotency_key: `charge_ ${ order . id } `
});
}
Step 3: Verify the Charge
Verify the final status before you mark the order as paid.
verify-charge.js
verify_charge.py
async function finalizeCharge ( reference ) {
const verification = await client . payments . verify ( reference );
if ( verification . status === "success" ) {
await db . orders . update ({
where: { reference },
data: {
status: "paid" ,
paid_at: new Date (),
payment_method: verification . channel ,
},
});
}
return verification ;
}
For setup charges, verification is also the step that makes a saved payment token ready for later merchant_initiated renewals.
Step 4: Handle Webhooks
Set up webhooks for reliable payment confirmation and refunds.
app . post ( "/webhooks/zentra" , async ( req , res ) => {
const signature = req . headers [ "x-zentra-signature" ];
const isValid = client . webhooks . verify ( req . body , signature );
if ( ! isValid ) {
return res . status ( 401 ). send ( "Invalid signature" );
}
const { event , data } = req . body ;
switch ( event ) {
case "payment.success" :
await finalizeCharge ( data . reference );
break ;
case "payment.refunded" :
await handleRefund ( data );
break ;
}
res . status ( 200 ). send ( "OK" );
});
Step 5: Process Refunds
Use POST /api/v1/payments/refunds with a stable business reference and idempotency key.
const refund = await client . payments . refund ({
charge_reference: "ORD_123" ,
amount_minor: 25000 ,
reason: "customer_request" ,
idempotency_key: "refund_ORD_123_partial_1" ,
});
Step 6: Run Recurring Renewals
After a verified customer-present setup charge, later renewals can reuse the same token:
const renewal = await client . payments . charge ({
amount_minor: invoice . amount_minor ,
customer_id: invoice . customer_id ,
payment_token_id: invoice . payment_token_id ,
reference: `INV_ ${ invoice . id } ` ,
capture_mode: "merchant_initiated" ,
idempotency_key: `invoice_ ${ invoice . id } ` ,
});
Recurring renewals should fail closed if the token was never primed by a verified customer-present charge.
Best Practices
keep all amounts in integer minor units
make every charge and refund idempotent
verify server-side before updating business state
treat webhooks as replay-safe and signature-verified
never store raw PAN or CVV in your own application
Next Steps
Payments Overview Review the current charge, refund, and token contract.
Handling Webhooks Build replay-safe event processing.