Technical guide to building recurring payment systems that handle retries, card updates, mandate management, and edge cases.
Collecting a one-time payment is conceptually simple: customer pays, you deliver. Recurring payments introduce a fundamentally different dynamic. You are charging someone who is not actively present in your checkout flow. Cards expire. Bank accounts close. Customers forget they subscribed. Your system must handle all of this while maintaining both technical reliability and customer trust.
Store a tokenized card reference and charge it periodically. This is the most common approach for SaaS and subscription services.
Advantages: Works globally, customers are familiar with the flow, immediate confirmation.
Challenges: Cards expire (typically every 3-4 years), card details change (lost/stolen replacement), and issuers may decline recurring charges they consider suspicious.
Card updater services: Visa Account Updater (VAU) and Mastercard Automatic Billing Updater (ABU) automatically update stored card credentials when a customer receives a new card. Most PSPs support this. Enable it.
Pull funds directly from the customer's bank account using a signed mandate. Dominant in Europe for subscription billing.
Advantages: No card expiration problem, lower processing fees than cards, high success rates for established mandates.
Challenges: Longer settlement time (D+5 for first collection on SEPA Core DD), chargeback window of 8 weeks (no-questions-asked) or 13 months (unauthorized), and mandate management overhead.
1. Customer provides IBAN and signs mandate (digital or paper)
2. You store mandate details and generate a Unique Mandate Reference (UMR)
3. You submit the mandate to your bank/PSP
4. For each collection: submit a pre-notification and the direct debit instruction
5. Bank processes the debit on the agreed date
6. Funds settle in your account (D+5 for first, D+2 for recurring on Core scheme)
Pre-notification requirement: You must inform the customer before each debit, typically 14 days in advance (reducible to 1 day with customer agreement). Your system needs to send these notifications on schedule.
Your recurring charge system needs a reliable scheduler. Do not rely on cron jobs that fire at midnight and attempt to charge everyone at once.
1. Store the next charge date on each subscription
2. A scheduler job runs every hour and picks up subscriptions due within the next hour
3. Each subscription gets its own queued charge job
4. Jobs execute independently with their own retry logic
5. After successful/failed charge, calculate and store the next charge date
Why hourly batches instead of one daily run? Distributing charges across the day reduces peak load on your PSP's API and your own infrastructure. It also means a temporary PSP outage does not affect all your charges.
Your charge scheduler must be idempotent. If the scheduler runs twice for the same period (due to a deployment, a crash, or a bug), it must not create duplicate charges:
public function chargeSubscription(Subscription $subscription): void
{
// Check if this period has already been charged
$existingCharge = Charge::where('subscription_id', $subscription->id)
->where('period_start', $subscription->current_period_start)
->where('period_end', $subscription->current_period_end)
->first();
if ($existingCharge) {
return; // Already charged for this period
}
// Create charge record first, then attempt payment
$charge = Charge::create([
'subscription_id' => $subscription->id,
'period_start' => $subscription->current_period_start,
'period_end' => $subscription->current_period_end,
'amount' => $subscription->plan->price,
'status' => 'pending',
]);
$this->attemptPayment($charge);
}
Payment failures in recurring billing are normal. Cards get replaced, bank balances fluctuate, issuers have temporary outages. A good retry strategy recovers 60-80% of initial failures.
Not all retry schedules are equal. Research and industry practice suggest:
Different decline codes require different responses:
Retryable:
Not retryable:
Map your PSP's decline codes to these categories and route accordingly.
When a recurring charge fails, you face a product decision: does the customer lose access immediately?
Common approach:
The exact timing depends on your business. Higher-value subscriptions warrant longer grace periods and more personal outreach.
Under PSD2, the first payment must be authenticated with SCA. Subsequent recurring charges of the same amount to the same payee are exempt from SCA.
Variable amounts complicate this. If your subscription price changes (usage-based billing, plan upgrades), you may need to re-authenticate. The practical threshold varies by issuer.
For SEPA Direct Debit, the signed mandate serves as the authentication. No additional SCA is required for individual collections.
Track these metrics for your recurring payment system:
Set alerts for:
Recurring payments are a continuous relationship between your system and your customers' banks. Build for resilience: distribute your charges, retry intelligently, handle every decline code, and monitor relentlessly. The difference between 95% and 98% collection rate is significant revenue.
Whether you're modernizing your infrastructure, navigating compliance, or building new software - we can help.
Book a 30-min Call