Rent Collection with Paystack Recurring Debits
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.
Tenancy Database Schema
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()
);
Monthly Rent Collection Job
// 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());
}
Learn More
This guide is part of the Paystack recurring billing series.
Key Takeaways
- ✓Tokenize tenant card at lease signing — you need a reusable authorization_code to charge each month.
- ✓Run a monthly cron job on the rent due date. Use charge_authorization, not a Paystack subscription plan.
- ✓On successful rent charge, immediately initiate a Paystack transfer to the landlord recipient.
- ✓Keep service charge and management fees from the rent amount before transferring to the landlord.
- ✓Give tenants a 5-day grace period before marking rent as overdue and sending late payment notices.
- ✓Store tenancy details in a database: tenant_email, authorization_code, rent_amount, landlord_recipient_code, due_day.
Frequently Asked Questions
- Can I charge different amounts each month for rent?
- Yes. charge_authorization lets you specify the amount on each call. Unlike Paystack subscription plans which have a fixed amount, charge_authorization is fully flexible. This is useful for rent that varies with utilities, or where the first month is prorated.
- What if a tenant disputes the automatic rent charge?
- They can raise a dispute with their bank (chargeback). Your strongest defence is a signed lease that authorizes recurring debit, a clear record of when and how much was charged, and a reference tied to a specific billing month. Keep all of this in your database and accessible for dispute resolution.
- How do I handle a tenant who wants to change their debit card?
- Send them a card update link — initialize a new tokenization charge, verify it, get the new authorization_code, and update the tenancy record. Keep the old code until the new one is confirmed. Never allow tenants to update their own authorization_code directly via your API without authentication.
- Should I use Paystack transfers or manual bank transfer to pay landlords?
- Paystack transfers (programmatic) are strongly preferred. They are instant, auditable, and trigger a transfer.success webhook. Manual bank transfers require you to log into a banking portal and are error-prone. Create a Paystack recipient for each landlord once and store their recipient_code for all future payouts.
Ready to build real-world apps?
Join the McTaba Labs full-stack marathon (4 months full-time · 6 months part-time). Learn M-Pesa, USSD, and WhatsApp engineering while shipping 8 production apps.
Apply to the McTaba Marathon