Skip to content

Stripe ghost subscription fix - #8941

Open
jigar-f wants to merge 4 commits into
mainfrom
jigar/stripe-ghost-sub-fix
Open

Stripe ghost subscription fix#8941
jigar-f wants to merge 4 commits into
mainfrom
jigar/stripe-ghost-sub-fix

Conversation

@jigar-f

@jigar-f jigar-f commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

This pull request refactors and improves the Stripe payment flow, particularly for Android, by switching to a deferred-intent approach. It ensures that the Stripe publishable key is dynamically sourced from the backend, centralizes environment selection logic, and improves error handling and user messaging for Stripe failures. Additionally, it cleans up and modernizes how provider data is modeled and accessed.

Stripe Payment Flow Improvements:

  • Replaces upfront Stripe SDK initialization with a deferred-intent flow, where the payment sheet is presented immediately and the backend subscription is only created after the user taps Pay. This prevents abandoned subscriptions and ensures the Stripe publishable key matches the backend environment. (lib/core/services/stripe_service.dart, lib/features/auth/choose_payment_method.dart) [1] [2]
  • Refactors the payment flow to dynamically update the Stripe publishable key from the backend's plans response, removing reliance on hardcoded or environment-based keys. (lib/core/services/stripe_service.dart, lib/core/models/plan_data.dart, lib/core/services/injection_container.dart) [1] [2] [3]

Provider and Plan Modeling:

  • Refactors the provider data model to a strongly-typed ProviderData class, making it easier and safer to access provider-specific fields like Stripe's pubKey. (lib/core/models/plan_data.dart)
  • Adds convenience methods to PlansData for retrieving the publishable key for the current platform and for sorting providers by subscription support. (lib/core/models/plan_data.dart)

Error Handling and User Messaging:

  • Improves error handling for Stripe failures by filtering out developer-facing error messages and only showing safe, user-facing messages in the UI. (lib/core/extensions/error.dart, lib/core/services/stripe_service.dart) [1] [2]
  • Adds a method to the plan model for retrieving the expected monthly price in USD cents, ensuring correct amounts are quoted to Stripe. (lib/core/extensions/plan.dart)

Environment Handling:

  • Centralizes and standardizes the environment selection logic with a new IsStaging method on Opts, ensuring consistent environment checks across the codebase. (lantern-core/utils/common.go, lantern-core/core.go, lantern-core/mobile/mobile.go) [1] [2] [3]

Other Cleanups:

  • Removes unused Stripe key accessors from AppSecrets and updates imports accordingly. (lib/core/common/app_secrets.dart)
  • Updates dependency injection and service initialization to match the new Stripe flow. (lib/core/services/injection_container.dart)

Summary by CodeRabbit

  • New Features

    • Payment setup now loads the appropriate Stripe configuration from available plan information.
    • Subscription creation is deferred until payment is confirmed, improving checkout reliability.
    • Plans now support platform-specific payment providers and display expected monthly pricing.
    • Added clearer, user-friendly messages for Stripe payment errors.
  • Bug Fixes

    • Improved handling of canceled payments and payment confirmation failures.
    • Enhanced staging environment detection and setup for more consistent behavior.

@jigar-f jigar-f self-assigned this Jul 31, 2026
Copilot AI review requested due to automatic review settings July 31, 2026 11:55
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 28850860-7735-4e01-886c-d7fca001c7b2

📥 Commits

Reviewing files that changed from the base of the PR and between e07dea8 and 4e9b429.

📒 Files selected for processing (4)
  • lib/core/models/plan_data.dart
  • lib/core/services/injection_container.dart
  • lib/core/services/stripe_service.dart
  • lib/features/auth/choose_payment_method.dart
🚧 Files skipped from review as they are similar to previous changes (3)
  • lib/core/models/plan_data.dart
  • lib/features/auth/choose_payment_method.dart
  • lib/core/services/injection_container.dart

📝 Walkthrough

Walkthrough

The change centralizes staging detection and setup. It supplies Stripe configuration from plans, registers Stripe without upfront key initialization, and defers subscription creation until the payment sheet confirms payment.

Changes

Staging runtime configuration

