Paystack partial debit allows you to charge a card for up to a requested amount, capturing whatever is available if the full amount is not. Pass at_least (minimum acceptable amount in kobo) alongside the full amount when calling charge_authorization. If the card has enough for at_least but not the full amount, Paystack charges what is available. You then track the remaining balance and collect it later.
Normally, a Paystack charge is all or nothing. You request 10,000 NGN. If the card has 10,000 NGN or more, the charge succeeds. If the card has 9,999 NGN, the charge fails entirely. The customer gets an "insufficient funds" error.
Partial debit changes this. You request 10,000 NGN but tell Paystack you will accept as little as 5,000 NGN. If the card has 7,500 NGN, Paystack charges 7,500 NGN and tells you what was captured. You handle the remaining 2,500 NGN separately.
This is useful when:
Partial debit works with the /transaction/charge_authorization endpoint, which means you need a saved authorization code from a previous successful payment. It does not work with Initialize Transaction or the first-time Charge API flow.
// Charge with partial debit enabled
async function chargeWithPartialDebit(authCode, email, fullAmountKobo, minimumAmountKobo) {
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({
authorization_code: authCode,
email: email,
amount: fullAmountKobo,
at_least: minimumAmountKobo,
currency: 'NGN',
}),
});
var data = await response.json();
if (data.data.status === 'success') {
var charged = data.data.amount;
var requested = fullAmountKobo;
var remaining = requested - charged;
return {
success: true,
charged: charged,
remaining: remaining,
reference: data.data.reference,
isPartial: charged < requested,
};
}
return {
success: false,
message: data.data.gateway_response,
};
}
// Usage
var result = await chargeWithPartialDebit(
'AUTH_xxxxx',
'customer@example.com',
1000000, // Request 10,000 NGN
500000 // Accept as little as 5,000 NGN
);
if (result.success) {
if (result.isPartial) {
console.log('Partial charge: ' + result.charged + ' kobo');
console.log('Remaining: ' + result.remaining + ' kobo');
// Schedule collection of the remaining amount
} else {
console.log('Full charge successful');
}
}
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
The at_least parameter is the minimum amount you will accept, in the smallest currency unit (kobo for NGN). Here is how Paystack uses it:
amount or more: charges the full amountat_least and amount: charges whatever the card hasat_least: the charge fails entirely (insufficient funds)Choosing the right at_least value depends on your business logic:
// For subscriptions: accept at least 50% of the plan cost
var planCost = 1000000; // 10,000 NGN
var atLeast = Math.round(planCost * 0.5); // 500000 (5,000 NGN)
// For wallet top-ups: accept any amount above minimum
var requestedTopUp = 500000; // 5,000 NGN
var atLeast = 10000; // 100 NGN minimum
// For installment payments: accept the scheduled installment or more
var installmentAmount = 250000; // 2,500 NGN
var atLeast = installmentAmount; // Full installment or nothing
If you set at_least equal to amount, partial debit is effectively disabled and the charge behaves like a normal all-or-nothing charge.
When a partial debit captures less than the full amount, you need a system to track and collect the remaining balance. Here is a pattern:
// After a partial charge
async function handlePartialPayment(orderId, charged, remaining, reference) {
// Update the order with what was paid
await db.orders.update(orderId, {
amount_paid: charged,
amount_remaining: remaining,
payment_status: remaining > 0 ? 'partial' : 'paid',
last_payment_reference: reference,
});
if (remaining > 0) {
// Schedule a retry for the remaining amount
await db.scheduledCharges.create({
order_id: orderId,
amount: remaining,
retry_at: new Date(Date.now() + 24 * 60 * 60 * 1000), // Try again in 24h
attempts: 0,
max_attempts: 3,
});
// Notify the customer
await sendEmail(order.email, 'partial_payment', {
charged: fromMinorUnit(charged),
remaining: fromMinorUnit(remaining),
next_attempt: 'tomorrow',
});
}
}
The retry logic depends on your business. Some options:
Subscription billing is the most common use case for partial debit. When a subscription renewal charge fails due to insufficient funds, you have three options:
Option 3 maximizes revenue and customer retention. The customer keeps their subscription, you collect partial revenue now and the rest later, and the customer experience is much better than an abrupt cancellation.
// Subscription renewal with partial debit
async function renewSubscription(subscription) {
var result = await chargeWithPartialDebit(
subscription.auth_code,
subscription.email,
subscription.plan_amount, // Full plan cost
Math.round(subscription.plan_amount * 0.25) // Accept at least 25%
);
if (result.success) {
// Extend the subscription regardless of partial/full
await db.subscriptions.extend(subscription.id, 30); // 30 days
if (result.isPartial) {
// Track the shortfall
await db.subscriptions.update(subscription.id, {
balance_due: result.remaining,
});
}
} else {
// Total failure - enter grace period
await db.subscriptions.update(subscription.id, {
status: 'grace_period',
grace_expires_at: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
});
}
}
The card returns exactly at_least
If the card has exactly the at_least amount, the charge succeeds for that amount. This is fine but easy to miss in testing because it requires a card with a specific balance.
Partial debit and refunds
You can refund a partial debit transaction like any other. The refund amount can be up to the charged amount, not the originally requested amount. If you charged 7,000 NGN out of a requested 10,000 NGN, you can refund up to 7,000 NGN.
Multiple partial debits to fill a full amount
You can run multiple partial debit charges against the same card to eventually collect the full amount. Each charge is a separate transaction. Track the cumulative amount collected and stop when the total reaches your target.
Partial debit is not available for all card types
Some card issuers do not support partial debit. In those cases, the charge behaves as all-or-nothing regardless of the at_least parameter. The charge either succeeds for the full amount or fails. Your code should handle both scenarios.
Communicating partial charges to customers
Customers can be confused by a charge that is less than what they expected. Always send a notification explaining what was charged, why it was partial, and what happens next for the remaining balance.
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