[PM-35454] feat: Add subscription API, domain models, and status badge component#6818
Conversation
| val statusBadgeGreenBorder: Color = Color(color = 0xFFB9F8CF) | ||
| val statusBadgeGreenBg: Color = Color(color = 0xFFF0FDF4) | ||
| val statusBadgeRedBorder: Color = Color(color = 0xFFFFC9C9) | ||
| val statusBadgeRedBg: Color = Color(color = 0xFFFEF2F2) | ||
| val statusBadgeOrangeBorder: Color = Color(color = 0xFFFCD9BD) | ||
| val statusBadgeOrangeBg: Color = Color(color = 0xFFFFF8F1) | ||
| val statusBadgeOrangeText: Color = Color(color = 0xFFB23300) |
There was a problem hiding this comment.
❓ @RishikaSG-28 @david-livefront I couldn't find {color}-{shade} names in our component library to stay consistent with the other primitive color names. Suggestions on how to proceed here? Will we have more primitives? Does it makes sense to continue using {color}-{shade} naming convention?
There was a problem hiding this comment.
Hey @SaintPatrck thanks for bringing this up! We're going to bring in the primitives from the Byte design system so everything stays consistent. I'll get those added to the component library and share it with you soon!
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #6818 +/- ##
==========================================
+ Coverage 85.44% 85.53% +0.08%
==========================================
Files 875 835 -40
Lines 60570 59156 -1414
Branches 8642 8615 -27
==========================================
- Hits 51757 50599 -1158
+ Misses 5833 5587 -246
+ Partials 2980 2970 -10
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
Great job! No new security vulnerabilities introduced in this pull request |
1ec1096 to
a6452ec
Compare
| } | ||
|
|
||
| private fun SubscriptionStatusJson.toPremiumSubscriptionStatus(): PremiumSubscriptionStatus = | ||
| when (this) { |
There was a problem hiding this comment.
I think you missed "paused" case seen here: https://github.com/bitwarden/server/blob/main/src/Billing/Constants/StripeSubscriptionStatus.cs
There was a problem hiding this comment.
@andrebispo5 do you know what status badge should that map to?
There was a problem hiding this comment.
@RishikaSG-28 I don't see "PAUSED" represented in Figma. Should we use the warning (orange) badge colors? Is "Paused" the desired text?
There was a problem hiding this comment.
@SaintPatrck Yes, orange/warning for Paused! Since the user is choosing to pause, "Paused" feels right. It's their action, their control.
Here's what I am thinking about the badge colors across all states:
Green (success) — subscription is running, everything's good
Orange (warning) — needs attention or action, but not terminated
Red (error) — subscription is ended or has a critical issue
| val quantity: Long, | ||
|
|
||
| @SerialName("cost") | ||
| val cost: Double, |
There was a problem hiding this comment.
Currency should be stored in a BigDecimal
cbf4111 to
0cb83ba
Compare
There was a problem hiding this comment.
📓 @andrebispo5 FYI, here are the new badge color primitive names.
0a18f8a to
049e150
Compare
| val subscription = (result as SubscriptionResult.Success).subscription | ||
| assertEquals(PremiumSubscriptionStatus.ACTIVE, subscription.status) | ||
| assertEquals(BigDecimal("19.80"), subscription.seatsCost) | ||
| assertEquals(BigDecimal("19.80"), subscription.nextChargeTotal) |
There was a problem hiding this comment.
Can we just compare the entire result
| val cadence: CadenceTypeJson, | ||
|
|
||
| @SerialName("discount") | ||
| val discount: BitwardenDiscountJson? = null, |
There was a problem hiding this comment.
What's with all the default nulls everywhere?
| * @property storageCost The cost of additional storage, or null if none. | ||
| * @property discountAmount The money value of any applied discount, or null if | ||
| * no discount is present. Percent-off discounts are resolved against the | ||
| * password manager subtotal at mapping time. |
There was a problem hiding this comment.
These extra indents seem odd?
…e component Introduces the subscription retrieval path (network API, service, repository, domain models) so subsequent work can render a premium user's actual billed line items and next-charge details. Adds a reusable `BitwardenStatusBadge` component with success / error / warning variants for displaying subscription status. No user-visible behavior change — consumed by PM-35455.
Rename status badge primitives from ad-hoc `statusBadge*` names to the
Figma-canonical `{color}{number}` scheme and add dark-variant primitives
so the dark theme stops reusing the soft light palette. Adds a dark
preview to verify the separation visually.
Double loses precision on currency arithmetic; switch cost, estimatedTax, and discount value to BigDecimal with a JSON-number-preserving serializer.
Stripe (and the server's StripeConstants.SubscriptionStatus) emits 'paused' as a valid subscription status; deserialization would otherwise throw on that value.
Fixed height 24dp, 12dp corner radius, and 4dp vertical inner padding match the Status Badges component's published dimensions
Defaults on nullable serialized fields are redundant given the network Json config (explicitNulls = false), so they obscured intent without adding value. The repository test now compares the entire result to match the convention used elsewhere in the file, and the SubscriptionInfo KDoc continuation indents are normalized to standard Kotlin formatting.
049e150 to
e50bdb6
Compare

🎟️ Tracking
https://bitwarden.atlassian.net/browse/PM-35454
📔 Objective
Introduces the subscription retrieval path (network API, service, repository, domain models) so premium users can soon see their actual billed rate, line items, and next-charge details, plus a reusable
BitwardenStatusBadgefor rendering subscription status.No user-visible behavior change in this PR — the new capabilities are consumed by the follow-up PM-35455, which stacks on top of this branch.
cadence,estimatedTax,discountAmount, andnextChargeTotalare surfaced throughSubscriptionInfo. Currency fields useBigDecimal(notDouble) to avoid floating-point precision loss on cart math; aBigDecimalSerializerpreserves the server's unquoted JSON number contract.discountAmountandnextChargeTotalare resolved at mapping time since the server does not expose a precomputed total.BitwardenStatusBadgeships with success / error / warning color variants that differentiate light and dark themes per the Figma spec; consumers supply the label and variant.📸 Screenshots