Description
When a user subscribes to OpenCode Lite using a card that requires 3D Secure (3DS) or SCA verification, the subscription is never activated. Payment is confirmed by Stripe, but BillingTable.lite is never set and LiteTable is never populated. The user is locked out of Lite indefinitely with no recovery path.
The customer.subscription.created handler activates Lite by writing BillingTable.lite = {} and inserting into LiteTable. It reads the payment method from default_payment_method on the subscription object.
In 3DS/SCA flows Stripe attaches the payment method asynchronously — after the redirect completes — so default_payment_method is null when customer.subscription.created fires. The handler throws before any DB writes:
const paymentMethodID = body.data.object.default_payment_method as string
if (!paymentMethodID) throw new Error("Payment method ID not found")
Stripe retries for 72 hours then stops. By that point payment has already succeeded, but activation was never moved to invoice.payment_succeeded. The user's card is charged and service is never granted.
3DS/SCA is mandatory for EU/UK cards and increasingly common globally.
Fix: Move Lite activation from customer.subscription.created to invoice.payment_succeeded (billing_reason === "subscription_create"), which fires only after Stripe confirms payment — covering both immediate and 3DS/SCA flows.
Plugins
No response
OpenCode version
No response
Steps to reproduce
- Subscribe to OpenCode Lite using an EU/UK card that requires 3DS (e.g. Stripe test card
4000002500003155).
- Complete the 3DS challenge and return to the app.
- Observe the workspace remains on the free tier despite payment succeeding.
Screenshot and/or share link
No response
Operating System
No response
Terminal
No response
Description
When a user subscribes to OpenCode Lite using a card that requires 3D Secure (3DS) or SCA verification, the subscription is never activated. Payment is confirmed by Stripe, but
BillingTable.liteis never set andLiteTableis never populated. The user is locked out of Lite indefinitely with no recovery path.The
customer.subscription.createdhandler activates Lite by writingBillingTable.lite = {}and inserting intoLiteTable. It reads the payment method fromdefault_payment_methodon the subscription object.In 3DS/SCA flows Stripe attaches the payment method asynchronously — after the redirect completes — so
default_payment_methodisnullwhencustomer.subscription.createdfires. The handler throws before any DB writes:Stripe retries for 72 hours then stops. By that point payment has already succeeded, but activation was never moved to
invoice.payment_succeeded. The user's card is charged and service is never granted.3DS/SCA is mandatory for EU/UK cards and increasingly common globally.
Fix: Move Lite activation from
customer.subscription.createdtoinvoice.payment_succeeded(billing_reason === "subscription_create"), which fires only after Stripe confirms payment — covering both immediate and 3DS/SCA flows.Plugins
No response
OpenCode version
No response
Steps to reproduce
4000002500003155).Screenshot and/or share link
No response
Operating System
No response
Terminal
No response