Skip to main content

Source: ocean/docs/billing-testing.md | ✏️ Edit on GitHub

Billing Testing Guide

This guide explains how to test the billing functionality using real Stripe test API keys and the integrated Supabase/GraphQL stack.

Overview

Our billing tests use real Stripe test API keys instead of mocks to ensure we're testing actual integration behavior. This approach helps catch real issues that mocks might miss, such as:

  • API version compatibility
  • Webhook timing and delivery
  • Payment method validation
  • Subscription state transitions
  • Error handling for real Stripe responses

Test Architecture

┌─────────────────┐     ┌──────────────────┐     ┌─────────────┐
│ Test Suite │────▶│ GraphQL Layer │────▶│ Stripe │
│ (Vitest) │ │ (Supabase) │ │ Test Mode │
└─────────────────┘ └──────────────────┘ └─────────────┘
│ │ │
└───────────────────────┴───────────────────────┘
Real API calls & webhooks

Test Files

  1. billing-test-setup.ts - Test utilities and helpers

    • Creates test users and organizations
    • Manages test Stripe customers
    • Provides cleanup functions
    • Includes test card numbers
  2. billing-graphql.test.ts - GraphQL resolver tests

    • Tests all billing queries and mutations
    • Verifies authorization and permissions
    • Tests error handling
  3. billing-hooks.test.tsx - React hooks tests

    • Tests all billing hooks with real API calls
    • Verifies cache invalidation
    • Tests loading and error states
  4. billing-e2e.test.ts - End-to-end flow tests

    • Complete subscription lifecycle
    • Payment method management
    • Failed payment recovery
    • Trial period handling

Running Tests

Prerequisites

  1. Ensure you have test Stripe API keys in your .env.local:

    VITE_STRIPE_PUBLIC_KEY=pk_test_...
    VITE_STRIPE_SECRET_KEY=sk_test_...
  2. Ensure Supabase is running locally or you're connected to a test instance:

    supabase start  # For local development
  3. Create test products in your Stripe dashboard:

    • Go to Stripe Test Dashboard
    • Create at least one product with monthly pricing
    • Note: Tests will skip if no products are found

Running All Billing Tests

# Run all billing tests
pnpm test billing

# Run specific test file
pnpm test billing-graphql
pnpm test billing-hooks
pnpm test billing-e2e

# Run in watch mode for development
pnpm test billing --watch

# Run with coverage
pnpm test billing --coverage

Running Individual Tests

# Run a specific test suite
pnpm test -t "Subscription Management"

# Run a specific test
pnpm test -t "should create a subscription"

Test Data Management

Automatic Cleanup

All tests automatically clean up after themselves:

  • Deletes test Stripe customers
  • Cancels test subscriptions
  • Removes test payment methods
  • Deletes test organizations and users

Test Isolation

Each test run creates unique test data with timestamps:

  • Email: test_${timestamp}@example.com
  • Organization: Test Org ${timestamp}
  • This prevents conflicts between parallel test runs

Test Cards

The test setup includes several test card numbers:

// Always succeeds
testCard: '4242424242424242'

// Requires 3D Secure authentication
testCard3DS: '4000002500003155'

// Always declined
testCardDeclined: '4000000000000002'

See Stripe Testing Cards for more options.

Writing New Tests

1. GraphQL Resolver Test Example

it('should create a subscription', async () => {
const data = await graphqlClient.request(CREATE_SUBSCRIPTION, {
input: {
priceId: 'price_xxx',
quantity: 1,
paymentMethodId: 'pm_xxx',
},
})

expect(data.createSubscription.success).toBe(true)
expect(data.createSubscription.subscription.status).toBe('active')
})

2. Hook Test Example

it('should fetch billing details', async () => {
const { result } = renderHook(() => useBillingDetails(), { wrapper })

await waitFor(() => {
expect(result.current.isSuccess).toBe(true)
})

expect(result.current.data?.organization).toBeDefined()
})

3. E2E Test Example

it('should complete full subscription flow', async () => {
// 1. Add payment method
const paymentMethod = await createTestPaymentMethod(customerId)

// 2. Create subscription
const subscription = await createSubscription(priceId, paymentMethod.id)

// 3. Wait for webhook
await waitForWebhook(() => checkSubscriptionActive())

// 4. Verify state
expect(subscription.status).toBe('active')
})

Debugging Tests

Enable Debug Logging

# Show all GraphQL requests/responses
DEBUG=graphql pnpm test billing

# Show Stripe API calls
DEBUG=stripe pnpm test billing

# Show all logs
DEBUG=* pnpm test billing

Common Issues

  1. "No products available"

    • Create test products in your Stripe dashboard
    • Ensure products are active and have prices
  2. "Webhook timeout"

    • Check if Supabase Edge Functions are running
    • Verify webhook endpoints are configured
    • Increase timeout in waitForWebhook()
  3. "Unauthorized" errors

    • Check if test user was created successfully
    • Verify auth token is being passed to GraphQL
    • Ensure user has correct organization role
  4. "Customer not found"

    • Verify Stripe customer was created
    • Check if customer ID was saved to organization
    • Ensure using correct Stripe API keys

Inspecting Test Data

During test development, you can inspect data:

// Log GraphQL responses
console.log(JSON.stringify(data, null, 2))

// Check Stripe dashboard
console.log(`Customer: https://dashboard.stripe.com/test/customers/${customerId}`)

// Query database directly
const { data } = await supabase.from('organizations').select('*').eq('id', orgId)
console.log(data)

CI/CD Integration

GitHub Actions Example

name: Billing Tests

on: [push, pull_request]

jobs:
test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Install dependencies
run: pnpm install

- name: Run billing tests
env:
VITE_STRIPE_PUBLIC_KEY: ${{ secrets.STRIPE_TEST_PUBLIC_KEY }}
VITE_STRIPE_SECRET_KEY: ${{ secrets.STRIPE_TEST_SECRET_KEY }}
VITE_SUPABASE_URL: ${{ secrets.SUPABASE_TEST_URL }}
VITE_SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_TEST_ANON_KEY }}
run: pnpm test billing --run

Local CI Testing

# Run tests as CI would
CI=true pnpm test billing --run --coverage

Best Practices

  1. Always use test API keys - Never use production keys in tests
  2. Clean up test data - Ensure cleanup runs even if tests fail
  3. Use unique identifiers - Timestamp-based IDs prevent conflicts
  4. Test error paths - Test declined cards, network errors, etc.
  5. Wait for webhooks - Don't assume immediate consistency
  6. Test permissions - Verify non-admin users can't access billing
  7. Monitor test performance - Real API calls are slower than mocks

Security Considerations

  1. Never commit API keys - Use environment variables
  2. Use separate test account - Don't mix test and production data
  3. Rotate test keys regularly - Treat test keys as sensitive
  4. Limit test data - Clean up old test customers periodically
  5. Monitor test usage - Watch for unusual test API activity

Troubleshooting

Viewing Stripe Logs

  1. Go to Stripe Dashboard Logs
  2. Filter by test mode
  3. Look for your test customer/subscription IDs

Checking Webhook Delivery

  1. Go to Stripe Webhooks
  2. Check webhook attempts and responses
  3. Verify endpoint is receiving events

Database Queries

-- Find test organizations
SELECT * FROM organizations
WHERE name LIKE 'Test Org %'
ORDER BY created_at DESC;

-- Check subscription status
SELECT * FROM organizations
WHERE stripe_customer_id IS NOT NULL
AND stripe_subscription_id IS NOT NULL;

-- View payment methods
SELECT * FROM payment_methods
WHERE customer_id = 'cus_xxx';