TinyShip
TinyShip
 TinyShip
TinyShip
TinyShip Documentation
TinyShip User GuideGetting StartedBasic Configuration
Payment configurationStripeDodo Payments ConfigurationCreemPayPalWeChat PayAlipayDynamic PricingPayment testing
Credits System ConfigurationAffiliate System Configuration
Storage Service ConfigurationDatabase ConfigurationCaptcha Configuration
Development Best PracticesLocal E2E Workflow
User GuidePayment

Payment testing

Test payments and webhooks in local development

This guide covers how to test payments in local development, including test-mode setup and webhook debugging for each provider.

Before you start: Finish the base setup in Payment configuration.

Test environment

Use test credentials in development.

Stripe test mode

# Test keys
STRIPE_SECRET_KEY=sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
STRIPE_PUBLIC_KEY=pk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
STRIPE_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Test cards:

  • Successful payment: 4242424242424242
  • Declined payment: 4000000000000002
  • Authentication required: 4000002500003155

WeChat Pay

WeChat Pay has no sandbox. Charge a tiny real amount, such as ¥0.01 per order. Amounts in config/payment.ts are yuan. The provider multiplies by 100 before it calls WeChat.

Creem test mode

# Test API (this is also the default when CREEM_SERVER_URL is unset)
CREEM_SERVER_URL=https://test-api.creem.io
CREEM_API_KEY=creem_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Dodo Payments test mode

DODO_PAYMENTS_API_KEY=TSxxxxxxxxxx
DODO_PAYMENTS_WEBHOOK_KEY=whsec_xxxxxxxxxx
DODO_PAYMENTS_TEST_MODE=true

Test cards:

  • Successful payment: 4242424242424242
  • Declined payment: 4000000000000002
  • Expiry: any future date (for example 06/32)
  • CVC: any three digits (for example 123)
  • Cardholder name: any English name. Digits are rejected.

Local webhook testing

Local development: Use the methods below to receive webhooks. You do not need a production webhook endpoint first.

WeChat Pay, Creem, and Dodo Payments need a public URL. Map your local server to one. Stripe can stay fully local with the Stripe CLI.

ProviderRecommended approach
StripeStripe CLI (no tunnel)
WeChat Payngrok / Cloudflare Tunnel
Creemngrok / Cloudflare Tunnel
Dodo Paymentsngrok / Cloudflare Tunnel

Tunnel tools

WeChat Pay, Creem, and Dodo Payments need a tunnel:

  • ngrok docs
  • Cloudflare Tunnel docs
# Public tunnel to the local app (port 7001)
npx ngrok http 7001

Point each provider at the tunnel:

  • WeChat Pay: https://abc123.ngrok.io/api/payment/webhook/wechat
  • Creem: https://abc123.ngrok.io/api/payment/webhook/creem
  • Dodo Payments: https://abc123.ngrok.io/api/payment/webhook/dodo

Stripe webhooks

Stripe supports two local webhook setups.

Option 1: Stripe CLI (recommended)

Docs: https://docs.stripe.com/stripe-cli

# 1. Install the Stripe CLI
# macOS
brew install stripe/stripe-cli/stripe

# Windows
scoop bucket add stripe https://github.com/stripe/scoop-stripe-cli.git
scoop install stripe

# 2. Log in
stripe login

# 3. Forward webhooks to the local app
stripe listen --forward-to localhost:7001/api/payment/webhook/stripe

# 4. Copy the signing secret the CLI prints into STRIPE_WEBHOOK_SECRET
# Example: whsec_1234567890abcdef...

What you get:

  • The whole flow stays on your machine
  • You receive real webhook events as they happen
  • Signature verification is handled for you
  • Event logs are available in the CLI

Option 2: ngrok and the Stripe Dashboard

Use this when the Stripe CLI is unavailable:

# 1. Start a tunnel
ngrok http 7001

