mirror of
https://github.com/bitwarden/android.git
synced 2026-11-09 10:34:18 -06:00
PM-46261: feat: Update to use the new premium subscription API
This commit is contained in:
+12
-34
@@ -1,18 +1,13 @@
|
||||
package com.x8bit.bitwarden.data.billing.repository.util
|
||||
|
||||
import com.bitwarden.network.model.BitwardenDiscountJson
|
||||
import com.bitwarden.network.model.BitwardenSubscriptionResponseJson
|
||||
import com.bitwarden.network.model.CadenceTypeJson
|
||||
import com.bitwarden.network.model.CartItemJson
|
||||
import com.bitwarden.network.model.DiscountTypeJson
|
||||
import com.bitwarden.network.model.InvoicePreviewItemJson
|
||||
import com.bitwarden.network.model.SubscriptionStatusJson
|
||||
import com.x8bit.bitwarden.data.billing.repository.model.PlanCadence
|
||||
import com.x8bit.bitwarden.data.billing.repository.model.PremiumSubscriptionStatus
|
||||
import com.x8bit.bitwarden.data.billing.repository.model.SubscriptionInfo
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
|
||||
private const val MONEY_SCALE: Int = 2
|
||||
|
||||
/**
|
||||
* Maps a [BitwardenSubscriptionResponseJson] into a [SubscriptionInfo] domain
|
||||
@@ -30,29 +25,22 @@ private const val MONEY_SCALE: Int = 2
|
||||
* does not expose a precomputed total.
|
||||
*/
|
||||
fun BitwardenSubscriptionResponseJson.toSubscriptionInfo(): SubscriptionInfo {
|
||||
val seatsCost = cart.passwordManager.seats.lineTotal()
|
||||
val storageCost = cart.passwordManager.additionalStorage?.lineTotal()
|
||||
val subtotal = seatsCost + (storageCost ?: BigDecimal.ZERO)
|
||||
val cartDiscount = cart.discount?.toDiscountAmount(baseAmount = subtotal)
|
||||
val seatsDiscount = cart.passwordManager.seats.discount
|
||||
?.toDiscountAmount(baseAmount = seatsCost)
|
||||
val discountAmount = listOfNotNull(cartDiscount, seatsDiscount)
|
||||
.takeIf { it.isNotEmpty() }
|
||||
?.reduce(BigDecimal::add)
|
||||
val estimatedTax = cart.estimatedTax
|
||||
val nextChargeTotal = subtotal -
|
||||
(discountAmount ?: BigDecimal.ZERO) +
|
||||
estimatedTax
|
||||
val seatsCost = invoicePreview.passwordManager.seats?.lineTotal() ?: BigDecimal.ZERO
|
||||
val storageCost = invoicePreview.passwordManager.additionalStorage?.lineTotal()
|
||||
val discountAmount = invoicePreview
|
||||
.discounts
|
||||
?.map { it.amount }
|
||||
?.reduce(operation = BigDecimal::add)
|
||||
|
||||
return SubscriptionInfo(
|
||||
status = toPremiumSubscriptionStatus(),
|
||||
cadence = cart.cadence.toPlanCadence(),
|
||||
cadence = invoicePreview.cadence.toPlanCadence(),
|
||||
seatsCost = seatsCost,
|
||||
storageCost = storageCost,
|
||||
discountAmount = discountAmount,
|
||||
estimatedTax = estimatedTax,
|
||||
nextChargeTotal = nextChargeTotal,
|
||||
nextCharge = nextCharge,
|
||||
estimatedTax = invoicePreview.estimatedTax,
|
||||
nextChargeTotal = invoicePreview.total,
|
||||
nextCharge = invoicePreview.nextPaymentAttempt,
|
||||
cancelAt = cancelAt,
|
||||
canceledDate = canceled,
|
||||
suspensionDate = suspension,
|
||||
@@ -77,21 +65,11 @@ private fun BitwardenSubscriptionResponseJson.toPremiumSubscriptionStatus():
|
||||
SubscriptionStatusJson.INCOMPLETE -> PremiumSubscriptionStatus.UPDATE_PAYMENT
|
||||
SubscriptionStatusJson.UNPAID -> PremiumSubscriptionStatus.UNPAID
|
||||
SubscriptionStatusJson.PAST_DUE -> PremiumSubscriptionStatus.PAST_DUE
|
||||
SubscriptionStatusJson.PAUSED -> PremiumSubscriptionStatus.PAUSED
|
||||
}
|
||||
|
||||
private fun CartItemJson.lineTotal(): BigDecimal = cost.multiply(quantity.toBigDecimal())
|
||||
private fun InvoicePreviewItemJson.lineTotal(): BigDecimal = cost.multiply(quantity.toBigDecimal())
|
||||
|
||||
private fun CadenceTypeJson.toPlanCadence(): PlanCadence = when (this) {
|
||||
CadenceTypeJson.ANNUALLY -> PlanCadence.ANNUALLY
|
||||
CadenceTypeJson.MONTHLY -> PlanCadence.MONTHLY
|
||||
}
|
||||
|
||||
private fun BitwardenDiscountJson.toDiscountAmount(baseAmount: BigDecimal): BigDecimal =
|
||||
when (type) {
|
||||
DiscountTypeJson.AMOUNT_OFF -> value
|
||||
DiscountTypeJson.PERCENT_OFF -> {
|
||||
val percentage = if (value < BigDecimal.ONE) value else value.movePointLeft(2)
|
||||
baseAmount.multiply(percentage).setScale(MONEY_SCALE, RoundingMode.HALF_UP)
|
||||
}
|
||||
}
|
||||
|
||||
+12
-11
@@ -5,11 +5,11 @@ import com.bitwarden.core.data.util.asFailure
|
||||
import com.bitwarden.core.data.util.asSuccess
|
||||
import com.bitwarden.network.model.BitwardenSubscriptionResponseJson
|
||||
import com.bitwarden.network.model.CadenceTypeJson
|
||||
import com.bitwarden.network.model.CartItemJson
|
||||
import com.bitwarden.network.model.CartJson
|
||||
import com.bitwarden.network.model.CheckoutSessionResponseJson
|
||||
import com.bitwarden.network.model.GetSubscriptionResponse
|
||||
import com.bitwarden.network.model.PasswordManagerCartItemsJson
|
||||
import com.bitwarden.network.model.InvoicePreviewItemJson
|
||||
import com.bitwarden.network.model.InvoicePreviewJson
|
||||
import com.bitwarden.network.model.PasswordManagerInvoicePreviewJson
|
||||
import com.bitwarden.network.model.PortalUrlResponseJson
|
||||
import com.bitwarden.network.model.PremiumPlanResponseJson
|
||||
import com.bitwarden.network.model.SubscriptionStatusJson
|
||||
@@ -308,25 +308,26 @@ private const val ANNUAL_PRICE = 19.99
|
||||
|
||||
private val ACTIVE_SUBSCRIPTION_RESPONSE = BitwardenSubscriptionResponseJson(
|
||||
status = SubscriptionStatusJson.ACTIVE,
|
||||
cart = CartJson(
|
||||
passwordManager = PasswordManagerCartItemsJson(
|
||||
seats = CartItemJson(
|
||||
translationKey = "premiumMembership",
|
||||
invoicePreview = InvoicePreviewJson(
|
||||
passwordManager = PasswordManagerInvoicePreviewJson(
|
||||
seats = InvoicePreviewItemJson(
|
||||
quantity = 1,
|
||||
cost = BigDecimal("19.80"),
|
||||
discount = null,
|
||||
discounts = null,
|
||||
),
|
||||
additionalStorage = null,
|
||||
),
|
||||
secretsManager = null,
|
||||
cadence = CadenceTypeJson.ANNUALLY,
|
||||
discount = null,
|
||||
discounts = null,
|
||||
startingBalance = null,
|
||||
estimatedTax = BigDecimal.ZERO,
|
||||
total = BigDecimal("19.80"),
|
||||
amountDue = BigDecimal("19.80"),
|
||||
nextPaymentAttempt = null,
|
||||
),
|
||||
storage = null,
|
||||
cancelAt = null,
|
||||
canceled = null,
|
||||
nextCharge = null,
|
||||
suspension = null,
|
||||
gracePeriod = null,
|
||||
)
|
||||
|
||||
+82
-147
@@ -1,12 +1,12 @@
|
||||
package com.x8bit.bitwarden.data.billing.repository.util
|
||||
|
||||
import com.bitwarden.network.model.BitwardenDiscountJson
|
||||
import com.bitwarden.network.model.BitwardenSubscriptionResponseJson
|
||||
import com.bitwarden.network.model.CadenceTypeJson
|
||||
import com.bitwarden.network.model.CartItemJson
|
||||
import com.bitwarden.network.model.CartJson
|
||||
import com.bitwarden.network.model.DiscountTypeJson
|
||||
import com.bitwarden.network.model.PasswordManagerCartItemsJson
|
||||
import com.bitwarden.network.model.InvoicePreviewDiscountJson
|
||||
import com.bitwarden.network.model.InvoicePreviewItemJson
|
||||
import com.bitwarden.network.model.InvoicePreviewJson
|
||||
import com.bitwarden.network.model.PasswordManagerInvoicePreviewJson
|
||||
import com.bitwarden.network.model.StorageJson
|
||||
import com.bitwarden.network.model.SubscriptionStatusJson
|
||||
import com.x8bit.bitwarden.data.billing.repository.model.PlanCadence
|
||||
@@ -61,14 +61,6 @@ class BitwardenSubscriptionResponseJsonExtensionsTest {
|
||||
assertEquals(PremiumSubscriptionStatus.PAST_DUE, info.status)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toSubscriptionInfo maps PAUSED to PAUSED`() {
|
||||
val info = buildResponse(
|
||||
status = SubscriptionStatusJson.PAUSED,
|
||||
).toSubscriptionInfo()
|
||||
assertEquals(PremiumSubscriptionStatus.PAUSED, info.status)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toSubscriptionInfo maps ACTIVE with cancelAt to PENDING_CANCELLATION`() {
|
||||
val info = buildResponse(
|
||||
@@ -119,6 +111,12 @@ class BitwardenSubscriptionResponseJsonExtensionsTest {
|
||||
assertNull(info.storageCost)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toSubscriptionInfo seatsCost is zero when seats are not present`() {
|
||||
val info = buildResponse(seatsCost = null).toSubscriptionInfo()
|
||||
assertEquals(BigDecimal.ZERO, info.seatsCost)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toSubscriptionInfo maps storageCost from additionalStorage when present`() {
|
||||
val info = buildResponse(
|
||||
@@ -147,105 +145,57 @@ class BitwardenSubscriptionResponseJsonExtensionsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toSubscriptionInfo discountAmount is null when no discount`() {
|
||||
val info = buildResponse(discount = null).toSubscriptionInfo()
|
||||
fun `toSubscriptionInfo discountAmount is null when no discounts`() {
|
||||
val info = buildResponse(discounts = null).toSubscriptionInfo()
|
||||
assertNull(info.discountAmount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toSubscriptionInfo discountAmount for AMOUNT_OFF passes value through`() {
|
||||
fun `toSubscriptionInfo discountAmount uses the applied amount rather than the value`() {
|
||||
val info = buildResponse(
|
||||
discount = BitwardenDiscountJson(
|
||||
type = DiscountTypeJson.AMOUNT_OFF,
|
||||
value = BigDecimal("2.10"),
|
||||
),
|
||||
).toSubscriptionInfo()
|
||||
assertEquals(BigDecimal("2.10"), info.discountAmount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toSubscriptionInfo discountAmount for PERCENT_OFF applies to PM subtotal`() {
|
||||
// seats 20 + storage 10 = 30 subtotal, 15% = 4.50
|
||||
val info = buildResponse(
|
||||
seatsCost = BigDecimal("20.00"),
|
||||
storageCost = BigDecimal("10.00"),
|
||||
discount = BitwardenDiscountJson(
|
||||
type = DiscountTypeJson.PERCENT_OFF,
|
||||
value = BigDecimal("15.00"),
|
||||
discounts = listOf(
|
||||
buildDiscount(
|
||||
type = DiscountTypeJson.PERCENT_OFF,
|
||||
value = BigDecimal("15"),
|
||||
amount = BigDecimal("4.50"),
|
||||
),
|
||||
),
|
||||
).toSubscriptionInfo()
|
||||
assertEquals(BigDecimal("4.50"), info.discountAmount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toSubscriptionInfo PERCENT_OFF discount treats value below one as a decimal fraction`() {
|
||||
fun `toSubscriptionInfo discountAmount sums the amounts of all invoice discounts`() {
|
||||
val info = buildResponse(
|
||||
seatsCost = BigDecimal("20.00"),
|
||||
discount = BitwardenDiscountJson(
|
||||
type = DiscountTypeJson.PERCENT_OFF,
|
||||
value = BigDecimal("0.5"),
|
||||
discounts = listOf(
|
||||
buildDiscount(
|
||||
type = DiscountTypeJson.AMOUNT_OFF,
|
||||
value = BigDecimal("2.00"),
|
||||
amount = BigDecimal("2.00"),
|
||||
),
|
||||
buildDiscount(
|
||||
type = DiscountTypeJson.PERCENT_OFF,
|
||||
value = BigDecimal("10"),
|
||||
amount = BigDecimal("1.98"),
|
||||
),
|
||||
),
|
||||
).toSubscriptionInfo()
|
||||
assertEquals(BigDecimal("10.00"), info.discountAmount)
|
||||
assertEquals(BigDecimal("3.98"), info.discountAmount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toSubscriptionInfo applies PM seats item-level discount`() {
|
||||
fun `toSubscriptionInfo discountAmount ignores item-level discounts`() {
|
||||
val info = buildResponse(
|
||||
seatsCost = BigDecimal("19.80"),
|
||||
seatsDiscount = BitwardenDiscountJson(
|
||||
type = DiscountTypeJson.AMOUNT_OFF,
|
||||
value = BigDecimal("5.00"),
|
||||
),
|
||||
).toSubscriptionInfo()
|
||||
assertEquals(BigDecimal("5.00"), info.discountAmount)
|
||||
assertEquals(BigDecimal("14.80"), info.nextChargeTotal)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toSubscriptionInfo PERCENT_OFF seats item discount applies to seats line total`() {
|
||||
val info = buildResponse(
|
||||
seatsCost = BigDecimal("19.80"),
|
||||
seatsQuantity = 2,
|
||||
seatsDiscount = BitwardenDiscountJson(
|
||||
type = DiscountTypeJson.PERCENT_OFF,
|
||||
value = BigDecimal("10.00"),
|
||||
),
|
||||
).toSubscriptionInfo()
|
||||
assertEquals(BigDecimal("3.96"), info.discountAmount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toSubscriptionInfo combines cart-level and PM seats item discounts`() {
|
||||
val info = buildResponse(
|
||||
seatsCost = BigDecimal("19.80"),
|
||||
seatsDiscount = BitwardenDiscountJson(
|
||||
type = DiscountTypeJson.AMOUNT_OFF,
|
||||
value = BigDecimal("3.00"),
|
||||
),
|
||||
discount = BitwardenDiscountJson(
|
||||
type = DiscountTypeJson.AMOUNT_OFF,
|
||||
value = BigDecimal("2.00"),
|
||||
),
|
||||
estimatedTax = BigDecimal("1.00"),
|
||||
).toSubscriptionInfo()
|
||||
assertEquals(BigDecimal("5.00"), info.discountAmount)
|
||||
assertEquals(BigDecimal("15.80"), info.nextChargeTotal)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toSubscriptionInfo ignores additionalStorage item-level discount`() {
|
||||
val info = buildResponse(
|
||||
seatsCost = BigDecimal("19.80"),
|
||||
storageCost = BigDecimal("4"),
|
||||
storageQuantity = 3,
|
||||
storageDiscount = BitwardenDiscountJson(
|
||||
type = DiscountTypeJson.AMOUNT_OFF,
|
||||
value = BigDecimal("5.00"),
|
||||
seatsDiscounts = listOf(
|
||||
buildDiscount(
|
||||
type = DiscountTypeJson.AMOUNT_OFF,
|
||||
value = BigDecimal("5.00"),
|
||||
amount = BigDecimal("5.00"),
|
||||
),
|
||||
),
|
||||
discounts = null,
|
||||
).toSubscriptionInfo()
|
||||
assertNull(info.discountAmount)
|
||||
assertEquals(BigDecimal("31.80"), info.nextChargeTotal)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -255,52 +205,23 @@ class BitwardenSubscriptionResponseJsonExtensionsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toSubscriptionInfo nextChargeTotal sums line items, subtracts discount, adds tax`() {
|
||||
// Matches the design example: 19.80 + 24.00 - 2.10 + 3.85 = 45.55
|
||||
fun `toSubscriptionInfo nextChargeTotal passes the invoice total through`() {
|
||||
val info = buildResponse(
|
||||
seatsCost = BigDecimal("19.80"),
|
||||
storageCost = BigDecimal("24.00"),
|
||||
discount = BitwardenDiscountJson(
|
||||
type = DiscountTypeJson.AMOUNT_OFF,
|
||||
value = BigDecimal("2.10"),
|
||||
discounts = listOf(
|
||||
buildDiscount(
|
||||
type = DiscountTypeJson.AMOUNT_OFF,
|
||||
value = BigDecimal("2.10"),
|
||||
amount = BigDecimal("2.10"),
|
||||
),
|
||||
),
|
||||
estimatedTax = BigDecimal("3.85"),
|
||||
total = BigDecimal("45.55"),
|
||||
).toSubscriptionInfo()
|
||||
assertEquals(BigDecimal("45.55"), info.nextChargeTotal)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toSubscriptionInfo nextChargeTotal multiplies storage line by quantity`() {
|
||||
val info = buildResponse(
|
||||
seatsCost = BigDecimal("19.80"),
|
||||
storageCost = BigDecimal("4"),
|
||||
storageQuantity = 3,
|
||||
estimatedTax = BigDecimal("2.04"),
|
||||
).toSubscriptionInfo()
|
||||
assertEquals(BigDecimal("33.84"), info.nextChargeTotal)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toSubscriptionInfo PERCENT_OFF discount applies to quantity-multiplied subtotal`() {
|
||||
val info = buildResponse(
|
||||
seatsCost = BigDecimal("19.80"),
|
||||
storageCost = BigDecimal("4"),
|
||||
storageQuantity = 3,
|
||||
discount = BitwardenDiscountJson(
|
||||
type = DiscountTypeJson.PERCENT_OFF,
|
||||
value = BigDecimal("10.00"),
|
||||
),
|
||||
).toSubscriptionInfo()
|
||||
assertEquals(BigDecimal("3.18"), info.discountAmount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toSubscriptionInfo nextChargeTotal with minimal cart equals seatsCost`() {
|
||||
// User-provided JSON: 19.80 + 0 - 0 + 0 = 19.80
|
||||
val info = buildResponse(seatsCost = BigDecimal("19.80")).toSubscriptionInfo()
|
||||
assertEquals(BigDecimal("19.80"), info.nextChargeTotal)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toSubscriptionInfo maps timestamps and gracePeriod`() {
|
||||
val canceled = Instant.parse("2026-01-01T00:00:00Z")
|
||||
@@ -308,7 +229,7 @@ class BitwardenSubscriptionResponseJsonExtensionsTest {
|
||||
val suspension = Instant.parse("2026-05-02T00:00:00Z")
|
||||
val info = buildResponse(
|
||||
canceled = canceled,
|
||||
nextCharge = next,
|
||||
nextPaymentAttempt = next,
|
||||
suspension = suspension,
|
||||
gracePeriod = 14,
|
||||
).toSubscriptionInfo()
|
||||
@@ -328,52 +249,66 @@ class BitwardenSubscriptionResponseJsonExtensionsTest {
|
||||
assertNull(info.gracePeriodDays)
|
||||
}
|
||||
|
||||
private fun buildDiscount(
|
||||
type: DiscountTypeJson,
|
||||
value: BigDecimal,
|
||||
amount: BigDecimal,
|
||||
): InvoicePreviewDiscountJson = InvoicePreviewDiscountJson(
|
||||
type = type,
|
||||
value = value,
|
||||
amount = amount,
|
||||
label = null,
|
||||
durationInMonths = null,
|
||||
)
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
private fun buildResponse(
|
||||
status: SubscriptionStatusJson = SubscriptionStatusJson.ACTIVE,
|
||||
cadence: CadenceTypeJson = CadenceTypeJson.ANNUALLY,
|
||||
seatsCost: BigDecimal = BigDecimal("19.80"),
|
||||
seatsCost: BigDecimal? = BigDecimal("19.80"),
|
||||
seatsQuantity: Long = 1,
|
||||
seatsDiscount: BitwardenDiscountJson? = null,
|
||||
seatsDiscounts: List<InvoicePreviewDiscountJson>? = null,
|
||||
storageCost: BigDecimal? = null,
|
||||
storageQuantity: Long = 1,
|
||||
storageDiscount: BitwardenDiscountJson? = null,
|
||||
discount: BitwardenDiscountJson? = null,
|
||||
discounts: List<InvoicePreviewDiscountJson>? = null,
|
||||
estimatedTax: BigDecimal = BigDecimal.ZERO,
|
||||
total: BigDecimal = BigDecimal("19.80"),
|
||||
storage: StorageJson? = null,
|
||||
cancelAt: Instant? = null,
|
||||
canceled: Instant? = null,
|
||||
nextCharge: Instant? = null,
|
||||
nextPaymentAttempt: Instant? = null,
|
||||
suspension: Instant? = null,
|
||||
gracePeriod: Int? = null,
|
||||
): BitwardenSubscriptionResponseJson = BitwardenSubscriptionResponseJson(
|
||||
status = status,
|
||||
cart = CartJson(
|
||||
passwordManager = PasswordManagerCartItemsJson(
|
||||
seats = CartItemJson(
|
||||
translationKey = "premiumMembership",
|
||||
quantity = seatsQuantity,
|
||||
cost = seatsCost,
|
||||
discount = seatsDiscount,
|
||||
),
|
||||
invoicePreview = InvoicePreviewJson(
|
||||
passwordManager = PasswordManagerInvoicePreviewJson(
|
||||
seats = seatsCost?.let {
|
||||
InvoicePreviewItemJson(
|
||||
quantity = seatsQuantity,
|
||||
cost = it,
|
||||
discounts = seatsDiscounts,
|
||||
)
|
||||
},
|
||||
additionalStorage = storageCost?.let {
|
||||
CartItemJson(
|
||||
translationKey = "additionalStorage",
|
||||
InvoicePreviewItemJson(
|
||||
quantity = storageQuantity,
|
||||
cost = it,
|
||||
discount = storageDiscount,
|
||||
discounts = null,
|
||||
)
|
||||
},
|
||||
),
|
||||
secretsManager = null,
|
||||
cadence = cadence,
|
||||
discount = discount,
|
||||
discounts = discounts,
|
||||
startingBalance = null,
|
||||
estimatedTax = estimatedTax,
|
||||
total = total,
|
||||
amountDue = total,
|
||||
nextPaymentAttempt = nextPaymentAttempt,
|
||||
),
|
||||
storage = storage,
|
||||
cancelAt = cancelAt,
|
||||
canceled = canceled,
|
||||
nextCharge = nextCharge,
|
||||
suspension = suspension,
|
||||
gracePeriod = gracePeriod,
|
||||
)
|
||||
|
||||
@@ -38,6 +38,6 @@ internal interface AuthenticatedBillingApi {
|
||||
/**
|
||||
* Retrieves the user's premium subscription details.
|
||||
*/
|
||||
@GET("/account/billing/vnext/subscription")
|
||||
@GET("/account/billing/subscription/preview")
|
||||
suspend fun getSubscription(): NetworkResult<BitwardenSubscriptionResponseJson>
|
||||
}
|
||||
|
||||
+56
-55
@@ -10,11 +10,10 @@ import java.time.Instant
|
||||
* Response object returned when retrieving the user's premium subscription details.
|
||||
*
|
||||
* @property status The current status of the subscription.
|
||||
* @property cart The cart details of the subscription.
|
||||
* @property invoicePreview The invoice preview details of the subscription.
|
||||
* @property storage The storage usage details, if available.
|
||||
* @property cancelAt The date the subscription is scheduled to cancel, if applicable.
|
||||
* @property canceled The date the subscription was canceled, if applicable.
|
||||
* @property nextCharge The date of the next charge, if applicable.
|
||||
* @property suspension The date the subscription was suspended, if applicable.
|
||||
* @property gracePeriod The grace period in days, if applicable.
|
||||
*/
|
||||
@@ -23,8 +22,8 @@ data class BitwardenSubscriptionResponseJson(
|
||||
@SerialName("status")
|
||||
val status: SubscriptionStatusJson,
|
||||
|
||||
@SerialName("cart")
|
||||
val cart: CartJson,
|
||||
@SerialName("invoicePreview")
|
||||
val invoicePreview: InvoicePreviewJson,
|
||||
|
||||
@SerialName("storage")
|
||||
val storage: StorageJson?,
|
||||
@@ -37,10 +36,6 @@ data class BitwardenSubscriptionResponseJson(
|
||||
@SerialName("canceled")
|
||||
val canceled: Instant?,
|
||||
|
||||
@Contextual
|
||||
@SerialName("nextCharge")
|
||||
val nextCharge: Instant?,
|
||||
|
||||
@Contextual
|
||||
@SerialName("suspension")
|
||||
val suspension: Instant?,
|
||||
@@ -74,83 +69,76 @@ enum class SubscriptionStatusJson {
|
||||
|
||||
@SerialName("trialing")
|
||||
TRIALING,
|
||||
|
||||
@SerialName("paused")
|
||||
PAUSED,
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the cart details of a subscription.
|
||||
* Represents the invoice preview details of a subscription.
|
||||
*
|
||||
* @property passwordManager The password manager cart items.
|
||||
* @property secretsManager The secrets manager cart items, if applicable.
|
||||
* @property passwordManager The password manager invoice preview items.
|
||||
* @property cadence The billing cadence of the subscription.
|
||||
* @property discount The discount applied to the cart, if applicable.
|
||||
* @property discounts The discounts applied to the invoice, if applicable.
|
||||
* @property startingBalance The starting balance of the invoice, if applicable.
|
||||
* @property estimatedTax The estimated tax amount.
|
||||
* @property total The total amount of the invoice.
|
||||
* @property amountDue The amount due on the invoice.
|
||||
* @property nextPaymentAttempt The billing-period boundary of the subscription, if applicable.
|
||||
*/
|
||||
@Serializable
|
||||
data class CartJson(
|
||||
data class InvoicePreviewJson(
|
||||
@SerialName("passwordManager")
|
||||
val passwordManager: PasswordManagerCartItemsJson,
|
||||
|
||||
@SerialName("secretsManager")
|
||||
val secretsManager: SecretsManagerCartItemsJson?,
|
||||
val passwordManager: PasswordManagerInvoicePreviewJson,
|
||||
|
||||
@SerialName("cadence")
|
||||
val cadence: CadenceTypeJson,
|
||||
|
||||
@SerialName("discount")
|
||||
val discount: BitwardenDiscountJson?,
|
||||
@SerialName("discounts")
|
||||
val discounts: List<InvoicePreviewDiscountJson>?,
|
||||
|
||||
@Contextual
|
||||
@SerialName("startingBalance")
|
||||
val startingBalance: BigDecimal?,
|
||||
|
||||
@Contextual
|
||||
@SerialName("estimatedTax")
|
||||
val estimatedTax: BigDecimal,
|
||||
|
||||
@Contextual
|
||||
@SerialName("total")
|
||||
val total: BigDecimal,
|
||||
|
||||
@Contextual
|
||||
@SerialName("amountDue")
|
||||
val amountDue: BigDecimal,
|
||||
|
||||
@Contextual
|
||||
@SerialName("nextPaymentAttempt")
|
||||
val nextPaymentAttempt: Instant?,
|
||||
)
|
||||
|
||||
/**
|
||||
* Represents the password manager cart items within a subscription.
|
||||
* Represents the password manager invoice preview items within a subscription.
|
||||
*
|
||||
* @property seats The seat pricing details.
|
||||
* @property seats The seat pricing details, if applicable.
|
||||
* @property additionalStorage The additional storage pricing details, if applicable.
|
||||
*/
|
||||
@Serializable
|
||||
data class PasswordManagerCartItemsJson(
|
||||
data class PasswordManagerInvoicePreviewJson(
|
||||
@SerialName("seats")
|
||||
val seats: CartItemJson,
|
||||
val seats: InvoicePreviewItemJson?,
|
||||
|
||||
@SerialName("additionalStorage")
|
||||
val additionalStorage: CartItemJson?,
|
||||
val additionalStorage: InvoicePreviewItemJson?,
|
||||
)
|
||||
|
||||
/**
|
||||
* Represents the secrets manager cart items within a subscription.
|
||||
* Represents a single invoice preview item within a subscription.
|
||||
*
|
||||
* @property seats The seat pricing details.
|
||||
* @property additionalServiceAccounts The additional service accounts pricing details,
|
||||
* if applicable.
|
||||
*/
|
||||
@Serializable
|
||||
data class SecretsManagerCartItemsJson(
|
||||
@SerialName("seats")
|
||||
val seats: CartItemJson,
|
||||
|
||||
@SerialName("additionalServiceAccounts")
|
||||
val additionalServiceAccounts: CartItemJson?,
|
||||
)
|
||||
|
||||
/**
|
||||
* Represents a single cart item within a subscription.
|
||||
*
|
||||
* @property translationKey The translation key for display purposes.
|
||||
* @property quantity The quantity of this item.
|
||||
* @property cost The cost of this item.
|
||||
* @property discount The discount applied to this item, if applicable.
|
||||
* @property discounts The discounts applied to this item, if applicable.
|
||||
*/
|
||||
@Serializable
|
||||
data class CartItemJson(
|
||||
@SerialName("translationKey")
|
||||
val translationKey: String,
|
||||
|
||||
data class InvoicePreviewItemJson(
|
||||
@SerialName("quantity")
|
||||
val quantity: Long,
|
||||
|
||||
@@ -158,24 +146,37 @@ data class CartItemJson(
|
||||
@SerialName("cost")
|
||||
val cost: BigDecimal,
|
||||
|
||||
@SerialName("discount")
|
||||
val discount: BitwardenDiscountJson?,
|
||||
@SerialName("discounts")
|
||||
val discounts: List<InvoicePreviewDiscountJson>?,
|
||||
)
|
||||
|
||||
/**
|
||||
* Represents a discount applied to a subscription or cart item.
|
||||
* Represents a discount applied to an invoice preview or invoice preview item.
|
||||
*
|
||||
* @property type The type of discount.
|
||||
* @property value The discount value.
|
||||
* @property value The discount value, either a whole-number percentage or dollars off.
|
||||
* @property amount The applied discount amount in dollars.
|
||||
* @property label The coupon name, if applicable.
|
||||
* @property durationInMonths The number of months the coupon repeats for, if applicable.
|
||||
*/
|
||||
@Serializable
|
||||
data class BitwardenDiscountJson(
|
||||
data class InvoicePreviewDiscountJson(
|
||||
@SerialName("type")
|
||||
val type: DiscountTypeJson,
|
||||
|
||||
@Contextual
|
||||
@SerialName("value")
|
||||
val value: BigDecimal,
|
||||
|
||||
@Contextual
|
||||
@SerialName("amount")
|
||||
val amount: BigDecimal,
|
||||
|
||||
@SerialName("label")
|
||||
val label: String?,
|
||||
|
||||
@SerialName("durationInMonths")
|
||||
val durationInMonths: Int?,
|
||||
)
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,15 +3,15 @@ package com.bitwarden.network.service
|
||||
import com.bitwarden.core.data.util.asSuccess
|
||||
import com.bitwarden.network.api.AuthenticatedBillingApi
|
||||
import com.bitwarden.network.base.BaseServiceTest
|
||||
import com.bitwarden.network.model.BitwardenDiscountJson
|
||||
import com.bitwarden.network.model.BitwardenSubscriptionResponseJson
|
||||
import com.bitwarden.network.model.CadenceTypeJson
|
||||
import com.bitwarden.network.model.CartItemJson
|
||||
import com.bitwarden.network.model.CartJson
|
||||
import com.bitwarden.network.model.CheckoutSessionResponseJson
|
||||
import com.bitwarden.network.model.DiscountTypeJson
|
||||
import com.bitwarden.network.model.GetSubscriptionResponse
|
||||
import com.bitwarden.network.model.PasswordManagerCartItemsJson
|
||||
import com.bitwarden.network.model.InvoicePreviewDiscountJson
|
||||
import com.bitwarden.network.model.InvoicePreviewItemJson
|
||||
import com.bitwarden.network.model.InvoicePreviewJson
|
||||
import com.bitwarden.network.model.PasswordManagerInvoicePreviewJson
|
||||
import com.bitwarden.network.model.PortalUrlResponseJson
|
||||
import com.bitwarden.network.model.PremiumPlanResponseJson
|
||||
import com.bitwarden.network.model.StorageJson
|
||||
@@ -159,7 +159,7 @@ class BillingServiceTest : BaseServiceTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getSubscription with null storage and discount should parse`() =
|
||||
fun `getSubscription with null storage and discounts should parse`() =
|
||||
runTest {
|
||||
val response = MockResponse()
|
||||
.setBody(SUBSCRIPTION_RESPONSE_MINIMAL_JSON)
|
||||
@@ -276,28 +276,34 @@ private val PREMIUM_PLAN_RESPONSE = PremiumPlanResponseJson(
|
||||
private const val SUBSCRIPTION_RESPONSE_JSON = """
|
||||
{
|
||||
"status": "active",
|
||||
"cart": {
|
||||
"invoicePreview": {
|
||||
"passwordManager": {
|
||||
"seats": {
|
||||
"translationKey": "premiumMembership",
|
||||
"quantity": 1,
|
||||
"cost": 19.80,
|
||||
"discount": null
|
||||
"discounts": null
|
||||
},
|
||||
"additionalStorage": {
|
||||
"translationKey": "additionalStorage",
|
||||
"quantity": 24,
|
||||
"cost": 24.00,
|
||||
"discount": null
|
||||
"discounts": null
|
||||
}
|
||||
},
|
||||
"secretsManager": null,
|
||||
"cadence": "annually",
|
||||
"discount": {
|
||||
"type": "amount-off",
|
||||
"value": 2.10
|
||||
},
|
||||
"estimatedTax": 3.85
|
||||
"discounts": [
|
||||
{
|
||||
"type": "amount-off",
|
||||
"value": 2.10,
|
||||
"amount": 2.10,
|
||||
"label": "Welcome Coupon",
|
||||
"durationInMonths": 12
|
||||
}
|
||||
],
|
||||
"startingBalance": 0,
|
||||
"estimatedTax": 3.85,
|
||||
"total": 45.55,
|
||||
"amountDue": 45.55,
|
||||
"nextPaymentAttempt": "2026-04-02T00:00:00Z"
|
||||
},
|
||||
"storage": {
|
||||
"available": 5,
|
||||
@@ -306,7 +312,6 @@ private const val SUBSCRIPTION_RESPONSE_JSON = """
|
||||
},
|
||||
"cancelAt": null,
|
||||
"canceled": null,
|
||||
"nextCharge": "2026-04-02T00:00:00Z",
|
||||
"suspension": null,
|
||||
"gracePeriod": null
|
||||
}
|
||||
@@ -314,28 +319,34 @@ private const val SUBSCRIPTION_RESPONSE_JSON = """
|
||||
|
||||
private val SUBSCRIPTION_RESPONSE = BitwardenSubscriptionResponseJson(
|
||||
status = SubscriptionStatusJson.ACTIVE,
|
||||
cart = CartJson(
|
||||
passwordManager = PasswordManagerCartItemsJson(
|
||||
seats = CartItemJson(
|
||||
translationKey = "premiumMembership",
|
||||
invoicePreview = InvoicePreviewJson(
|
||||
passwordManager = PasswordManagerInvoicePreviewJson(
|
||||
seats = InvoicePreviewItemJson(
|
||||
quantity = 1,
|
||||
cost = BigDecimal("19.80"),
|
||||
discount = null,
|
||||
discounts = null,
|
||||
),
|
||||
additionalStorage = CartItemJson(
|
||||
translationKey = "additionalStorage",
|
||||
additionalStorage = InvoicePreviewItemJson(
|
||||
quantity = 24,
|
||||
cost = BigDecimal("24.00"),
|
||||
discount = null,
|
||||
discounts = null,
|
||||
),
|
||||
),
|
||||
secretsManager = null,
|
||||
cadence = CadenceTypeJson.ANNUALLY,
|
||||
discount = BitwardenDiscountJson(
|
||||
type = DiscountTypeJson.AMOUNT_OFF,
|
||||
value = BigDecimal("2.10"),
|
||||
discounts = listOf(
|
||||
InvoicePreviewDiscountJson(
|
||||
type = DiscountTypeJson.AMOUNT_OFF,
|
||||
value = BigDecimal("2.10"),
|
||||
amount = BigDecimal("2.10"),
|
||||
label = "Welcome Coupon",
|
||||
durationInMonths = 12,
|
||||
),
|
||||
),
|
||||
startingBalance = BigDecimal("0"),
|
||||
estimatedTax = BigDecimal("3.85"),
|
||||
total = BigDecimal("45.55"),
|
||||
amountDue = BigDecimal("45.55"),
|
||||
nextPaymentAttempt = Instant.parse("2026-04-02T00:00:00Z"),
|
||||
),
|
||||
storage = StorageJson(
|
||||
available = 5,
|
||||
@@ -344,7 +355,6 @@ private val SUBSCRIPTION_RESPONSE = BitwardenSubscriptionResponseJson(
|
||||
),
|
||||
cancelAt = null,
|
||||
canceled = null,
|
||||
nextCharge = Instant.parse("2026-04-02T00:00:00Z"),
|
||||
suspension = null,
|
||||
gracePeriod = null,
|
||||
)
|
||||
@@ -352,25 +362,26 @@ private val SUBSCRIPTION_RESPONSE = BitwardenSubscriptionResponseJson(
|
||||
private const val SUBSCRIPTION_RESPONSE_MONTHLY_JSON = """
|
||||
{
|
||||
"status": "active",
|
||||
"cart": {
|
||||
"invoicePreview": {
|
||||
"passwordManager": {
|
||||
"seats": {
|
||||
"translationKey": "premiumMembership",
|
||||
"quantity": 1,
|
||||
"cost": 1.67,
|
||||
"discount": null
|
||||
"discounts": null
|
||||
},
|
||||
"additionalStorage": null
|
||||
},
|
||||
"secretsManager": null,
|
||||
"cadence": "monthly",
|
||||
"discount": null,
|
||||
"estimatedTax": 0
|
||||
"discounts": null,
|
||||
"startingBalance": null,
|
||||
"estimatedTax": 0,
|
||||
"total": 1.67,
|
||||
"amountDue": 1.67,
|
||||
"nextPaymentAttempt": null
|
||||
},
|
||||
"storage": null,
|
||||
"cancelAt": null,
|
||||
"canceled": null,
|
||||
"nextCharge": null,
|
||||
"suspension": null,
|
||||
"gracePeriod": null
|
||||
}
|
||||
@@ -378,25 +389,26 @@ private const val SUBSCRIPTION_RESPONSE_MONTHLY_JSON = """
|
||||
|
||||
private val SUBSCRIPTION_RESPONSE_MONTHLY = BitwardenSubscriptionResponseJson(
|
||||
status = SubscriptionStatusJson.ACTIVE,
|
||||
cart = CartJson(
|
||||
passwordManager = PasswordManagerCartItemsJson(
|
||||
seats = CartItemJson(
|
||||
translationKey = "premiumMembership",
|
||||
invoicePreview = InvoicePreviewJson(
|
||||
passwordManager = PasswordManagerInvoicePreviewJson(
|
||||
seats = InvoicePreviewItemJson(
|
||||
quantity = 1,
|
||||
cost = BigDecimal("1.67"),
|
||||
discount = null,
|
||||
discounts = null,
|
||||
),
|
||||
additionalStorage = null,
|
||||
),
|
||||
secretsManager = null,
|
||||
cadence = CadenceTypeJson.MONTHLY,
|
||||
discount = null,
|
||||
discounts = null,
|
||||
startingBalance = null,
|
||||
estimatedTax = BigDecimal("0"),
|
||||
total = BigDecimal("1.67"),
|
||||
amountDue = BigDecimal("1.67"),
|
||||
nextPaymentAttempt = null,
|
||||
),
|
||||
storage = null,
|
||||
cancelAt = null,
|
||||
canceled = null,
|
||||
nextCharge = null,
|
||||
suspension = null,
|
||||
gracePeriod = null,
|
||||
)
|
||||
@@ -404,25 +416,26 @@ private val SUBSCRIPTION_RESPONSE_MONTHLY = BitwardenSubscriptionResponseJson(
|
||||
private const val SUBSCRIPTION_RESPONSE_MINIMAL_JSON = """
|
||||
{
|
||||
"status": "active",
|
||||
"cart": {
|
||||
"invoicePreview": {
|
||||
"passwordManager": {
|
||||
"seats": {
|
||||
"translationKey": "premiumMembership",
|
||||
"quantity": 1,
|
||||
"cost": 19.80,
|
||||
"discount": null
|
||||
"discounts": null
|
||||
},
|
||||
"additionalStorage": null
|
||||
},
|
||||
"secretsManager": null,
|
||||
"cadence": "annually",
|
||||
"discount": null,
|
||||
"estimatedTax": 0
|
||||
"discounts": null,
|
||||
"startingBalance": null,
|
||||
"estimatedTax": 0,
|
||||
"total": 19.80,
|
||||
"amountDue": 19.80,
|
||||
"nextPaymentAttempt": null
|
||||
},
|
||||
"storage": null,
|
||||
"cancelAt": null,
|
||||
"canceled": null,
|
||||
"nextCharge": null,
|
||||
"suspension": null,
|
||||
"gracePeriod": null
|
||||
}
|
||||
@@ -430,25 +443,26 @@ private const val SUBSCRIPTION_RESPONSE_MINIMAL_JSON = """
|
||||
|
||||
private val SUBSCRIPTION_RESPONSE_MINIMAL = BitwardenSubscriptionResponseJson(
|
||||
status = SubscriptionStatusJson.ACTIVE,
|
||||
cart = CartJson(
|
||||
passwordManager = PasswordManagerCartItemsJson(
|
||||
seats = CartItemJson(
|
||||
translationKey = "premiumMembership",
|
||||
invoicePreview = InvoicePreviewJson(
|
||||
passwordManager = PasswordManagerInvoicePreviewJson(
|
||||
seats = InvoicePreviewItemJson(
|
||||
quantity = 1,
|
||||
cost = BigDecimal("19.80"),
|
||||
discount = null,
|
||||
discounts = null,
|
||||
),
|
||||
additionalStorage = null,
|
||||
),
|
||||
secretsManager = null,
|
||||
cadence = CadenceTypeJson.ANNUALLY,
|
||||
discount = null,
|
||||
discounts = null,
|
||||
startingBalance = null,
|
||||
estimatedTax = BigDecimal("0"),
|
||||
total = BigDecimal("19.80"),
|
||||
amountDue = BigDecimal("19.80"),
|
||||
nextPaymentAttempt = null,
|
||||
),
|
||||
storage = null,
|
||||
cancelAt = null,
|
||||
canceled = null,
|
||||
nextCharge = null,
|
||||
suspension = null,
|
||||
gracePeriod = null,
|
||||
)
|
||||
@@ -456,38 +470,49 @@ private val SUBSCRIPTION_RESPONSE_MINIMAL = BitwardenSubscriptionResponseJson(
|
||||
private const val SUBSCRIPTION_RESPONSE_AMOUNT_OFF_JSON = """
|
||||
{
|
||||
"status": "active",
|
||||
"cart": {
|
||||
"invoicePreview": {
|
||||
"passwordManager": {
|
||||
"seats": {
|
||||
"translationKey": "premiumMembership",
|
||||
"quantity": 1,
|
||||
"cost": 19.80,
|
||||
"discount": null
|
||||
"discounts": null
|
||||
},
|
||||
"additionalStorage": null
|
||||
},
|
||||
"secretsManager": null,
|
||||
"cadence": "annually",
|
||||
"discount": {
|
||||
"type": "amount-off",
|
||||
"value": 5.00
|
||||
},
|
||||
"estimatedTax": 0
|
||||
"discounts": [
|
||||
{
|
||||
"type": "amount-off",
|
||||
"value": 5.00,
|
||||
"amount": 5.00,
|
||||
"label": null,
|
||||
"durationInMonths": null
|
||||
}
|
||||
],
|
||||
"startingBalance": null,
|
||||
"estimatedTax": 0,
|
||||
"total": 19.80,
|
||||
"amountDue": 19.80,
|
||||
"nextPaymentAttempt": null
|
||||
},
|
||||
"storage": null,
|
||||
"cancelAt": null,
|
||||
"canceled": null,
|
||||
"nextCharge": null,
|
||||
"suspension": null,
|
||||
"gracePeriod": null
|
||||
}
|
||||
"""
|
||||
|
||||
private val SUBSCRIPTION_RESPONSE_AMOUNT_OFF = SUBSCRIPTION_RESPONSE_MINIMAL.copy(
|
||||
cart = SUBSCRIPTION_RESPONSE_MINIMAL.cart.copy(
|
||||
discount = BitwardenDiscountJson(
|
||||
type = DiscountTypeJson.AMOUNT_OFF,
|
||||
value = BigDecimal("5.00"),
|
||||
invoicePreview = SUBSCRIPTION_RESPONSE_MINIMAL.invoicePreview.copy(
|
||||
discounts = listOf(
|
||||
InvoicePreviewDiscountJson(
|
||||
type = DiscountTypeJson.AMOUNT_OFF,
|
||||
value = BigDecimal("5.00"),
|
||||
amount = BigDecimal("5.00"),
|
||||
label = null,
|
||||
durationInMonths = null,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -495,38 +520,55 @@ private val SUBSCRIPTION_RESPONSE_AMOUNT_OFF = SUBSCRIPTION_RESPONSE_MINIMAL.cop
|
||||
private const val SUBSCRIPTION_RESPONSE_PERCENT_OFF_JSON = """
|
||||
{
|
||||
"status": "active",
|
||||
"cart": {
|
||||
"invoicePreview": {
|
||||
"passwordManager": {
|
||||
"seats": {
|
||||
"translationKey": "premiumMembership",
|
||||
"quantity": 1,
|
||||
"cost": 19.80,
|
||||
"discount": null
|
||||
"discounts": [
|
||||
{
|
||||
"type": "percent-off",
|
||||
"value": 15,
|
||||
"amount": 2.97,
|
||||
"label": null,
|
||||
"durationInMonths": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"additionalStorage": null
|
||||
},
|
||||
"secretsManager": null,
|
||||
"cadence": "annually",
|
||||
"discount": {
|
||||
"type": "percent-off",
|
||||
"value": 15.00
|
||||
},
|
||||
"estimatedTax": 0
|
||||
"discounts": null,
|
||||
"startingBalance": null,
|
||||
"estimatedTax": 0,
|
||||
"total": 19.80,
|
||||
"amountDue": 19.80,
|
||||
"nextPaymentAttempt": null
|
||||
},
|
||||
"storage": null,
|
||||
"cancelAt": null,
|
||||
"canceled": null,
|
||||
"nextCharge": null,
|
||||
"suspension": null,
|
||||
"gracePeriod": null
|
||||
}
|
||||
"""
|
||||
|
||||
private val SUBSCRIPTION_RESPONSE_PERCENT_OFF = SUBSCRIPTION_RESPONSE_MINIMAL.copy(
|
||||
cart = SUBSCRIPTION_RESPONSE_MINIMAL.cart.copy(
|
||||
discount = BitwardenDiscountJson(
|
||||
type = DiscountTypeJson.PERCENT_OFF,
|
||||
value = BigDecimal("15.00"),
|
||||
invoicePreview = SUBSCRIPTION_RESPONSE_MINIMAL.invoicePreview.copy(
|
||||
passwordManager = SUBSCRIPTION_RESPONSE_MINIMAL.invoicePreview.passwordManager.copy(
|
||||
seats = InvoicePreviewItemJson(
|
||||
quantity = 1,
|
||||
cost = BigDecimal("19.80"),
|
||||
discounts = listOf(
|
||||
InvoicePreviewDiscountJson(
|
||||
type = DiscountTypeJson.PERCENT_OFF,
|
||||
value = BigDecimal("15"),
|
||||
amount = BigDecimal("2.97"),
|
||||
label = null,
|
||||
durationInMonths = null,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -534,25 +576,26 @@ private val SUBSCRIPTION_RESPONSE_PERCENT_OFF = SUBSCRIPTION_RESPONSE_MINIMAL.co
|
||||
private const val SUBSCRIPTION_RESPONSE_UNKNOWN_CADENCE_JSON = """
|
||||
{
|
||||
"status": "active",
|
||||
"cart": {
|
||||
"invoicePreview": {
|
||||
"passwordManager": {
|
||||
"seats": {
|
||||
"translationKey": "premiumMembership",
|
||||
"quantity": 1,
|
||||
"cost": 19.80,
|
||||
"discount": null
|
||||
"discounts": null
|
||||
},
|
||||
"additionalStorage": null
|
||||
},
|
||||
"secretsManager": null,
|
||||
"cadence": "weekly",
|
||||
"discount": null,
|
||||
"estimatedTax": 0
|
||||
"discounts": null,
|
||||
"startingBalance": null,
|
||||
"estimatedTax": 0,
|
||||
"total": 19.80,
|
||||
"amountDue": 19.80,
|
||||
"nextPaymentAttempt": null
|
||||
},
|
||||
"storage": null,
|
||||
"cancelAt": null,
|
||||
"canceled": null,
|
||||
"nextCharge": null,
|
||||
"suspension": null,
|
||||
"gracePeriod": null
|
||||
}
|
||||
@@ -569,30 +612,30 @@ private fun subscriptionResponseJsonForStatus(
|
||||
SubscriptionStatusJson.INCOMPLETE_EXPIRED -> "incomplete_expired"
|
||||
SubscriptionStatusJson.UNPAID -> "unpaid"
|
||||
SubscriptionStatusJson.TRIALING -> "trialing"
|
||||
SubscriptionStatusJson.PAUSED -> "paused"
|
||||
}
|
||||
return """
|
||||
{
|
||||
"status": "$wireValue",
|
||||
"cart": {
|
||||
"invoicePreview": {
|
||||
"passwordManager": {
|
||||
"seats": {
|
||||
"translationKey": "premiumMembership",
|
||||
"quantity": 1,
|
||||
"cost": 19.80,
|
||||
"discount": null
|
||||
"discounts": null
|
||||
},
|
||||
"additionalStorage": null
|
||||
},
|
||||
"secretsManager": null,
|
||||
"cadence": "annually",
|
||||
"discount": null,
|
||||
"estimatedTax": 0
|
||||
"discounts": null,
|
||||
"startingBalance": null,
|
||||
"estimatedTax": 0,
|
||||
"total": 19.80,
|
||||
"amountDue": 19.80,
|
||||
"nextPaymentAttempt": null
|
||||
},
|
||||
"storage": null,
|
||||
"cancelAt": null,
|
||||
"canceled": null,
|
||||
"nextCharge": null,
|
||||
"suspension": null,
|
||||
"gracePeriod": null
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user