How to handle and prevent chargebacks in online payment systems, covering dispute flows, evidence submission, and reduction strategies.
A chargeback occurs when a cardholder disputes a transaction with their bank, and the bank reverses the payment. For the merchant, this means lost revenue, a chargeback fee (typically 15-25 EUR), and potential damage to your processing relationship if rates get too high.
You cannot eliminate chargebacks entirely. But you can reduce them significantly and handle disputes effectively when they arise.
1. Customer contacts their bank to dispute a charge
2. Bank initiates a chargeback, debiting your PSP
3. PSP debits your account and notifies you
4. You have 7-14 days to submit evidence (representment)
5. Bank reviews evidence and decides
6. If you win: funds returned. If you lose: chargeback stands.
7. Either party can escalate to arbitration (expensive, rarely done)
SEPA DD chargebacks work differently:
The 8-week unconditional window makes SEPA DD riskier for one-time transactions but manageable for subscription billing where you have an ongoing customer relationship.
Understanding why chargebacks happen helps you prevent them:
The cardholder claims they did not make or authorize the transaction. This includes genuine fraud (stolen card) and friendly fraud (customer made the purchase but claims they did not).
Prevention:
The cardholder does not recognize the charge on their statement.
Prevention:
Customer claims they paid but never received the product or service.
Prevention:
Customer received the product but it does not match the description.
Prevention:
Customer was charged twice for the same transaction.
Prevention:
When a chargeback notification arrives, your system should automatically gather relevant evidence:
class ChargebackEvidenceCollector
{
public function collect(Chargeback $chargeback): ChargebackEvidence
{
$payment = $chargeback->payment;
$order = $payment->order;
$customer = $order->customer;
return new ChargebackEvidence(
transactionDetails: [
'date' => $payment->created_at,
'amount' => $payment->amount,
'description' => $payment->description,
'ip_address' => $order->ip_address,
'device_fingerprint' => $order->device_fingerprint,
],
customerDetails: [
'email' => $customer->email,
'registration_date' => $customer->created_at,
'previous_purchases' => $customer->orders()->paid()->count(),
],
deliveryEvidence: $this->collectDeliveryEvidence($order),
authenticationEvidence: $this->collect3DSEvidence($payment),
communicationHistory: $this->collectEmails($customer, $order),
usageEvidence: $this->collectUsageLogs($customer, $order),
);
}
}
For each chargeback type, provide targeted evidence:
Fraud claims:
Not received:
Not as described:
class ChargebackHandler
{
public function handle(ChargebackNotification $notification): void
{
// 1. Record the chargeback
$chargeback = Chargeback::create([
'payment_id' => $notification->paymentId,
'reason_code' => $notification->reasonCode,
'amount' => $notification->amount,
'deadline' => $notification->responseDeadline,
'status' => 'received',
]);
// 2. Collect evidence automatically
$evidence = $this->evidenceCollector->collect($chargeback);
$chargeback->update(['evidence' => $evidence->toArray()]);
// 3. Assess whether to fight or accept
if ($this->shouldAccept($chargeback)) {
$chargeback->update(['status' => 'accepted']);
return;
}
// 4. Queue for review and submission
$chargeback->update(['status' => 'pending_review']);
ChargebackReviewNeeded::dispatch($chargeback);
}
private function shouldAccept(Chargeback $chargeback): bool
{
// Auto-accept if: amount is very small, no evidence available,
// or customer has a history of legitimate complaints
return $chargeback->amount < 500 // cents
&& ! $chargeback->hasStrongEvidence();
}
}
Card networks impose chargeback rate thresholds:
Visa: Chargeback ratio above 0.9% triggers the Visa Dispute Monitoring Program (VDMP). Above 1.8% enters the Excessive program. Penalties include fines and potential termination.
Mastercard: Excessive Chargeback Program triggers at 1.5% chargeback ratio. Penalties escalate monthly.
Calculate your ratio: Chargebacks received in a month divided by transactions processed in that month.
Track this ratio daily and set alerts well below the threshold:
// Daily chargeback rate check
$transactions = Payment::where('created_at', '>=', now()->subDays(30))->count();
$chargebacks = Chargeback::where('created_at', '>=', now()->subDays(30))->count();
$rate = $chargebacks / max($transactions, 1);
if ($rate > 0.005) { // Alert at 0.5%, well below Visa's 0.9% threshold
Alert::critical("Chargeback rate at {$rate}%");
}
The most effective chargeback reduction strategies:
Chargebacks are manageable when you build systems that prevent them, collect evidence automatically, and respond within deadlines. Track your chargeback ratio obsessively. The goal is not zero chargebacks (impossible) but a sustainable rate well below network thresholds.
Whether you're modernizing your infrastructure, navigating compliance, or building new software - we can help.
Book a 30-min Call