Paystack Partial Debits Explained
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.
What Partial Debit Solves
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:
- Some payment is better than no payment: A subscription platform would rather collect 7,500 NGN now and 2,500 NGN later than lose the customer entirely.
- Wallet top-ups: The customer wants to top up 10,000 NGN. Their card only has 7,000 NGN. Partial debit captures 7,000 and credits their wallet accordingly.
- Installment plans: You are collecting a scheduled payment. Getting part of it now and the rest later is better than failing the entire collection.
- Reducing payment friction: "Insufficient funds" is the most common card decline reason. Partial debit turns some of those declines into partial successes.
How to Implement Partial Debit
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');
}
}
The at_least Parameter
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:
- If the card has the full
amountor more: charges the fullamount - If the card has between
at_leastandamount: charges whatever the card has - If the card has less than
at_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.
Handling the Remaining Balance
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:
- Automatic retry: Try to charge the remaining amount after 24-48 hours, hoping the customer has added funds to their account
- Customer-initiated: Send the customer a payment link for the remaining amount and let them pay when ready
- Escalating reminders: Send reminders with increasing urgency, then restrict service access if the balance is not cleared within a grace period
Partial Debit in Subscription Billing
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:
- Fail and cancel: The charge fails, the subscription is cancelled, the customer loses access. Simple but aggressive.
- Retry later: The charge fails, you retry in 24 hours, and again in 48 hours. This gives the customer time to add funds but does not collect anything in the meantime.
- Partial debit: Capture whatever the card has (above your minimum), continue providing service, and collect the remaining amount at the next billing cycle or via a follow-up charge.
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),
});
}
}
Edge Cases and Gotchas
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.
Key Takeaways
- ✓Partial debit lets you capture a portion of the requested amount when the card does not have sufficient funds for the full charge.
- ✓Use the at_least parameter to set the minimum amount you will accept. If the card balance is below at_least, the charge fails entirely.
- ✓The response tells you the actual amount charged. Track the difference between the requested and charged amount as a remaining balance.
- ✓Partial debit only works with charge_authorization (saved cards). It does not work with Initialize Transaction or first-time card payments.
- ✓Common use cases: wallet top-ups, installment plans, subscription billing where partial payment is better than no payment.
- ✓Always communicate clearly to the customer what was charged and what remains. Unexpected partial charges confuse people.
Frequently Asked Questions
- Does partial debit work with first-time card payments?
- No. Partial debit only works with charge_authorization, which requires a saved authorization code from a previous successful payment. It does not work with Initialize Transaction or the first-time Charge API flow.
- How do I know if a charge was partial or full?
- Compare the amount in the response (data.amount) with the amount you requested. If data.amount is less than what you sent in the request body, the charge was partial. The response does not have an explicit "partial" flag, so you must compare the values yourself.
- Can I use partial debit with GHS, ZAR, or KES currencies?
- Partial debit availability may vary by currency and card type. It is most commonly used with NGN transactions. Check with Paystack support for availability in other currencies.
- What is the minimum I can set for at_least?
- The minimum at_least value follows the same minimum transaction amount rules as regular charges. For NGN, this is typically around 100 kobo (1 Naira). Setting at_least to 0 or a negative number will result in an API error.
- Does the customer get notified about a partial charge?
- Paystack sends the customer a receipt for the amount that was actually charged. It does not notify them about the shortfall. You should send your own notification explaining that a partial payment was collected and what the remaining balance is.
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