Layer / File(s) Summary
Centralized staging detection
lantern-core/utils/common.go, lantern-core/core.go
Opts.IsStaging accepts both "stage" and "staging". Core initialization uses the helper.
Staging backend setup
lantern-core/mobile/mobile.go
StartIPCServer applies staging environment variables before creating the local backend.

Deferred Stripe payment flow

Layer / File(s) Summary
Plan provider and Stripe key contract
lib/core/extensions/plan.dart, lib/core/models/plan_data.dart
Plan data selects platform providers, exposes the Stripe publishable key, preserves provider payloads, and provides monthly amounts in cents.
Dynamic Stripe service registration
lib/core/services/injection_container.dart, lib/features/plans/provider/plans_notifier.dart
Android registers Stripe without upfront initialization. Plans state updates the Stripe publishable key. Notification initialization runs separately.
Deferred payment-sheet confirmation
lib/core/services/stripe_service.dart, lib/features/auth/choose_payment_method.dart, lib/core/common/app_secrets.dart
The payment sheet receives amount and email, creates the subscription on Pay, and confirms the returned intent. Static Stripe key getters are removed.
Stripe error presentation
lib/core/services/stripe_service.dart, lib/features/auth/choose_payment_method.dart
Cancellation and Stripe failures use separate logging and user-facing messages. Non-Stripe failures retain their existing error path.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested reviewers: copilot

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: preventing unintended Stripe subscriptions during payment flow.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jigar/stripe-ghost-sub-fix

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Not ready to approve

The updated Stripe flow is missing an applySettings() propagation step and has inconsistent Stripe error filtering that can surface developer-facing messages to users.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Pull request overview

This pull request refactors the Stripe subscription flow to a deferred-intent approach (to avoid creating abandoned “ghost” subscriptions), sources the Stripe publishable key dynamically from the backend plans response, and centralizes staging-environment detection in lantern-core.

Changes:

  • Moves Android Stripe subscriptions to a deferred-intent PaymentSheet flow where the backend subscription is created only after the user taps Pay.
  • Refactors plans/provider modeling to expose a typed Stripe publishable key (PlansData.stripePubKey) and keeps the Stripe SDK key in sync when plans change.
  • Centralizes environment selection via Opts.IsStaging() and ensures staging is set early enough on Android IPC startup.
File summaries
File Description
lib/features/plans/provider/plans_notifier.dart Adds a state listener to sync Stripe publishable key from fetched/cached plans.
lib/features/auth/choose_payment_method.dart Switches Android Stripe flow to deferred-intent callbacks; improves Stripe error handling in UI.
lib/core/services/stripe_service.dart Reworks Stripe service to deferred-intent PaymentSheet initialization/confirmation and adds user-facing error filtering.
lib/core/services/injection_container.dart Registers StripeService on Android without upfront initialization; simplifies notification init path.
lib/core/models/plan_data.dart Adds platform provider accessors and typed provider data (incl. Stripe pubKey).
lib/core/extensions/plan.dart Adds monthlyUsdCents helper for Stripe amount quoting.
lib/core/extensions/error.dart Adds StripeException-specific localized description filtering to avoid developer-facing messages.
lib/core/common/app_secrets.dart Removes Stripe publishable key accessors from env-based secrets.
lantern-core/utils/common.go Adds Opts.IsStaging() helper to centralize staging environment checks.
lantern-core/mobile/mobile.go Ensures staging env is set before backend construction on Android IPC startup.
lantern-core/core.go Replaces ad-hoc staging checks with opts.IsStaging().
Review details
  • Files reviewed: 11/11 changed files
  • Comments generated: 4
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Comment thread lib/core/services/stripe_service.dart
Comment thread lib/core/services/stripe_service.dart
Comment thread lib/features/plans/provider/plans_notifier.dart
Comment thread lib/core/services/injection_container.dart Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
lantern-core/utils/common.go (1)

18-23: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add regression tests for Opts.IsStaging.

Opts.IsStaging() controls stage-environment selection, but the test files do not cover "stage", "staging", or a non-staging value. Add focused cases for those inputs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lantern-core/utils/common.go` around lines 18 - 23, Add focused regression
tests for Opts.IsStaging covering both accepted values, "stage" and "staging",
which must return true, plus a non-staging environment value, which must return
false. Keep the tests table-driven or otherwise scoped specifically to this
method.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/core/extensions/plan.dart`:
- Around line 15-20: Update the monthlyUsdCents getter to convert the Stripe
expectedMonthlyPrice amount from major USD units to cents by multiplying the
parsed value by 100 before rounding. Keep _amountOf and _formatPriceMap
unchanged so display currency formatting continues to use the existing
major-unit conversion.

