Bonaventure OgetoBy Bonaventure Ogeto|

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 amount or more: charges the full amount
  • If the card has between at_least and amount: 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:

  1. Fail and cancel: The charge fails, the subscription is cancelled, the customer loses access. Simple but aggressive.
  2. 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.
  3. 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.

Stay Up to Date

Partial debit behavior can change as Paystack updates their billing features. We keep these guides current.

Sign up for the McTaba newsletter to stay informed about Paystack API changes.

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