# 2. Copy the https URL ngrok prints
# Example: https://abc123.ngrok.io

# 3. In the Stripe Dashboard, add a webhook endpoint:
# https://abc123.ngrok.io/api/payment/webhook/stripe
# Events:
# - checkout.session.completed
# - customer.subscription.updated
# - customer.subscription.deleted

Set the signing secret:

# .env.local
STRIPE_WEBHOOK_SECRET=whsec_your_webhook_secret_here

Check that events arrive

1. Trigger a basic event

# Watch webhook events (Stripe CLI)
stripe listen --forward-to localhost:7001/api/payment/webhook/stripe --events checkout.session.completed,customer.subscription.updated

# Send built-in test events
stripe trigger checkout.session.completed
stripe trigger customer.subscription.updated
stripe trigger customer.subscription.deleted

2. What stripe trigger does

stripe trigger sends fixed fixture data. You cannot customize the payload, and it still runs the real webhook path:

# List triggers
stripe trigger --help

# Subscription-related events
stripe trigger checkout.session.completed   # Checkout completed
stripe trigger invoice.payment_succeeded    # Invoice paid
stripe trigger customer.subscription.created # Subscription created

The fixture:

  • Matches the shape of a real event
  • Includes the required fields and relations
  • Uses fixed test values
  • Does not let you set a specific user ID or order ID

3. Test with your own data

Stripe Dashboard

# 1. Create a real Checkout Session in the Stripe Dashboard (test mode)
# 2. Pay with 4242 4242 4242 4242
# 3. Watch the webhook your local app receives

Send a custom webhook yourself

curl -X POST http://localhost:7001/api/payment/webhook/stripe \
  -H "Content-Type: application/json" \
  -H "Stripe-Signature: YOUR_TEST_SIGNATURE" \
  -d '{
    "id": "evt_test_webhook",
    "object": "event",
    "type": "checkout.session.completed",
    "data": {
      "object": {
        "id": "cs_test_custom_session_id",
        "mode": "subscription",
        "customer": "cus_test_customer",
        "metadata": {
          "planId": "your_plan_id",
          "userId": "your_user_id",
          "orderId": "your_order_id"
        }
      }
    }
  }'

Recommended test flow

# 1. Forward Stripe webhooks
stripe listen --forward-to localhost:7001/api/payment/webhook/stripe

# 2. Start the app
pnpm run dev

# 3. Open pricing and pay with a test card
open http://localhost:7001/pricing

# 4. Watch the server logs and confirm the webhook handler ran

Test cards

Stripe

Card numberScenario
4242 4242 4242 4242Successful payment
4000 0000 0000 0002Declined
4000 0000 0000 9995Insufficient funds
4000 0025 0000 31553D Secure required

WeChat Pay

  • Pay with a real WeChat scan
  • Use ¥0.01
  • Refund the charge after the test

Creem

  • Use the test cards Creem provides
  • See the Creem docs

Dodo Payments

Card numberScenario
4242 4242 4242 4242Successful payment
4000 0000 0000 0002Declined
  • Expiry: any future date (for example 06/32)
  • CVC: any three digits (for example 123)
  • Cardholder name: any English name (no digits)
  • See the Dodo Payments docs

Related docs:

  • Payment configuration — providers and plans
  • Dodo Payments — Dodo setup
  • Credits — credit packs and consumption

Dynamic Pricing

Manage pricing plans in real time from the admin panel, without code changes or redeployment

Credits System Configuration

Configure credit packs and how AI features spend them

On this page

Test environmentStripe test modeWeChat PayCreem test modeDodo Payments test modeLocal webhook testingTunnel toolsStripe webhooksOption 1: Stripe CLI (recommended)Option 2: ngrok and the Stripe DashboardCheck that events arrive1. Trigger a basic event2. What stripe trigger does3. Test with your own dataRecommended test flowTest cardsStripeWeChat PayCreemDodo Payments