1. LEARNING OBJECTIVES
By the end of this expansive, 20+ page lesson, you will be able to:
-
Understand the difference between a Chargeback (a forcible funds reversal) and a Retrieval Request (a request for a receipt).
-
Map the lifecycle of a dispute from the customer’s bank (Issuer) to the merchant’s bank (Acquirer) and back.
-
Decode the highly specific Visa/Mastercard Reason Codes and match them to the exact type of required legal evidence.
-
Understand the process of Representment—the merchant’s formal right to challenge and win the dispute.
-
Design an automated Representment Engine that automatically compiles dynamic evidence (IP Address, AVS Match, CVV Match, Shipping Tracking).
-
Analyze the timeline metrics: The Due Date (usually 20-30 days) and the Pre-Arbitration process.
-
Build a complete, beginner-friendly Python simulation of a Chargeback Representment Engine that ingests a dispute, matches it to a Reason Code, and generates the correct evidence payload to submit to the Acquirer.
-
Write a Python script to calculate a merchant’s Chargeback Ratio and automatically issue a warning when it breaches the 0.9% threshold.
2. THE CREDIT CARD DISPUTE LIFECYCLE
2.1 The Trigger
A cardholder sees an unauthorized charge on their credit card statement. They call their Issuing Bank (the bank that issued the card). The Issuing Bank initiates a Dispute.
The Issuing Bank does not ask the merchant for permission. They trigger a Chargeback, which immediately pulls the transaction amount out of the Merchant’s Account and puts it back on the Cardholder’s card.
2.2 Retrieval Request vs. Chargeback
Before a full chargeback, the Issuer might initially send a Retrieval Request (a request for a copy of a receipt to prove the transaction existed). The merchant has 7 days to respond with a receipt. If the merchant fails to respond, the Issuer immediately escalates it to a full chargeback. This is a critical failure point for many small merchants—they just ignore the email, and they lose the money.
2.3 The Reason Codes (The Legal Justification)
When the Issuer initiates the chargeback, they attach a specific Reason Code, which dictates the legal reasoning for the reversal:
-
Visa Code 10.4: “Merchandise/Services Not Received” – the customer claims they paid for an item that never arrived.
-
Visa Code 10.5: “Cardholder Disputes Transaction” – the customer claims they did not authorize the payment (fraud).
-
Visa Code 12.5: “Incorrect Transaction Amount” – the merchant charged a different amount than advertised.
-
Visa Code 30.0: “Recurring Billing Canceled” – the customer canceled a subscription but the merchant continued to charge them.
3. REPRESENTMENT: THE MERCHANT’S COUNTER-ATTACK
3.1 The Concept of Legal Rebuttal
The merchant is not defenseless. The process of fighting back is called Representment. The merchant must compile a Compelling Evidence Packet that directly refutes the specific Reason Code.
Crucial FinTech Rule: If the merchant uses the wrong evidence for the wrong reason code, the Representment is automatically rejected. You must perfectly match the evidence to the code.
3.2 The Evidence Mapping (The Automation Logic)
To win a representment, a FinTech platform’s backend must automate this exact evidence-matching:
| Reason Code | Required Evidence for Win |
|---|---|
| 10.4 (Not Received) | Valid tracking number with proof of delivery to the exact shipping address. If it says “Delivered to front porch”, you win. |
| 10.5 (Fraud/Unauthorized) | AVS (Address Verification Service) Match + CVV Match + IP Address matching the billing address. If the user’s credit card is entered from a home IP, it proves they had the physical card. |
| 12.5 (Incorrect Amount) | Screenshot of the Checkout Page showing the user explicitly agreed to the price. |
| 30.0 (Recurring Billing) | The original sign-up timestamp, and proof that the user clicked the “Agree to Terms” checkbox for recurring billing. |
3.3 The Timeline Clock (The Killer of Disputes)
The Issuing Bank gives the Merchant’s Acquirer a strict deadline to respond.
-
Response Window: Typically 20 to 30 days from the date the chargeback is filed.
-
The Fatal Error: If the merchant’s automated system fails to submit the Representment packet via the Acquirer’s portal before the deadline passes by even 1 millisecond, the Issuer automatically wins. The money is permanently lost.
4. PRE-ARBITRATION AND THE MATCH LIST
4.1 Pre-Arbitration
If the merchant submits Representment and the Issuer rejects it, the merchant can escalate further to Pre-Arbitration. At this stage, Visa/Mastercard steps in to act as a referee. They review the evidence manually. The merchant must pay a fee (typically $500) just to enter this stage. If Visa/Mastercard rules in the merchant’s favor, the money is reversed back to the merchant, and the Issuer is fined for filing a frivolous chargeback.
4.2 The MATCH List Warning
As we learned in Lesson 4, the Merchant’s Chargeback Ratio is the most critical metric.
In Lesson 7, we will implement a script that calculates this daily:
Chargeback_Ratio=ChargebacksTotal_Transactions×100
If the ratio breaches 0.9%, the FinTech platform must automatically send an emergency email to the merchant’s CEO warning them. If it breaches 1.0%, the platform must threaten to shut down their processing rights to protect the platform’s own Acquirer license.
5. BEGINNER HANDS-ON LAB PART 1: THE AUTOMATED REPRESENTMENT ENGINE
We will build a Python simulation of a Representment Engine. We will create a mock database of chargebacks with their Visa Reason Codes, and write a function that automatically fetches the correct evidence and compiles an evidence packet.
import pandas as pd from datetime import datetime, timedelta # --- STEP 1: MOCK DATABASE OF INCOMING CHARGEBACKS --- chargeback_data = [ {"chargeback_id": "CB_001", "merchant": "Amazon", "txn_id": "TXN_100", "reason_code": "10.4", "customer_claim": "Item never arrived"}, {"chargeback_id": "CB_002", "merchant": "Netflix", "txn_id": "TXN_101", "reason_code": "30.0", "customer_claim": "Canceled subscription"}, {"chargeback_id": "CB_003", "merchant": "Shopify Store", "txn_id": "TXN_102", "reason_code": "10.5", "customer_claim": "Fraud"} ] df_cbs = pd.DataFrame(chargeback_data) # --- STEP 2: MOCK DATABASE OF TRANSACTION EVIDENCE --- # This data would be stored in the platform's transaction logs. evidence_db = { "TXN_100": {"tracking_number": "1Z999AA10123456784", "delivery_status": "Delivered", "date": "2024-01-10", "amount": 50.00}, "TXN_101": {"subscription_start": "2023-01-01", "terms_agreed": True, "subscription_id": "SUB_123", "amount": 15.00}, "TXN_102": {"avs_match": "Y", "cvv_match": "M", "ip_address": "192.168.1.5", "billing_address": "123 Main St, NY", "amount": 250.00} } # --- STEP 3: THE REPRESENTMENT EVIDENCE COMPILER --- def compile_representment(chargeback_id, reason_code, txn_id): """ Looks up the matching evidence based on the Visa Reason Code. """ print(f"\n[REPRESENTMENT ENGINE] Processing Chargeback {chargeback_id} (Code: {reason_code})") # 1. Fetch the raw evidence from the transaction database if txn_id not in evidence_db: return {"status": "FAILED", "reason": "Transaction evidence not found in database."} evidence = evidence_db[txn_id] # 2. Match the evidence to the specific Visa Reason Code representment_packet = { "chargeback_id": chargeback_id, "txn_id": txn_id, "submission_date": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "evidence_items": [] } if reason_code == "10.4": # Item Not Received if "tracking_number" in evidence and evidence["delivery_status"] == "Delivered": representment_packet["evidence_items"].append(f"Tracking Number: {evidence['tracking_number']}") representment_packet["evidence_items"].append(f"Delivery Status: {evidence['delivery_status']}") representment_packet["status"] = "READY_FOR_SUBMISSION" else: representment_packet["status"] = "UNSUCCESSFUL - Lack of tracking proof" elif reason_code == "30.0": # Recurring Billing if evidence["terms_agreed"] == True: representment_packet["evidence_items"].append(f"Original Subscription Date: {evidence['subscription_start']}") representment_packet["evidence_items"].append("User checked 'Agree to Terms' box at signup.") representment_packet["status"] = "READY_FOR_SUBMISSION" else: representment_packet["status"] = "UNSUCCESSFUL - No signup record" elif reason_code == "10.5": # Fraud / Unauthorized representment_packet["evidence_items"].append(f"AVS Match: {evidence['avs_match']}") representment_packet["evidence_items"].append(f"CVV Match: {evidence['cvv_match']}") representment_packet["evidence_items"].append(f"Transaction IP Address: {evidence['ip_address']}") if evidence["avs_match"] == "Y" and evidence["cvv_match"] == "M": representment_packet["status"] = "READY_FOR_SUBMISSION" else: representment_packet["status"] = "WEAK_EVIDENCE - Low chance of winning" else: representment_packet["status"] = "UNKNOWN_REASON_CODE" return representment_packet # --- STEP 4: RUN THE SIMULATION --- print("--- INITIATING REPRESENTMENT AUTOMATION ---") for _, row in df_cbs.iterrows(): cb_id = row['chargeback_id'] reason = row['reason_code'] txn = row['txn_id'] result = compile_representment(cb_id, reason, txn) if result["status"] == "READY_FOR_SUBMISSION": print(f"✅ {cb_id}: Representment Packet Compiled Successfully!") print(f" Evidence: {result['evidence_items']}") print(" -> Submitting via API to Acquiring Bank... DONE.") elif result["status"] == "UNSUCCESSFUL - Lack of tracking proof": print(f"❌ {cb_id}: Dispute lost. No tracking data available to prove delivery.") else: print(f"⚠️ {cb_id}: Representment may fail due to weak evidence.")
Interpretation of the Lab:
When you run this code, the engine automatically maps the Visa Reason Code to the correct evidence type. For TXN_100 (Reason 10.4), it successfully compiles the tracking number. For TXN_102 (Reason 10.5), it compiles the AVS and CVV match.
In a real production environment, a webhook would be sent to the Acquiring Bank’s API containing a PDF with this exact data. The 30-day deadline is automated—the system ensures the packet is submitted within 24 hours of receiving the chargeback, eliminating the risk of a missed deadline.
6. BEGINNER HANDS-ON LAB PART 2: CHARGEBACK RATIO MONITORING
This script automatically calculates a merchant’s daily chargeback ratio and issues an alert if they approach the deadly 1% threshold.
import pandas as pd from datetime import datetime, timedelta # --- STEP 1: MOCK TRANSACTION AND CHARGEBACK DATA --- # Let's simulate 1,000 total transactions, with 9 chargebacks. total_txns = 1000 chargeback_count = 9 # --- STEP 2: THE CHARGEBACK RATIO CALCULATOR --- def calculate_chargeback_ratio(total_txns, cb_count): ratio = (cb_count / total_txns) * 100 return round(ratio, 2) current_ratio = calculate_chargeback_ratio(total_txns, chargeback_count) print("--- CHARGEBACK RATIO MONITORING DASHBOARD ---") print(f"Total Transactions Today: {total_txns}") print(f"Total Chargebacks Today: {chargeback_count}") print(f"Current Chargeback Ratio: {current_ratio}%") # --- STEP 3: AUTOMATED ALERTING LOGIC --- # Visa/Mastercard strict threshold is 1.0%. # Most platforms set an internal warning at 0.9% to give merchants a buffer. if current_ratio >= 1.0: print("\n🚨 CRITICAL ALERT! MATCH LIST IMMINENT!") print("ACTION: Merchant processing privileges are immediately suspended.") print("ACTION: Dispute resolution team assigned to handle the 1% breach.") elif current_ratio >= 0.9: print("\n⚠️ WARNING! Merchant approaching the MATCH list threshold!") print("ACTION: Automated email sent to Merchant CEO.") print("ACTION: Payout schedule frozen until chargeback ratio drops below 0.9%.") else: print("\n✅ Merchant is in the 'Safe Zone'. Chargeback ratio is acceptable.")
Interpretation of the Lab:
If this merchant has 9 chargebacks out of 1,000, they are exactly at 0.9%. The platform’s risk engine detects this, freezes their payouts, and alerts the CEO. If they get one more chargeback (reaching 1.0%), the platform will legally be required to terminate their contract to protect the platform’s own Visa/Mastercard license. This is the exact math that runs on a massive scale inside Stripe’s and Adyen’s risk departments.
7. SUMMARY FOR THE FINANCE PRACTITIONER
Chargeback Management is a critical engineering discipline, not just a customer service role.
-
Automation is the only defense. The 30-day deadline is non-negotiable. You must build a Representment Engine that can automatically pull tracking numbers, AVS results, and IP addresses the moment a chargeback is filed, and submit the evidence to the Acquirer’s API instantly.
-
Data mapping is key. A Visa Code 10.4 dispute requires totally different evidence than a Code 10.5. Your SQL database must be structured to store delivery status, IP geo-location, and subscription sign-up dates, so the Representment Engine can query them rapidly.
-
The 1% rule is absolute. The Chargeback Ratio is the first thing Visa/Mastercard looks at. You must build a daily dashboard to monitor this. If a merchant hits 1%, they are blacklisted from accepting credit cards globally—this can destroy a business.