At policy enrollment, initialize a zero-naira charge or small tokenization payment to get a reusable authorization_code for the customer card. Store it against the policy. On each premium due date, call POST /transaction/charge_authorization with the stored authorization_code and the premium amount. Handle invoice.payment_failed for dunning, and lapse the policy after 3 failed attempts.
When a customer signs up for a policy, collect their card and get a reusable authorization code. Use a small charge (e.g., NGN 50) that you refund immediately, or a zero-value charge if Paystack supports it on your account.
// Step 1: Initialize a tokenization payment
app.post('/api/policy/enroll', async (req, res) => {
var { email, policyNumber, premium } = req.body;
var reference = 'enroll_' + policyNumber + '_' + Date.now();
var response = await fetch('https://api.paystack.co/transaction/initialize', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
email,
amount: 5000, // NGN 50 tokenization charge
reference,
metadata: {
policy_number: policyNumber,
action: 'card_tokenization',
},
}),
});
var data = await response.json();
res.json({ authorization_url: data.data.authorization_url, reference });
});
// Step 2: After payment, verify and store the authorization_code
app.post('/api/policy/confirm-enrollment', async (req, res) => {
var { reference } = req.body;
var verifyRes = await fetch(
'https://api.paystack.co/transaction/verify/' + reference,
{ headers: { Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY } }
);
var data = await verifyRes.json();
if (data.data.status === 'success') {
var auth = data.data.authorization;
await db.policies.update({
authorization_code: auth.authorization_code,
card_last4: auth.last4,
card_brand: auth.brand,
card_reusable: auth.reusable,
}, { where: { policy_number: data.data.metadata.policy_number } });
// Optionally refund the NGN 50 tokenization charge
await refundTransaction(reference);
res.json({ enrolled: true });
}
});
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
// Handle failed charge via charge.failed webhook
if (event.event === 'charge.failed') {
var policyNumber = event.data.metadata?.policy_number;
if (!policyNumber) return;
var policy = await db.policies.findByPolicyNumber(policyNumber);
var failedAttempts = (policy.failed_attempts || 0) + 1;
if (failedAttempts >= 3) {
// Lapse the policy
await db.policies.update({
status: 'lapsed',
lapsed_at: new Date().toISOString(),
failed_attempts: failedAttempts,
}, { where: { policy_number: policyNumber } });
await sendPolicyLapseNotification(policy.customer_email, policyNumber);
} else {
// Schedule retry: +3 days for attempt 1, +7 days for attempt 2
var daysUntilRetry = failedAttempts === 1 ? 3 : 7;
var retryDate = new Date();
retryDate.setDate(retryDate.getDate() + daysUntilRetry);
await db.policies.update({
failed_attempts: failedAttempts,
next_premium_date: retryDate.toISOString().split('T')[0],
}, { where: { policy_number: policyNumber } });
await sendPaymentFailedNotification(policy.customer_email, { failedAttempts, retryDate });
}
}
This guide is part of the Paystack recurring billing series.
KES 2,999
The definitive guide to building payment systems with Paystack — from your first transaction to production-grade payment infrastructure across Africa. 9 modules, 47 lessons. Free preview available.
Start Paystack Course