Skip to content

Stripe Testing: Simulating Payments with Test Mode and Cards

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Stripe Testing: Simulating Payments with Test Mode and Cards. We cover key concepts, practical examples, and best practices to help you master this topic.

Stripe test mode provides test API keys, test card numbers, and Webhook forwarding to simulate payment scenarios including success, SCA authentication, declines, and disputes without moving real money.

What You'll Learn

How to use Stripe test mode effectively, simulate payment success and failure with test cards, test 3D Secure authentication, validate webhook handling using the Stripe CLI, and test edge cases.

Why It Matters

Thorough testing prevents lost revenue from failed payments, ensures SCA Compliance, and validates webhook logic. DodaTech uses test cards to verify all payment flows before each deployment to production.

Real-World Use

Before launching a new pricing tier, the DodaTech team runs 15 test scenarios: successful payment, 3D Secure, insufficient funds, expired card, dispute, and refund. All pass in test mode before go-live.

flowchart LR
    A["Test Card\nNumber"] --> B{"Card Type"}
    B -->|424242...| C["Success"]
    B -->|400000...| D["3D Secure\nRequired"]
    B -->|400000...| E["Insufficient\nFunds"]
    B -->|400000...| F["Expired\nCard"]
    C --> G["PaymentIntent\nsucceeded"]
    D --> H["requires_action"]
    E --> I["PaymentIntent\nfailed"]
    F --> J["PaymentIntent\nfailed"]
    style C fill:#bbf7d0,stroke:#16a34a
    style D fill:#fef3c7,stroke:#d97706
    style E fill:#fecaca,stroke:#dc2626
    style F fill:#fecaca,stroke:#dc2626

Test Mode Setup

import stripe
import os

# Switch between test and live mode
def get_stripe_client(use_test=True):
    if use_test:
        stripe.api_key = "sk_test_51Mq..."  # Test secret key
    else:
        stripe.api_key = os.environ["STRIPE_SECRET_KEY"]  # Live key
    return stripe

# Verify test mode
client = get_stripe_client(True)
account = stripe.Account.retrieve()
print(f"Account: {account.id}")
print(f"Test mode: {not account.charges_enabled}")  # Charges disabled in test
# Expected output:
# Account: acct_TestAccount123
# Test mode: True

# List test products
products = stripe.Product.list(limit=3)
for p in products:
    print(f"  {p.id}: {p.name} ({'active' if p.active else 'archived'})")

Test Card Reference

# Common test cards for different scenarios
test_cards = {
    "success_visa": "4242424242424242",
    "success_visa_debit": "4000056655665556",
    "sca_required": "4000002500003155",
    "decline_insufficient_funds": "4000000000009995",
    "decline_expired_card": "4000000000000069",
    "decline_stolen_card": "4000000000004954",
    "decline_processing_error": "4000000000000119",
    "dispute_chargeback": "4000000000000259"
}

def simulate_payment(card_number, amount=2999):
    """Create and confirm a PaymentIntent with a test card."""
    stripe.api_key = "sk_test_..."
    intent = stripe.PaymentIntent.create(
        amount=amount,
        currency="usd",
        payment_method_data={
            "type": "card",
            "card": {"number": card_number, "exp_month": 12, "exp_year": 2030, "cvc": "123"}
        },
        confirm=True
    )
    print(f"Card: {card_number[:4]}...{card_number[-4:]}")
    print(f"Status: {intent.status}")
    if intent.status == "requires_action":
        print("3D Secure authentication required")
    elif intent.status == "succeeded":
        print(f"Payment succeeded: ${intent.amount/100:.2f}")
    elif intent.status == "requires_payment_method":
        error = intent.last_payment_error
        print(f"Declined: {error.decline_code} ({error.message})")
    return intent

# Test success
result = simulate_payment("4242424242424242")
# Expected output:
# Card: 4242...4242
# Status: succeeded
# Payment succeeded: $29.99

# Test insufficient funds
result = simulate_payment("4000000000009995")
# Expected output:
# Card: 4000...9995
# Status: requires_payment_method
# Declined: insufficient_funds (Your card has insufficient funds.)

Webhook Testing with Stripe CLI

# Start webhook forwarding (run in terminal)
# stripe listen --forward-to localhost:8000/webhook/stripe

# The CLI provides a signing secret:
# > Ready! Your webhook signing secret is whsec_...

# Verify webhook signature (Python)
import hashlib
import hmac

def verify_webhook_signature(payload, sig_header, secret):
    try:
        event = stripe.Webhook.construct_event(
            payload, sig_header, secret
        )
        print(f"Verified event: {event.type}")
        return event
    except ValueError:
        print("Invalid payload")
    except stripe.error.SignatureVerificationError:
        print("Invalid signature")
    return None

def handle_test_webhook():
    """Simulate receiving a webhook event."""
    # In test mode, Stripe sends real webhooks
    # Use stripe trigger to simulate events:
    # stripe trigger payment_intent.succeeded

    print("Listening for events...")
    print("Use: stripe trigger payment_intent.succeeded")
    print("Use: stripe trigger checkout.session.completed")
    print("Use: stripe trigger customer.subscription.updated")

