Stripe integration: subscriptions, one-time payments and credits
How ShipAny wires Stripe Checkout, webhooks, subscriptions, promotion codes and credit grants — configured from the admin panel, with no Stripe products to pre-create.
Last updated: Oct 8, 2026
Every ShipAny template ships with a complete Stripe integration: Checkout sessions for one-time purchases and subscriptions, a signed webhook endpoint, subscription lifecycle handling, promotion codes, and automatic credit grants — all configured from the admin panel instead of environment variables.
What's included
- One-time payments and subscriptions — the checkout mode is chosen from the order type (
paymentorsubscription). - No products to pre-create in Stripe — line items are sent as inline
price_data, so the prices in your pricing config are the source of truth. - Card, WeChat Pay and Alipay — for one-time payments, the allowed methods come from your settings (
card,wechat_pay,alipay). Stripe only supports WeChat Pay and Alipay for one-time payments, so subscriptions stay on Stripe's defaults. - Promotion codes — customers can enter Stripe promotion codes at checkout, or a code can be pre-applied to the session.
- Customer reuse — an existing Stripe customer is looked up by email before a new one is created.
- Self-serve cancellation — users can cancel their subscription from their account settings; the provider also exposes Stripe's billing portal if you want to link to it.
How a payment flows
- The user clicks a plan;
POST /api/payment/checkoutcreates an order (statuscreated) and a Stripe Checkout session. - After paying, Stripe redirects back to your success URL and sends a webhook to
/api/payment/notify/stripe. - The webhook signature is verified against your signing secret using the raw request body.
- The order is marked
paid, and in one database transaction ShipAny creates or updates the subscription and grants the plan's credits.
The order row is locked with SELECT … FOR UPDATE inside that transaction, so a webhook retry and the synchronous success callback can't both grant credits for the same payment.
Webhook events handled
| Stripe event | What ShipAny does |
|---|---|
checkout.session.completed | Marks the order paid, creates the subscription, grants credits |
invoice.payment_succeeded | Renewal: extends the subscription period and grants the next cycle's credits |
invoice.payment_failed | Acknowledged without changing state — Stripe's own retry schedule handles dunning |
customer.subscription.updated | Syncs plan / status / period changes |
customer.subscription.deleted | Marks the subscription canceled |
Setup
- In the Stripe dashboard, copy your secret key and publishable key.
- Add a webhook endpoint pointing to
https://<your-domain>/api/payment/notify/stripeand subscribe to the five events above. Copy its signing secret. - In ShipAny, open Admin → Settings → Payment → Stripe, fill in the secret key, publishable key and webhook signing secret, then switch on Enable Stripe. Values are stored in the database (encrypted at rest when
CONFIG_ENCRYPTION_KEYis set). - Define your plans and their credit amounts in the pricing config, then run a test purchase in Stripe test mode.
Pitfalls to avoid
- Point the webhook at the domain that actually serves your app. If you move hosting (for example to Cloudflare Workers), the webhook URL stays the same as long as the domain does — but test one real payment after the move.
- Check that payment settings actually load in production. A healthy homepage doesn't prove your Stripe keys are being read.
/api/config/publicshould reportstripe_enabled: "true"; if it doesn't, the app isn't reading your settings table. - Don't expect WeChat Pay / Alipay on subscriptions. Stripe only offers them for one-time payments; use the native Alipay or WeChat Pay providers if you need recurring billing in China.
Other payment providers
Stripe is the default, but the same order → subscription → credits pipeline also runs on Creem, Alipay and WeChat Pay. Pick the default provider in the admin panel, or let users choose at checkout.
