[PR #6793] [PM-33946] feat: Add dynamic pricing and fix checkout flow #32881

Open
opened 2026-04-18 16:13:53 -05:00 by GiteaMirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/bitwarden/android/pull/6793
Author: @SaintPatrck
Created: 4/13/2026
Status: 🔄 Open

Base: mainHead: premium-upgrade/PM-33946-dynamic-pricing-impl


📝 Commits (10+)

  • 1e37d28 [PM-33946] feat: Replace static pricing with dynamic data from plans API
  • c884d3c Address code review findings for pricing error handling
  • ac97765 Fix checkout callback URL, dialog timing, and result handling
  • 474cd5f Use syncForResult to prevent stuck loading dialog on sync failure
  • 28087e4 Pass PremiumCheckoutCallbackResult through SpecialCircumstance directly
  • 43e5e57 Address review findings and add missing test coverage
  • a24e139 Show confirming upgrade dialog and snackbar on PlanScreen
  • 8589b04 Order imports and fix formatting
  • 4785609 Update specialCircumstance mock value
  • 3ad13c5 Show loading dialog during initial pricing fetch

📊 Changes

24 files changed (+1366 additions, -139 deletions)

View changed files

📝 app/src/main/kotlin/com/x8bit/bitwarden/MainViewModel.kt (+9 -5)
📝 app/src/main/kotlin/com/x8bit/bitwarden/data/billing/manager/PremiumStateManagerImpl.kt (+6 -5)
📝 app/src/main/kotlin/com/x8bit/bitwarden/data/billing/repository/BillingRepository.kt (+6 -0)
📝 app/src/main/kotlin/com/x8bit/bitwarden/data/billing/repository/BillingRepositoryImpl.kt (+15 -0)
app/src/main/kotlin/com/x8bit/bitwarden/data/billing/repository/model/PremiumPlanPricingResult.kt (+28 -0)
app/src/main/kotlin/com/x8bit/bitwarden/data/billing/util/PremiumCheckoutUtils.kt (+65 -0)
📝 app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/model/SpecialCircumstance.kt (+4 -1)
📝 app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/premium/plan/PlanScreen.kt (+43 -5)
📝 app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/premium/plan/PlanViewModel.kt (+305 -43)
📝 app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/premium/plan/handlers/PlanHandlers.kt (+10 -0)
📝 app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavViewModel.kt (+2 -2)
📝 app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/util/ShortcutUtils.kt (+1 -1)
📝 app/src/test/kotlin/com/x8bit/bitwarden/MainViewModelTest.kt (+97 -7)
📝 app/src/test/kotlin/com/x8bit/bitwarden/data/billing/repository/BillingRepositoryTest.kt (+51 -0)
app/src/test/kotlin/com/x8bit/bitwarden/data/billing/util/PremiumCheckoutUtilsTest.kt (+63 -0)
📝 app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/premium/plan/PlanScreenTest.kt (+159 -0)
📝 app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/premium/plan/PlanViewModelTest.kt (+360 -68)
📝 app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/util/ShortcutUtilsTest.kt (+12 -2)
📝 network/src/main/kotlin/com/bitwarden/network/api/AuthenticatedBillingApi.kt (+8 -0)
network/src/main/kotlin/com/bitwarden/network/model/PremiumPlanResponseJson.kt (+51 -0)

...and 4 more files

📄 Description

🎟️ Tracking

https://bitwarden.atlassian.net/browse/PM-33946

📔 Objective

Replace the static placeholder rate with dynamic pricing from the plans API, and implement correct checkout result handling for the premium upgrade flow.

Dynamic pricing:

  • Fetch pricing from GET /api/plans/premium on PlanScreen load
  • Show loading and error states when pricing is unavailable
  • Format the monthly rate from the annual plan price

Checkout result handling:

  • Correct the callback URL to bitwarden://premium-checkout-result to match the server-configured Stripe redirect (PM-34173)
  • Parse ?result=success / ?result=canceled query parameter via PremiumCheckoutCallbackResult sealed class, following the existing CookieCallbackResult pattern
  • Observe specialCircumstanceStateFlow at runtime to detect when the user returns from checkout
  • On success: show syncing state, trigger syncForResult(), navigate back with snackbar if premium is detected, or show "Upgrade pending" dialog with Sync now / Continue options if not yet provisioned
  • On canceled: show "Payment not received yet" dialog per Figma designs
  • Track checkout return state via isAwaitingPremiumStatus on ViewState.Free

📸 Screenshots

## 📋 Pull Request Information **Original PR:** https://github.com/bitwarden/android/pull/6793 **Author:** [@SaintPatrck](https://github.com/SaintPatrck) **Created:** 4/13/2026 **Status:** 🔄 Open **Base:** `main` ← **Head:** `premium-upgrade/PM-33946-dynamic-pricing-impl` --- ### 📝 Commits (10+) - [`1e37d28`](https://github.com/bitwarden/android/commit/1e37d28e23a786b5a5b15c1dd3faef440b4c2f88) [PM-33946] feat: Replace static pricing with dynamic data from plans API - [`c884d3c`](https://github.com/bitwarden/android/commit/c884d3c7cffef7e71f9ef784e410291a4ef6d3dc) Address code review findings for pricing error handling - [`ac97765`](https://github.com/bitwarden/android/commit/ac97765475cf0ae6b0493ccba7b61cb00fe78ea0) Fix checkout callback URL, dialog timing, and result handling - [`474cd5f`](https://github.com/bitwarden/android/commit/474cd5f67322f7d79812c5e72067caa2222bde6d) Use syncForResult to prevent stuck loading dialog on sync failure - [`28087e4`](https://github.com/bitwarden/android/commit/28087e411676510ae51bced2876e8ebd035aad9a) Pass PremiumCheckoutCallbackResult through SpecialCircumstance directly - [`43e5e57`](https://github.com/bitwarden/android/commit/43e5e57dda8f86e82d7a1355d60443569c9f258a) Address review findings and add missing test coverage - [`a24e139`](https://github.com/bitwarden/android/commit/a24e1394b21e5dd67f1eb0b24354082b16c4dcbb) Show confirming upgrade dialog and snackbar on PlanScreen - [`8589b04`](https://github.com/bitwarden/android/commit/8589b04f99c548547f85cfc62269f1a9c236fc7d) Order imports and fix formatting - [`4785609`](https://github.com/bitwarden/android/commit/47856092cb125d3a374116c8f21227cd280c8d00) Update specialCircumstance mock value - [`3ad13c5`](https://github.com/bitwarden/android/commit/3ad13c5d663443be643a8bf6ec565d5d358996f0) Show loading dialog during initial pricing fetch ### 📊 Changes **24 files changed** (+1366 additions, -139 deletions) <details> <summary>View changed files</summary> 📝 `app/src/main/kotlin/com/x8bit/bitwarden/MainViewModel.kt` (+9 -5) 📝 `app/src/main/kotlin/com/x8bit/bitwarden/data/billing/manager/PremiumStateManagerImpl.kt` (+6 -5) 📝 `app/src/main/kotlin/com/x8bit/bitwarden/data/billing/repository/BillingRepository.kt` (+6 -0) 📝 `app/src/main/kotlin/com/x8bit/bitwarden/data/billing/repository/BillingRepositoryImpl.kt` (+15 -0) ➕ `app/src/main/kotlin/com/x8bit/bitwarden/data/billing/repository/model/PremiumPlanPricingResult.kt` (+28 -0) ➕ `app/src/main/kotlin/com/x8bit/bitwarden/data/billing/util/PremiumCheckoutUtils.kt` (+65 -0) 📝 `app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/model/SpecialCircumstance.kt` (+4 -1) 📝 `app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/premium/plan/PlanScreen.kt` (+43 -5) 📝 `app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/premium/plan/PlanViewModel.kt` (+305 -43) 📝 `app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/premium/plan/handlers/PlanHandlers.kt` (+10 -0) 📝 `app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavViewModel.kt` (+2 -2) 📝 `app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/util/ShortcutUtils.kt` (+1 -1) 📝 `app/src/test/kotlin/com/x8bit/bitwarden/MainViewModelTest.kt` (+97 -7) 📝 `app/src/test/kotlin/com/x8bit/bitwarden/data/billing/repository/BillingRepositoryTest.kt` (+51 -0) ➕ `app/src/test/kotlin/com/x8bit/bitwarden/data/billing/util/PremiumCheckoutUtilsTest.kt` (+63 -0) 📝 `app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/premium/plan/PlanScreenTest.kt` (+159 -0) 📝 `app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/premium/plan/PlanViewModelTest.kt` (+360 -68) 📝 `app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/util/ShortcutUtilsTest.kt` (+12 -2) 📝 `network/src/main/kotlin/com/bitwarden/network/api/AuthenticatedBillingApi.kt` (+8 -0) ➕ `network/src/main/kotlin/com/bitwarden/network/model/PremiumPlanResponseJson.kt` (+51 -0) _...and 4 more files_ </details> ### 📄 Description ## 🎟️ Tracking https://bitwarden.atlassian.net/browse/PM-33946 ## 📔 Objective Replace the static placeholder rate with dynamic pricing from the plans API, and implement correct checkout result handling for the premium upgrade flow. **Dynamic pricing:** - Fetch pricing from `GET /api/plans/premium` on PlanScreen load - Show loading and error states when pricing is unavailable - Format the monthly rate from the annual plan price **Checkout result handling:** - Correct the callback URL to `bitwarden://premium-checkout-result` to match the server-configured Stripe redirect (PM-34173) - Parse `?result=success` / `?result=canceled` query parameter via `PremiumCheckoutCallbackResult` sealed class, following the existing `CookieCallbackResult` pattern - Observe `specialCircumstanceStateFlow` at runtime to detect when the user returns from checkout - On success: show syncing state, trigger `syncForResult()`, navigate back with snackbar if premium is detected, or show "Upgrade pending" dialog with Sync now / Continue options if not yet provisioned - On canceled: show "Payment not received yet" dialog per Figma designs - Track checkout return state via `isAwaitingPremiumStatus` on `ViewState.Free` ## 📸 Screenshots <video src="https://github.com/user-attachments/assets/1dc09c06-488c-498b-85ab-60e89677f495" /> --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
GiteaMirror added the pull-request label 2026-04-18 16:13:53 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/android#32881