At lease signing, tokenize the tenant card with a small authorization charge. Store the authorization_code against the tenancy record. On the first of each month, run a cron job that calls charge_authorization for each active tenancy. On success, trigger a Paystack transfer to the landlord recipient. On failure, notify the tenant and set a grace period before flagging as overdue.
CREATE TABLE tenancies (
id SERIAL PRIMARY KEY,
tenant_email TEXT NOT NULL,
tenant_name TEXT NOT NULL,
authorization_code TEXT, -- Paystack reusable auth
card_last4 TEXT,
property_id INTEGER NOT NULL,
landlord_recipient_code TEXT NOT NULL, -- Paystack transfer recipient
rent_amount NUMERIC(12, 2) NOT NULL, -- Monthly rent in local currency
service_charge NUMERIC(12, 2) DEFAULT 0,
currency TEXT DEFAULT 'NGN',
rent_due_day INTEGER DEFAULT 1, -- Day of month
lease_start DATE NOT NULL,
lease_end DATE NOT NULL,
status TEXT DEFAULT 'active', -- active | overdue | terminated
last_paid_date DATE,
failed_attempts INTEGER DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW()
);
// collect-rent.js — run on the 1st of each month (or configured due day)
async function collectMonthlyRent() {
var today = new Date();
var currentDay = today.getDate();
var dueTenancies = await db.tenancies.findAll({
where: {
status: 'active',
rent_due_day: currentDay,
authorization_code: { notNull: true },
lease_end: { gt: today },
}
});
for (var tenancy of dueTenancies) {
var reference = 'rent_' + tenancy.id + '_' + today.toISOString().slice(0, 7);
// Check not already collected this month
var alreadyPaid = await db.payments.findOne({
where: { tenancy_id: tenancy.id, reference }
});
if (alreadyPaid) continue;
var totalCharge = tenancy.rent_amount + tenancy.service_charge;
var response = await fetch('https://api.paystack.co/transaction/charge_authorization', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: tenancy.tenant_email,
amount: Math.round(totalCharge * 100),
authorization_code: tenancy.authorization_code,
reference,
currency: tenancy.currency,
metadata: { tenancy_id: tenancy.id, type: 'rent' },
}),
});
var data = await response.json();
if (data.data?.status === 'success') {
await db.payments.create({ tenancy_id: tenancy.id, reference, amount: totalCharge, status: 'paid' });
await db.tenancies.update({ last_paid_date: today, failed_attempts: 0 }, { where: { id: tenancy.id } });
// Pay landlord (minus service charge)
await initiateTransfer(tenancy.landlord_recipient_code, tenancy.rent_amount, tenancy.currency);
}
}
}
async function initiateTransfer(recipientCode, amount, currency) {
return fetch('https://api.paystack.co/transfer', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
source: 'balance',
recipient: recipientCode,
amount: Math.round(amount * 100),
currency,
reason: 'Monthly rent payment',
}),
}).then(r => r.json());
}
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
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