# Expected terminal output when running stripe trigger:
# 2024-06-28 12:00:00  --> payment_intent.succeeded [evt_test_...]

Testing Disputes and Refunds

def create_test_dispute():
    """Use a dispute-trigger card to simulate a chargeback."""
    intent = stripe.PaymentIntent.create(
        amount=5000,
        currency="usd",
        payment_method_data={
            "type": "card",
            "card": {"number": "4000000000000259", "exp_month": 12, "exp_year": 2030, "cvc": "123"}
        },
        confirm=True
    )
    print(f"Dispute payment: {intent.id} ({intent.status})")
    # Stripe automatically creates a dispute for this card
    return intent

def test_refund_flow(payment_intent_id):
    """Test full and partial refunds."""
    # Full refund
    refund = stripe.Refund.create(
        payment_intent=payment_intent_id
    )
    print(f"Full refund: {refund.id} (${refund.amount/100:.2f})")
    print(f"Status: {refund.status}")

    # Partial refund (if full refund above failed)
    try:
        partial = stripe.Refund.create(
            payment_intent=payment_intent_id,
            amount=1000
        )
        print(f"Partial refund: {partial.id} ($10.00)")
    except stripe.error.InvalidRequestError as e:
        print(f"Cannot partial refund after full refund: {e}")

# test_refund_flow("pi_TestPaymentIntentABC")
# Expected output:
# Full refund: re_1FullRefundABC ($50.00)
# Status: succeeded
# Cannot partial refund after full refund: This PaymentIntent has already been refunded.

Common Mistakes

1. Testing with Live Keys Accidentally

Test keys start with sk_test_. Live keys start with sk_live_. A single test with live keys charges real money. Always use environment variables and check the prefix programmatically before processing.

2. Not Testing 3D Secure Flows

The card 4000002500003155 requires SCA. If your code doesn't handle requires_action, the payment stalls. Always test this card to verify your 3D Secure handling.

3. Skipping Webhook Testing

Without testing Webhooks, you won't know if your event handling works until production. Use stripe listen with stripe trigger to simulate every event type before going live.

4. Using Expired Test Cards

Test cards with expiry 01/2020 will be rejected. Always use a future date (12/2030). Stripe's test cards work regardless of the actual expiry as long as it's in the future.

5. Not Testing Edge Case Cards

Only testing the happy path card (4242424242424242) misses failure handling. Test declined cards, SCA cards, dispute cards, and processing error cards to ensure all paths work.

Practice Questions

  1. What is the difference between test mode and live mode in Stripe?
  2. Which test card number triggers 3D Secure authentication?
  3. How do you simulate webhook events locally?
  4. Which test card triggers a dispute/chargeback?

Answers:

  1. Test mode uses sk_test_ keys and test cards — no real money moves. Live mode uses sk_live_ keys and processes real transactions. Test mode is free and unlimited.
  2. 4000002500003155 triggers 3D Secure authentication. The PaymentIntent returns status: requires_action with next_action.redirect_to_url.
  3. Use the Stripe CLI: run stripe listen --forward-to localhost:8000/webhook/stripe and trigger events with stripe trigger payment_intent.succeeded.
  4. 4000000000000259 triggers an automatic dispute. The PaymentIntent succeeds, then Stripe creates a dispute object shortly after.

Challenge: Create a comprehensive test suite that runs all 10 test card scenarios (success, 3DS, insufficient funds, expired card, stolen card, processing error, dispute, CVC check fail, address check fail, and elevated risk level). Record pass/fail for each and report coverage of your webhook handlers.

FAQ

Do test mode charges appear on my bank statement?

No, test mode charges are entirely simulated. No money moves, no bank statements, no real transactions.

Can I use test cards with Checkout Sessions?

Yes, test cards work with Checkout Sessions in test mode. Create a session, open the URL, and enter a test card number on Stripe's hosted payment page.

How do I reset test mode data?

Test mode data persists. To reset, use the Stripe Dashboard: Settings > Test mode > Clear test data. This removes all test products, customers, and payment history.

Can I test webhooks without the Stripe CLI?

Yes, you can manually send POST requests to your webhook endpoint with test payloads. Use the Dashboard > Developers > Webhooks > Send test webhook to generate sample payloads.

How do I test different currencies in test mode?

Simply pass a different currency code (eur, gbp, jpy) when creating PaymentIntents or Prices. Test mode supports all currencies with simulated conversion rates.

Mini Project

Build a test automation suite: create a script that iterates through all 10 test cards, asserts correct status for each, triggers and receives webhook events via Stripe CLI, simulates a dispute lifecycle, tests refund and partial refund flows, generates a pass/fail report, and validates webhook signature verification.

What's Next

Complete Payment Project — build a full payment system combining everything you learned.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro