Skip to content
arminrad edited this page Mar 16, 2026 · 2 revisions

Stripe Integration

Payment processing and subscription management with Stripe


Overview

Complete Stripe integration for:

  • One-time credit purchases
  • Subscription plans
  • Webhook event handling
  • Automatic credit addition
  • Referral bonus processing

Credit Model: 1 credit = $0.01 ($10 purchase = 1000 credits)


Quick Start

Environment Variables

# Stripe Keys
STRIPE_SECRET_KEY=sk_test_xxx
STRIPE_PUBLISHABLE_KEY=pk_test_xxx
STRIPE_WEBHOOK_SECRET=whsec_xxx

# Frontend URL
FRONTEND_URL=http://localhost:3000

Create Checkout Session

curl -X POST http://localhost:8000/api/stripe/checkout-session \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 1000,
    "currency": "usd",
    "description": "1000 credits purchase"
  }'

Response:

{
  "session_id": "cs_test_...",
  "url": "https://checkout.stripe.com/c/pay/...",
  "payment_id": 1,
  "status": "pending",
  "amount": 1000,
  "expires_at": "2024-12-16T10:30:00Z"
}

API Endpoints

Credit Packages

GET /api/stripe/credit-packages

Returns available credit bundles (public, no auth).

Checkout Session

POST /api/stripe/checkout-session

Request:

{
  "amount": 1000,
  "currency": "usd",
  "description": "Optional description"
}

Response: Session ID and checkout URL

Get Session

GET /api/stripe/checkout-session/{session_id}

Fetch session details.

Payment Intent

POST /api/stripe/payment-intent
GET  /api/stripe/payment-intent/{id}

Create or retrieve PaymentIntent (alternative to checkout).

Payment History

GET /api/stripe/payments          # List all
GET /api/stripe/payments/{id}     # Get specific

Webhook

POST /api/stripe/webhook

Stripe calls this endpoint - must verify signature.

Refund (Admin)

POST /api/stripe/refund

Requires admin auth.


Webhook Configuration

Production

  1. Go to Stripe Dashboard → Developers → Webhooks
  2. Add endpoint: https://your-domain.com/api/stripe/webhook
  3. Select events:
    • checkout.session.completed
    • checkout.session.expired
    • payment_intent.succeeded
    • payment_intent.payment_failed
    • payment_intent.canceled
    • charge.refunded
  4. Copy signing secret to .env as STRIPE_WEBHOOK_SECRET

Local Development

Install Stripe CLI:

# macOS
brew install stripe/stripe-cli/stripe

# Windows
scoop install stripe

# Linux
See https://stripe.com/docs/stripe-cli

Forward webhooks:

# Login
stripe login

# Forward to local server
stripe listen --forward-to localhost:8000/api/stripe/webhook

Copy the whsec_... secret to .env.


Credit Processing

Automatic Flow

1. User creates checkout session
2. User completes payment at Stripe
3. Stripe sends webhook to /api/stripe/webhook
4. Backend verifies signature
5. Backend adds credits to user account
6. Backend creates payment record
7. If referral code used & first purchase:
   → Add bonus to both users
8. Mark payment as completed

Credit Calculation

credits = amount_in_cents / 100  # $10 = 1000 cents = 10.00 credits

Referral Bonus

If user has referred_by_code and has_made_first_purchase = false:

  • User gets: payment amount + $10 bonus
  • Referrer gets: $10 bonus
  • Only applied once (first purchase >= $10)

Database Schema

Payments Table

CREATE TABLE payments (
  id INTEGER PRIMARY KEY,
  user_id INTEGER,
  amount NUMERIC,           -- Amount in dollars
  currency TEXT,            -- 'usd'
  status TEXT,              -- 'pending', 'completed', 'failed', 'refunded'
  stripe_session_id TEXT,   -- Checkout session ID
  stripe_payment_intent_id TEXT,
  description TEXT,
  created_at TIMESTAMP,
  completed_at TIMESTAMP
);

Testing

Test Credit Cards

Purpose Number
Success 4242 4242 4242 4242
3D Secure 4000 0025 0000 3155
Declined 4000 0000 0000 9995
Insufficient funds 4000 0000 0000 9995
Expired 4000 0000 0000 0069

Use any future expiry, any CVC, any ZIP.

Trigger Events

# Simulate successful payment
stripe trigger checkout.session.completed

# Simulate failed payment
stripe trigger payment_intent.payment_failed

# Simulate expired session
stripe trigger checkout.session.expired

# Simulate refund
stripe trigger charge.refunded

End-to-End Test

# 1. Create session
SESSION=$(curl -X POST http://localhost:8000/api/stripe/checkout-session \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"amount":1000,"currency":"usd"}' | jq -r '.url')

# 2. Open checkout URL
echo "Complete payment at: $SESSION"

# 3. Verify credits added
curl http://localhost:8000/user/balance \
  -H "Authorization: Bearer $API_KEY"

# 4. Check payment history
curl http://localhost:8000/api/stripe/payments \
  -H "Authorization: Bearer $API_KEY"

Webhook Event Handling

Events Processed

Location: src/routes/payments.py

checkout.session.completed:

  • Extract user, amount, payment intent
  • Add credits to user
  • Apply referral bonus (if applicable)
  • Create payment record
  • Mark as completed

checkout.session.expired:

  • Mark payment as expired
  • Update status

payment_intent.succeeded:

  • Confirm payment completed
  • Update records

payment_intent.payment_failed:

  • Mark payment as failed
  • Log error

payment_intent.canceled:

  • Mark as canceled
  • Clean up session

charge.refunded:

  • Deduct credits from user
  • Mark payment as refunded
  • Create refund record

Signature Verification

import stripe

def verify_webhook(payload: bytes, signature: str):
    try:
        event = stripe.Webhook.construct_event(
            payload, signature, webhook_secret
        )
        return event
    except ValueError:
        # Invalid payload
        raise
    except stripe.error.SignatureVerificationError:
        # Invalid signature
        raise

Never bypass signature verification in production!


Security Best Practices

  1. Always verify webhook signatures
  2. Use test keys in development
  3. Never expose secret keys in frontend
  4. Use HTTPS in production
  5. Validate amounts server-side
  6. Check payment status before granting access
  7. Implement idempotency for webhooks
  8. Log all payment events

Troubleshooting

Webhook not receiving events

Cause: Incorrect endpoint URL or firewall blocking

Solution:

  1. Check webhook endpoint in Stripe Dashboard
  2. Ensure URL is publicly accessible
  3. Verify webhook secret matches .env
  4. Check logs for incoming requests

Signature verification failed

Cause: Wrong webhook secret or modified payload

Solution:

  1. Copy fresh webhook secret from Stripe
  2. Update STRIPE_WEBHOOK_SECRET in .env
  3. Restart server
  4. Don't modify webhook payload before verification

Credits not added after payment

Cause: Webhook not processed or failed

Solution:

  1. Check Stripe CLI output for errors
  2. Review webhook endpoint logs
  3. Verify user_id in session metadata
  4. Check database for payment record
  5. Look for errors in application logs

Test payment fails locally

Cause: Webhook forwarding not running

Solution:

# Start webhook forwarding
stripe listen --forward-to localhost:8000/api/stripe/webhook

Production Checklist

  • Switch to live API keys
  • Configure production webhook endpoint
  • Enable webhook event filtering
  • Set up webhook monitoring/alerts
  • Test end-to-end payment flow
  • Verify credit calculations
  • Test refund process
  • Configure error notifications
  • Set up payment analytics
  • Document customer support procedures

Related Documentation


Last Updated: December 2024 Status: Production Ready

For questions: See Troubleshooting or Stripe Docs


Related

Clone this wiki locally