Skip to content

Lite subscription never activates for 3DS/SCA payments causing users to be permanently locked out #28408

Closed as not planned

Description

@PanAchy

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

  1. Subscribe to OpenCode Lite using an EU/UK card that requires 3DS (e.g. Stripe test card 4000002500003155).
  2. Complete the 3DS challenge and return to the app.
  3. 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

Activity

  1. LucaWolf commented on Jul 17, 2026

    @LucaWolf

    OpenCode, I was about to subscribe but not trusting the process at this moment :-( I do not want "try and see if it worked" or to fiddle with support emails and what not. Please review and address the ticket. This is what Github's copilot says about the matter (regardless if AI slop or not)

    Root Cause

    Missing Payment Confirmation for Subscription Activation
    The Issue:

    When customer.subscription.created fires (line 108), the code immediately activates the subscription (sets lite: {} at line 150) without verifying that the initial invoice payment succeeded.

    The subscription can be created by Stripe even when:

    The payment is still pending (e.g., requires 3DS)
    The invoice hasn't been paid yet
    The subscription status is incomplete or incomplete_expired
    

    What Happens:

    customer.subscription.created fires → Subscription activated immediately ✓
    Payment requires 3DS verification → User is shown 3DS prompt
    Payment eventually succeeds at Stripe → invoice.payment_succeeded fires with billing_reason: "subscription_create"
    But the webhook only records the payment (inserts to PaymentTable, line 245-258), it does NOT activate the subscription because it's already activated
    

    The Gap:

    The invoice.payment_succeeded handler (lines 210-258) for subscription creation does NOT include:

    // This is MISSING - no update to actually activate/enable the subscription
    await tx.update(BillingTable).set({
      lite: {},  // Should set this when invoice payment succeeds
      // ... other fields
    }).where(eq(BillingTable.workspaceID, workspaceID))

    So if the subscription was already set to active but there's a failure state or the system crashes between events, OR if there's a condition where the subscription needs to transition from incomplete to active, there's no handler to actually mark it as successfully activated.
    Even Worse - Incomplete Subscriptions:

    Line 187-196 handle customer.subscription.updated only when status is incomplete_expired, which deactivates the subscription. But there's no handler for when the subscription transitions from incomplete → active after successful payment.
    Scenario That Breaks:

    User subscribes → customer.subscription.created fires → lite: {} is set (activates)
    3DS verification required
    User completes 3DS
    invoice.payment_succeeded fires → payment recorded but no subscription state update
    If there's any system state mismatch, the subscription appears inactive even though payment succeeded
    

    Fix Needed:

    Add a handler for when subscription completes payment:

    if (body.type === "customer.subscription.updated" && 
        body.data.object.status === "active" && 
        body.data.previous_attributes?.status !== "active") {
      // Subscription just became active after payment confirmation
      // Ensure lite subscription is properly activated
    }

    Or better: ensure invoice.payment_succeeded with billing_reason: "subscription_create" also updates subscription activation status in your database.

  2. github-actions commented on Sep 17, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions