Stripe Testing: Simulating Payments with Test Mode and Cards
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
- What is the difference between test mode and live mode in Stripe?
- Which test card number triggers 3D Secure authentication?
- How do you simulate webhook events locally?
- Which test card triggers a dispute/chargeback?
Answers:
- Test mode uses
sk_test_keys and test cards — no real money moves. Live mode usessk_live_keys and processes real transactions. Test mode is free and unlimited. 4000002500003155triggers 3D Secure authentication. The PaymentIntent returnsstatus: requires_actionwithnext_action.redirect_to_url.- Use the Stripe CLI: run
stripe listen --forward-to localhost:8000/webhook/stripeand trigger events withstripe trigger payment_intent.succeeded. 4000000000000259triggers 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
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