In `@lib/core/models/plan_data.dart`:
- Around line 9-12: Update the platformProviders getter in the plan data model
to use PlatformUtils.isAndroid instead of PlatformUtils.isMobile, returning
providers.android on Android and providers.desktop on iOS or other platforms to
match choose_payment_method.dart and sort the displayed provider list.

In `@lib/core/services/stripe_service.dart`:
- Around line 163-174: Unify Stripe user-facing error filtering by making
StripeErrorMessage.userFacingMessage delegate to
StripeErrorExtension.localizedDescription, or by sharing its allowlist that
permits only card_error, validation_error, and decline-coded errors. Update
lib/core/services/stripe_service.dart:163-174 to remove the inconsistent
_hiddenErrorTypes denylist; retain lib/core/extensions/error.dart:159-174 as the
single implementation if delegation is used, otherwise remove it only if it
becomes redundant.

In `@lib/features/auth/choose_payment_method.dart`:
- Around line 209-246: Guard both the onSuccess and onError callbacks passed to
startStripeSDK with an early context.mounted check before invoking
finishPaymentRedirect, onPurchaseResult, or any context.showSnackBar call.
Preserve the existing success, cancellation, logging, and error-message behavior
when the widget remains mounted.

---

Nitpick comments:
In `@lantern-core/utils/common.go`:
- Around line 18-23: Add focused regression tests for Opts.IsStaging covering
both accepted values, "stage" and "staging", which must return true, plus a
non-staging environment value, which must return false. Keep the tests
table-driven or otherwise scoped specifically to this method.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 44fe5435-fe81-4a4b-9ee0-65f1aeafdae7

📥 Commits

Reviewing files that changed from the base of the PR and between 7e3945d and e07dea8.

📒 Files selected for processing (11)
  • lantern-core/core.go
  • lantern-core/mobile/mobile.go
  • lantern-core/utils/common.go
  • lib/core/common/app_secrets.dart
  • lib/core/extensions/error.dart
  • lib/core/extensions/plan.dart
  • lib/core/models/plan_data.dart
  • lib/core/services/injection_container.dart
  • lib/core/services/stripe_service.dart
  • lib/features/auth/choose_payment_method.dart
  • lib/features/plans/provider/plans_notifier.dart
💤 Files with no reviewable changes (1)
  • lib/core/common/app_secrets.dart

Comment thread lib/core/extensions/plan.dart
Comment thread lib/core/models/plan_data.dart Outdated
Comment thread lib/core/services/stripe_service.dart
Comment thread lib/features/auth/choose_payment_method.dart
Copilot AI review requested due to automatic review settings July 31, 2026 13:04

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Not ready to approve

The Stripe publishable key is updated but Stripe.instance.applySettings() is no longer called anywhere, which can prevent the native SDK from receiving the key before initPaymentSheet runs.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Review details

Suppressed comments (1)

lib/core/services/stripe_service.dart:62

  • Stripe.publishableKey is set via updatePublishableKey, but I can't find any call to Stripe.instance.applySettings() in the repo. flutter_stripe's docs/examples require applySettings() after setting the key so the native SDK picks it up; without it, initPaymentSheet may fail due to a missing/stale publishable key.
      // initPaymentSheet applies any pending settings (including the
      // publishable key) to the native SDK itself. If plans never provided
      // a key, this throws StripeConfigException into the catch below.
      await Stripe.instance.initPaymentSheet(
  • Files reviewed: 7/7 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Copilot AI review requested due to automatic review settings July 31, 2026 13:09

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Not ready to approve

The deferred PaymentSheet is initialized with IntentMode.paymentMode but the flow can return a SetupIntent client secret, which can break the PaymentSheet confirm step due to intent-type mismatch.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Review details
  • Files reviewed: 7/7 changed files
  • Comments generated: 1
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Comment on lines +64 to +68
intentConfiguration: IntentConfiguration(
mode: IntentMode.paymentMode(
currencyCode: 'USD',
amount: amount,
// The subscription charges this payment method on renewal, so
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants