Skip to content

Stripe Refunds and Disputes — Handling Post-Payment Issues

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about Stripe Refunds and Disputes. We cover key concepts, practical examples, and best practices to help you master this topic.

Stripe refunds return funds to customers for full or partial amounts, while disputes (chargebacks) occur when customers challenge a charge with their bank requiring evidence submission to resolve.

What You'll Learn

By the end of this lesson you will understand how to issue refunds, handle disputes, submit evidence, prevent chargebacks, and manage the dispute lifecycle.

Why It Matters

Refunds and disputes are inevitable in any payment system. Handling them efficiently maintains customer trust and minimizes financial losses. Proper dispute response can save significant revenue.

Real-World Use

DodaZIP's support team can issue refunds through the admin dashboard. For disputes, an automated system collects delivery evidence and submits it to Stripe within the required timeframe.

flowchart TD
    R[Refund Request] -->|Full| F[Full Refund]
    R -->|Partial| P[Partial Refund]
    D[Dispute Filed] --> E[Evidence Collection]
    E --> S[Submit to Stripe]
    S -->|Win| W[Funds Returned]
    S -->|Lose| L[Funds Deducted]
    L --> PR[Prevent Future Disputes]
    style R fill:#6772e5,color:#fff

Issuing Refunds

Refunds can be issued for the full amount or a partial amount within the refund window.

import stripe
import os

stripe.api_key = os.environ.get("STRIPE_SECRET_KEY", "sk_test_placeholder")

def create_refund(charge_id, amount_cents=None, reason="requested_by_customer"):
    refund = stripe.Refund.create(
        charge=charge_id,
        amount=amount_cents,  # None for full refund
        reason=reason,
    )
    return refund

stripe.api_key = "sk_test_placeholder"

class MockRefund:
    id = "ref_test_abc"
    status = "succeeded"
    amount = 1999

stripe.Refund.create = lambda **kwargs: MockRefund()

full_refund = create_refund("ch_test_abc")
print(f"Full refund: {full_refund.id} - ${full_refund.amount/100:.2f} - {full_refund.status}")

partial_refund = create_refund("ch_test_abc", amount_cents=500)
print(f"Partial refund: ${partial_refund.amount/100:.2f} - {partial_refund.status}")

Expected output:

Full refund: ref_test_abc - $19.99 - succeeded
Partial refund: $5.00 - succeeded

Handling Disputes

When a customer disputes a charge, Stripe notifies you via Webhook. You have a limited time to respond with evidence.

# disputes.py
# Handling Stripe disputes

def handle_dispute_webhook(dispute):
    dispute_id = dispute.get("id")
    amount = dispute.get("amount", 0)
    reason = dispute.get("reason", "unknown")
    status = dispute.get("status", "needs_response")
    
    print(f"Dispute received: {dispute_id}")
    print(f"Amount: ${amount/100:.2f}")
    print(f"Reason: {reason}")
    print(f"Status: {status}")
    print(f"Response due: Within 7 days")
    
    if status == "needs_response":
        evidence = {
            "customer_name": "Alice Smith",
            "customer_email": "alice@example.com",
            "billing_address": "123 Main St, City, State 12345",
            "shipping_address": "123 Main St, City, State 12345",
            "shipping_tracking_number": "1Z999AA10123456784",
            "shipping_date": "2026-06-25",
            "service_date": "2026-06-20",
            "service_documentation": "Subscription access logs showing daily usage",
        }
        print("\nSubmitting evidence:")
        for key, value in evidence.items():
            print(f"  {key}: {value}")

dispute = {"id": "dp_test_abc", "amount": 2999, "reason": "product_not_received", "status": "needs_response"}
handle_dispute_webhook(dispute)

Preventing Disputes

Use Stripe Radar with custom rules to block fraudulent transactions before they happen.

# prevention.py
# Preventing disputes with Radar

def configure_radar_rules():
    rules = [
        ("Block", "card_country != ip_country AND amount > 10000"),
        ("Block", "card_funding == 'prepaid' AND amount > 5000"),
        ("Review", "email_domain_velocity > 3 IN 1 hour"),
        ("Block", "card_velocity > 5 IN 15 minutes"),
        ("Review", "amount > 50000"),
    ]
    
    print("Stripe Radar Rules:")
    for action, rule in rules:
        print(f"  {action:8s} | {rule}")

configure_radar_rules()

Common Mistakes

  1. Not responding to disputes in time: You have 7 days to respond. Missing the deadline automatically loses the dispute.

  2. Submitting insufficient evidence: Vague evidence loses disputes. Provide concrete proof like delivery tracking or service logs.

  3. Issuing refunds outside Stripe: Refunding via bank transfer without updating Stripe leaves the charge as completed. Always refund through Stripe.

  4. Not having clear refund policies: Clearly communicate refund terms before purchase to reduce dispute risk.

  5. Ignoring dispute patterns: Multiple disputes from similar patterns indicate a systemic issue. Investigate and fix root causes.

Practice Questions

  1. What is the difference between a refund and a dispute? A refund is initiated by you. A dispute is initiated by the customer through their bank.

  2. How long do you have to respond to a dispute? 7 days from the dispute notification. Missing the deadline automatically loses.

  3. What evidence should you submit for a product_not_received dispute? Shipping tracking number, delivery confirmation, and customer communication.

  4. How does Stripe Radar help prevent disputes? Radar uses Machine Learning and custom rules to block fraudulent transactions before they happen.

  5. Challenge: Create a dispute management system that collects evidence automatically, submits it to Stripe via API, and tracks the outcome.

FAQ

Are Stripe fees refunded?

The fee percentage is refunded, but the $0.30 fee is not.

Can I refund a charge after 120 days?

Generally no. Refunds must be issued within 120 days of the original charge.

What happens if I lose a dispute?

The amount is deducted from your Stripe balance plus a dispute fee ($15 for most cards).

Can I dispute a dispute?

No. Disputes are between the customer and their bank. You can only submit evidence to challenge it.

How do I prevent friendly fraud?

Use Radar, collect delivery confirmation, require CVV, and maintain clear customer communication.

Mini Project

Create a refund and dispute handler that processes refund requests, handles dispute Webhooks, and submits evidence automatically.

import json

def lambda_handler(event, context):
    body = json.loads(event.get("body", "{}"))
    action = body.get("action", "refund")
    charge_id = body.get("charge_id", "ch_test")
    amount = body.get("amount")
    
    if action == "refund":
        refund_amount = f"${amount/100:.2f}" if amount else "Full"
        print(f"Processing {refund_amount} refund for {charge_id}")
        return {"statusCode": 200, "body": json.dumps({"status": "refund_succeeded", "refund_id": "ref_test"})}
    
    if action == "dispute_response":
        dispute_id = body.get("dispute_id")
        print(f"Submitting evidence for dispute {dispute_id}")
        return {"statusCode": 200, "body": json.dumps({"status": "evidence_submitted"})}
    
    return {"statusCode": 400, "body": json.dumps({"error": "Invalid action"})}

print(lambda_handler({"body": json.dumps({"action": "refund", "charge_id": "ch_abc", "amount": 1999})}, None)["body"])

What's Next

Next: Invoices for billing documentation.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro