From c9326c4c22355175920bf069b1a176a762a4caaa Mon Sep 17 00:00:00 2001 From: Patrick Honkonen Date: Fri, 13 Mar 2026 17:25:20 -0400 Subject: [PATCH 1/5] [PM-33508] feat: Add AuthenticatedBillingApi and BillingService network layer Add billing network infrastructure for premium upgrade and subscription management flows: - AuthenticatedBillingApi with checkout session and portal session endpoints - CheckoutSessionRequestJson/ResponseJson for premium checkout - PortalUrlResponseJson for Stripe customer portal - BillingService interface and implementation (platform hardcoded to "android") - Wire BillingService into BitwardenServiceClient --- .../network/BitwardenServiceClient.kt | 6 ++ .../network/BitwardenServiceClientImpl.kt | 8 ++ .../network/api/AuthenticatedBillingApi.kt | 28 +++++++ .../model/CheckoutSessionRequestJson.kt | 15 ++++ .../model/CheckoutSessionResponseJson.kt | 15 ++++ .../network/model/PortalUrlResponseJson.kt | 15 ++++ .../network/service/BillingService.kt | 20 +++++ .../network/service/BillingServiceImpl.kt | 29 +++++++ .../network/service/BillingServiceTest.kt | 79 +++++++++++++++++++ 9 files changed, 215 insertions(+) create mode 100644 network/src/main/kotlin/com/bitwarden/network/api/AuthenticatedBillingApi.kt create mode 100644 network/src/main/kotlin/com/bitwarden/network/model/CheckoutSessionRequestJson.kt create mode 100644 network/src/main/kotlin/com/bitwarden/network/model/CheckoutSessionResponseJson.kt create mode 100644 network/src/main/kotlin/com/bitwarden/network/model/PortalUrlResponseJson.kt create mode 100644 network/src/main/kotlin/com/bitwarden/network/service/BillingService.kt create mode 100644 network/src/main/kotlin/com/bitwarden/network/service/BillingServiceImpl.kt create mode 100644 network/src/test/kotlin/com/bitwarden/network/service/BillingServiceTest.kt diff --git a/network/src/main/kotlin/com/bitwarden/network/BitwardenServiceClient.kt b/network/src/main/kotlin/com/bitwarden/network/BitwardenServiceClient.kt index 67c105f099f..b5bfab1dd69 100644 --- a/network/src/main/kotlin/com/bitwarden/network/BitwardenServiceClient.kt +++ b/network/src/main/kotlin/com/bitwarden/network/BitwardenServiceClient.kt @@ -9,6 +9,7 @@ import com.bitwarden.network.provider.RefreshTokenProvider import com.bitwarden.network.provider.TokenProvider import com.bitwarden.network.service.AccountsService import com.bitwarden.network.service.AuthRequestsService +import com.bitwarden.network.service.BillingService import com.bitwarden.network.service.CiphersService import com.bitwarden.network.service.ConfigService import com.bitwarden.network.service.DevicesService @@ -70,6 +71,11 @@ interface BitwardenServiceClient { */ val authRequestsService: AuthRequestsService + /** + * Provides access to the Billing service. + */ + val billingService: BillingService + /** * Provides access to the Ciphers service. */ diff --git a/network/src/main/kotlin/com/bitwarden/network/BitwardenServiceClientImpl.kt b/network/src/main/kotlin/com/bitwarden/network/BitwardenServiceClientImpl.kt index b7027ce6409..229cfcd6e8d 100644 --- a/network/src/main/kotlin/com/bitwarden/network/BitwardenServiceClientImpl.kt +++ b/network/src/main/kotlin/com/bitwarden/network/BitwardenServiceClientImpl.kt @@ -15,6 +15,8 @@ import com.bitwarden.network.retrofit.RetrofitsImpl import com.bitwarden.network.service.AccountsServiceImpl import com.bitwarden.network.service.AuthRequestsService import com.bitwarden.network.service.AuthRequestsServiceImpl +import com.bitwarden.network.service.BillingService +import com.bitwarden.network.service.BillingServiceImpl import com.bitwarden.network.service.CiphersService import com.bitwarden.network.service.CiphersServiceImpl import com.bitwarden.network.service.ConfigService @@ -115,6 +117,12 @@ internal class BitwardenServiceClientImpl( ) } + override val billingService: BillingService by lazy { + BillingServiceImpl( + authenticatedBillingApi = retrofits.authenticatedApiRetrofit.create(), + ) + } + override val ciphersService: CiphersService by lazy { CiphersServiceImpl( azureApi = retrofits.createStaticRetrofit().create(), diff --git a/network/src/main/kotlin/com/bitwarden/network/api/AuthenticatedBillingApi.kt b/network/src/main/kotlin/com/bitwarden/network/api/AuthenticatedBillingApi.kt new file mode 100644 index 00000000000..6ffb0342f0f --- /dev/null +++ b/network/src/main/kotlin/com/bitwarden/network/api/AuthenticatedBillingApi.kt @@ -0,0 +1,28 @@ +package com.bitwarden.network.api + +import com.bitwarden.network.model.CheckoutSessionRequestJson +import com.bitwarden.network.model.CheckoutSessionResponseJson +import com.bitwarden.network.model.NetworkResult +import com.bitwarden.network.model.PortalUrlResponseJson +import retrofit2.http.Body +import retrofit2.http.POST + +/** + * Defines raw calls under the /account/billing API with authentication applied. + */ +internal interface AuthenticatedBillingApi { + + /** + * Creates a Stripe checkout session for premium upgrade. + */ + @POST("/account/billing/vnext/premium/checkout") + suspend fun createCheckoutSession( + @Body body: CheckoutSessionRequestJson, + ): NetworkResult + + /** + * Creates a Stripe customer portal session for managing the premium subscription. + */ + @POST("/account/billing/vnext/portal-session") + suspend fun getPortalUrl(): NetworkResult +} diff --git a/network/src/main/kotlin/com/bitwarden/network/model/CheckoutSessionRequestJson.kt b/network/src/main/kotlin/com/bitwarden/network/model/CheckoutSessionRequestJson.kt new file mode 100644 index 00000000000..41a4ff9d7e0 --- /dev/null +++ b/network/src/main/kotlin/com/bitwarden/network/model/CheckoutSessionRequestJson.kt @@ -0,0 +1,15 @@ +package com.bitwarden.network.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Request object for creating a Stripe checkout session for premium upgrade. + * + * @property platform The platform identifier (e.g., "android" or "ios"). + */ +@Serializable +data class CheckoutSessionRequestJson( + @SerialName("platform") + val platform: String, +) diff --git a/network/src/main/kotlin/com/bitwarden/network/model/CheckoutSessionResponseJson.kt b/network/src/main/kotlin/com/bitwarden/network/model/CheckoutSessionResponseJson.kt new file mode 100644 index 00000000000..ff23a4fa248 --- /dev/null +++ b/network/src/main/kotlin/com/bitwarden/network/model/CheckoutSessionResponseJson.kt @@ -0,0 +1,15 @@ +package com.bitwarden.network.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Response object returned when creating a premium checkout session. + * + * @property checkoutUrl The Stripe checkout URL for premium upgrade. + */ +@Serializable +data class CheckoutSessionResponseJson( + @SerialName("checkoutUrl") + val checkoutUrl: String, +) diff --git a/network/src/main/kotlin/com/bitwarden/network/model/PortalUrlResponseJson.kt b/network/src/main/kotlin/com/bitwarden/network/model/PortalUrlResponseJson.kt new file mode 100644 index 00000000000..831ac4b26d9 --- /dev/null +++ b/network/src/main/kotlin/com/bitwarden/network/model/PortalUrlResponseJson.kt @@ -0,0 +1,15 @@ +package com.bitwarden.network.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Response object returned when requesting a Stripe customer portal session. + * + * @property url The Stripe customer portal URL. + */ +@Serializable +data class PortalUrlResponseJson( + @SerialName("url") + val url: String, +) diff --git a/network/src/main/kotlin/com/bitwarden/network/service/BillingService.kt b/network/src/main/kotlin/com/bitwarden/network/service/BillingService.kt new file mode 100644 index 00000000000..1a30e19cdce --- /dev/null +++ b/network/src/main/kotlin/com/bitwarden/network/service/BillingService.kt @@ -0,0 +1,20 @@ +package com.bitwarden.network.service + +import com.bitwarden.network.model.CheckoutSessionResponseJson +import com.bitwarden.network.model.PortalUrlResponseJson + +/** + * Provides an API for interacting with the billing endpoints. + */ +interface BillingService { + + /** + * Creates a Stripe checkout session for premium upgrade. + */ + suspend fun createCheckoutSession(): Result + + /** + * Creates a Stripe customer portal session for managing the premium subscription. + */ + suspend fun getPortalUrl(): Result +} diff --git a/network/src/main/kotlin/com/bitwarden/network/service/BillingServiceImpl.kt b/network/src/main/kotlin/com/bitwarden/network/service/BillingServiceImpl.kt new file mode 100644 index 00000000000..4cf978dbfff --- /dev/null +++ b/network/src/main/kotlin/com/bitwarden/network/service/BillingServiceImpl.kt @@ -0,0 +1,29 @@ +package com.bitwarden.network.service + +import com.bitwarden.network.api.AuthenticatedBillingApi +import com.bitwarden.network.model.CheckoutSessionRequestJson +import com.bitwarden.network.model.CheckoutSessionResponseJson +import com.bitwarden.network.model.PortalUrlResponseJson +import com.bitwarden.network.util.toResult + +/** + * The default implementation of the [BillingService]. + */ +internal class BillingServiceImpl( + private val authenticatedBillingApi: AuthenticatedBillingApi, +) : BillingService { + + override suspend fun createCheckoutSession(): Result = + authenticatedBillingApi + .createCheckoutSession( + body = CheckoutSessionRequestJson(platform = PLATFORM), + ) + .toResult() + + override suspend fun getPortalUrl(): Result = + authenticatedBillingApi + .getPortalUrl() + .toResult() +} + +private const val PLATFORM = "android" diff --git a/network/src/test/kotlin/com/bitwarden/network/service/BillingServiceTest.kt b/network/src/test/kotlin/com/bitwarden/network/service/BillingServiceTest.kt new file mode 100644 index 00000000000..8fa87642ae7 --- /dev/null +++ b/network/src/test/kotlin/com/bitwarden/network/service/BillingServiceTest.kt @@ -0,0 +1,79 @@ +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.CheckoutSessionResponseJson +import com.bitwarden.network.model.PortalUrlResponseJson +import kotlinx.coroutines.test.runTest +import okhttp3.mockwebserver.MockResponse +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import retrofit2.create + +class BillingServiceTest : BaseServiceTest() { + + private val billingApi: AuthenticatedBillingApi = retrofit.create() + private val service = BillingServiceImpl( + authenticatedBillingApi = billingApi, + ) + + @Test + fun `createCheckoutSession when response is Failure should return Failure`() = + runTest { + val response = MockResponse().setResponseCode(400) + server.enqueue(response) + val actual = service.createCheckoutSession() + assertTrue(actual.isFailure) + } + + @Test + fun `createCheckoutSession when response is Success should return Success`() = + runTest { + val response = MockResponse() + .setBody(CHECKOUT_SESSION_RESPONSE_JSON) + .setResponseCode(200) + server.enqueue(response) + val actual = service.createCheckoutSession() + assertEquals(CHECKOUT_SESSION_RESPONSE.asSuccess(), actual) + } + + @Test + fun `getPortalUrl when response is Failure should return Failure`() = runTest { + val response = MockResponse().setResponseCode(400) + server.enqueue(response) + val actual = service.getPortalUrl() + assertTrue(actual.isFailure) + } + + @Test + fun `getPortalUrl when response is Success should return Success`() = runTest { + val response = MockResponse() + .setBody(PORTAL_URL_RESPONSE_JSON) + .setResponseCode(200) + server.enqueue(response) + val actual = service.getPortalUrl() + assertEquals(PORTAL_URL_RESPONSE.asSuccess(), actual) + } +} + +private const val CHECKOUT_SESSION_RESPONSE_JSON = """ +{ + "checkoutUrl": "https://checkout.stripe.com/c/pay/test_session_123" +} +""" + +private val CHECKOUT_SESSION_RESPONSE = CheckoutSessionResponseJson( + checkoutUrl = "https://checkout.stripe.com/c/pay/test_session_123", +) + +private const val PORTAL_URL_RESPONSE_JSON = """ +{ + "url": "https://billing.stripe.com/p/session/test_portal_456" +} +""" + +private val PORTAL_URL_RESPONSE = PortalUrlResponseJson( + url = "https://billing.stripe.com/p/session/test_portal_456", +) From 88873747aeb975c64baf87ee980e4206f6475698 Mon Sep 17 00:00:00 2001 From: Patrick Honkonen Date: Mon, 16 Mar 2026 10:59:21 -0400 Subject: [PATCH 2/5] Add returnUrl request body to portal session endpoint --- .../network/api/AuthenticatedBillingApi.kt | 5 ++++- .../network/model/PortalSessionRequestJson.kt | 15 +++++++++++++++ .../bitwarden/network/service/BillingService.kt | 4 +++- .../network/service/BillingServiceImpl.kt | 9 +++++++-- .../network/service/BillingServiceTest.kt | 6 ++++-- 5 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 network/src/main/kotlin/com/bitwarden/network/model/PortalSessionRequestJson.kt diff --git a/network/src/main/kotlin/com/bitwarden/network/api/AuthenticatedBillingApi.kt b/network/src/main/kotlin/com/bitwarden/network/api/AuthenticatedBillingApi.kt index 6ffb0342f0f..f0ced18f11f 100644 --- a/network/src/main/kotlin/com/bitwarden/network/api/AuthenticatedBillingApi.kt +++ b/network/src/main/kotlin/com/bitwarden/network/api/AuthenticatedBillingApi.kt @@ -3,6 +3,7 @@ package com.bitwarden.network.api import com.bitwarden.network.model.CheckoutSessionRequestJson import com.bitwarden.network.model.CheckoutSessionResponseJson import com.bitwarden.network.model.NetworkResult +import com.bitwarden.network.model.PortalSessionRequestJson import com.bitwarden.network.model.PortalUrlResponseJson import retrofit2.http.Body import retrofit2.http.POST @@ -24,5 +25,7 @@ internal interface AuthenticatedBillingApi { * Creates a Stripe customer portal session for managing the premium subscription. */ @POST("/account/billing/vnext/portal-session") - suspend fun getPortalUrl(): NetworkResult + suspend fun getPortalUrl( + @Body body: PortalSessionRequestJson, + ): NetworkResult } diff --git a/network/src/main/kotlin/com/bitwarden/network/model/PortalSessionRequestJson.kt b/network/src/main/kotlin/com/bitwarden/network/model/PortalSessionRequestJson.kt new file mode 100644 index 00000000000..22137301980 --- /dev/null +++ b/network/src/main/kotlin/com/bitwarden/network/model/PortalSessionRequestJson.kt @@ -0,0 +1,15 @@ +package com.bitwarden.network.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Request object for creating a Stripe customer portal session. + * + * @property returnUrl The URL to redirect the user to after visiting the portal. + */ +@Serializable +data class PortalSessionRequestJson( + @SerialName("returnUrl") + val returnUrl: String, +) diff --git a/network/src/main/kotlin/com/bitwarden/network/service/BillingService.kt b/network/src/main/kotlin/com/bitwarden/network/service/BillingService.kt index 1a30e19cdce..c438cc42c07 100644 --- a/network/src/main/kotlin/com/bitwarden/network/service/BillingService.kt +++ b/network/src/main/kotlin/com/bitwarden/network/service/BillingService.kt @@ -15,6 +15,8 @@ interface BillingService { /** * Creates a Stripe customer portal session for managing the premium subscription. + * + * @param returnUrl The URL to redirect the user to after visiting the portal. */ - suspend fun getPortalUrl(): Result + suspend fun getPortalUrl(returnUrl: String): Result } diff --git a/network/src/main/kotlin/com/bitwarden/network/service/BillingServiceImpl.kt b/network/src/main/kotlin/com/bitwarden/network/service/BillingServiceImpl.kt index 4cf978dbfff..a6176e09482 100644 --- a/network/src/main/kotlin/com/bitwarden/network/service/BillingServiceImpl.kt +++ b/network/src/main/kotlin/com/bitwarden/network/service/BillingServiceImpl.kt @@ -3,6 +3,7 @@ package com.bitwarden.network.service import com.bitwarden.network.api.AuthenticatedBillingApi import com.bitwarden.network.model.CheckoutSessionRequestJson import com.bitwarden.network.model.CheckoutSessionResponseJson +import com.bitwarden.network.model.PortalSessionRequestJson import com.bitwarden.network.model.PortalUrlResponseJson import com.bitwarden.network.util.toResult @@ -20,9 +21,13 @@ internal class BillingServiceImpl( ) .toResult() - override suspend fun getPortalUrl(): Result = + override suspend fun getPortalUrl( + returnUrl: String, + ): Result = authenticatedBillingApi - .getPortalUrl() + .getPortalUrl( + body = PortalSessionRequestJson(returnUrl = returnUrl), + ) .toResult() } diff --git a/network/src/test/kotlin/com/bitwarden/network/service/BillingServiceTest.kt b/network/src/test/kotlin/com/bitwarden/network/service/BillingServiceTest.kt index 8fa87642ae7..c2a57137215 100644 --- a/network/src/test/kotlin/com/bitwarden/network/service/BillingServiceTest.kt +++ b/network/src/test/kotlin/com/bitwarden/network/service/BillingServiceTest.kt @@ -43,7 +43,7 @@ class BillingServiceTest : BaseServiceTest() { fun `getPortalUrl when response is Failure should return Failure`() = runTest { val response = MockResponse().setResponseCode(400) server.enqueue(response) - val actual = service.getPortalUrl() + val actual = service.getPortalUrl(returnUrl = RETURN_URL) assertTrue(actual.isFailure) } @@ -53,7 +53,7 @@ class BillingServiceTest : BaseServiceTest() { .setBody(PORTAL_URL_RESPONSE_JSON) .setResponseCode(200) server.enqueue(response) - val actual = service.getPortalUrl() + val actual = service.getPortalUrl(returnUrl = RETURN_URL) assertEquals(PORTAL_URL_RESPONSE.asSuccess(), actual) } } @@ -77,3 +77,5 @@ private const val PORTAL_URL_RESPONSE_JSON = """ private val PORTAL_URL_RESPONSE = PortalUrlResponseJson( url = "https://billing.stripe.com/p/session/test_portal_456", ) + +private const val RETURN_URL = "https://vault.bitwarden.com/#/settings/subscription" From cd1756ef260c78c7c1cd7650dd61abd5ec42f2c1 Mon Sep 17 00:00:00 2001 From: Patrick Honkonen Date: Mon, 16 Mar 2026 11:43:08 -0400 Subject: [PATCH 3/5] Rename checkoutUrl to checkoutSessionUrl in CheckoutSessionResponseJson --- .../bitwarden/network/model/CheckoutSessionResponseJson.kt | 6 +++--- .../com/bitwarden/network/service/BillingServiceTest.kt | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/network/src/main/kotlin/com/bitwarden/network/model/CheckoutSessionResponseJson.kt b/network/src/main/kotlin/com/bitwarden/network/model/CheckoutSessionResponseJson.kt index ff23a4fa248..201c0b14996 100644 --- a/network/src/main/kotlin/com/bitwarden/network/model/CheckoutSessionResponseJson.kt +++ b/network/src/main/kotlin/com/bitwarden/network/model/CheckoutSessionResponseJson.kt @@ -6,10 +6,10 @@ import kotlinx.serialization.Serializable /** * Response object returned when creating a premium checkout session. * - * @property checkoutUrl The Stripe checkout URL for premium upgrade. + * @property checkoutSessionUrl The Stripe checkout URL for premium upgrade. */ @Serializable data class CheckoutSessionResponseJson( - @SerialName("checkoutUrl") - val checkoutUrl: String, + @SerialName("checkoutSessionUrl") + val checkoutSessionUrl: String, ) diff --git a/network/src/test/kotlin/com/bitwarden/network/service/BillingServiceTest.kt b/network/src/test/kotlin/com/bitwarden/network/service/BillingServiceTest.kt index c2a57137215..2cfbb23c4c3 100644 --- a/network/src/test/kotlin/com/bitwarden/network/service/BillingServiceTest.kt +++ b/network/src/test/kotlin/com/bitwarden/network/service/BillingServiceTest.kt @@ -60,12 +60,12 @@ class BillingServiceTest : BaseServiceTest() { private const val CHECKOUT_SESSION_RESPONSE_JSON = """ { - "checkoutUrl": "https://checkout.stripe.com/c/pay/test_session_123" + "checkoutSessionUrl": "https://checkout.stripe.com/c/pay/test_session_123" } """ private val CHECKOUT_SESSION_RESPONSE = CheckoutSessionResponseJson( - checkoutUrl = "https://checkout.stripe.com/c/pay/test_session_123", + checkoutSessionUrl = "https://checkout.stripe.com/c/pay/test_session_123", ) private const val PORTAL_URL_RESPONSE_JSON = """ From 8bce796adb15662c3ff62400ecedf49445f29c5d Mon Sep 17 00:00:00 2001 From: Patrick Honkonen Date: Wed, 18 Mar 2026 09:32:49 -0400 Subject: [PATCH 4/5] Remove returnUrl from portal session endpoint --- .../network/api/AuthenticatedBillingApi.kt | 5 +---- .../network/model/PortalSessionRequestJson.kt | 15 --------------- .../bitwarden/network/service/BillingService.kt | 4 +--- .../network/service/BillingServiceImpl.kt | 9 ++------- .../network/service/BillingServiceTest.kt | 6 ++---- 5 files changed, 6 insertions(+), 33 deletions(-) delete mode 100644 network/src/main/kotlin/com/bitwarden/network/model/PortalSessionRequestJson.kt diff --git a/network/src/main/kotlin/com/bitwarden/network/api/AuthenticatedBillingApi.kt b/network/src/main/kotlin/com/bitwarden/network/api/AuthenticatedBillingApi.kt index f0ced18f11f..6ffb0342f0f 100644 --- a/network/src/main/kotlin/com/bitwarden/network/api/AuthenticatedBillingApi.kt +++ b/network/src/main/kotlin/com/bitwarden/network/api/AuthenticatedBillingApi.kt @@ -3,7 +3,6 @@ package com.bitwarden.network.api import com.bitwarden.network.model.CheckoutSessionRequestJson import com.bitwarden.network.model.CheckoutSessionResponseJson import com.bitwarden.network.model.NetworkResult -import com.bitwarden.network.model.PortalSessionRequestJson import com.bitwarden.network.model.PortalUrlResponseJson import retrofit2.http.Body import retrofit2.http.POST @@ -25,7 +24,5 @@ internal interface AuthenticatedBillingApi { * Creates a Stripe customer portal session for managing the premium subscription. */ @POST("/account/billing/vnext/portal-session") - suspend fun getPortalUrl( - @Body body: PortalSessionRequestJson, - ): NetworkResult + suspend fun getPortalUrl(): NetworkResult } diff --git a/network/src/main/kotlin/com/bitwarden/network/model/PortalSessionRequestJson.kt b/network/src/main/kotlin/com/bitwarden/network/model/PortalSessionRequestJson.kt deleted file mode 100644 index 22137301980..00000000000 --- a/network/src/main/kotlin/com/bitwarden/network/model/PortalSessionRequestJson.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.bitwarden.network.model - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -/** - * Request object for creating a Stripe customer portal session. - * - * @property returnUrl The URL to redirect the user to after visiting the portal. - */ -@Serializable -data class PortalSessionRequestJson( - @SerialName("returnUrl") - val returnUrl: String, -) diff --git a/network/src/main/kotlin/com/bitwarden/network/service/BillingService.kt b/network/src/main/kotlin/com/bitwarden/network/service/BillingService.kt index c438cc42c07..1a30e19cdce 100644 --- a/network/src/main/kotlin/com/bitwarden/network/service/BillingService.kt +++ b/network/src/main/kotlin/com/bitwarden/network/service/BillingService.kt @@ -15,8 +15,6 @@ interface BillingService { /** * Creates a Stripe customer portal session for managing the premium subscription. - * - * @param returnUrl The URL to redirect the user to after visiting the portal. */ - suspend fun getPortalUrl(returnUrl: String): Result + suspend fun getPortalUrl(): Result } diff --git a/network/src/main/kotlin/com/bitwarden/network/service/BillingServiceImpl.kt b/network/src/main/kotlin/com/bitwarden/network/service/BillingServiceImpl.kt index a6176e09482..4cf978dbfff 100644 --- a/network/src/main/kotlin/com/bitwarden/network/service/BillingServiceImpl.kt +++ b/network/src/main/kotlin/com/bitwarden/network/service/BillingServiceImpl.kt @@ -3,7 +3,6 @@ package com.bitwarden.network.service import com.bitwarden.network.api.AuthenticatedBillingApi import com.bitwarden.network.model.CheckoutSessionRequestJson import com.bitwarden.network.model.CheckoutSessionResponseJson -import com.bitwarden.network.model.PortalSessionRequestJson import com.bitwarden.network.model.PortalUrlResponseJson import com.bitwarden.network.util.toResult @@ -21,13 +20,9 @@ internal class BillingServiceImpl( ) .toResult() - override suspend fun getPortalUrl( - returnUrl: String, - ): Result = + override suspend fun getPortalUrl(): Result = authenticatedBillingApi - .getPortalUrl( - body = PortalSessionRequestJson(returnUrl = returnUrl), - ) + .getPortalUrl() .toResult() } diff --git a/network/src/test/kotlin/com/bitwarden/network/service/BillingServiceTest.kt b/network/src/test/kotlin/com/bitwarden/network/service/BillingServiceTest.kt index 2cfbb23c4c3..10c515ac058 100644 --- a/network/src/test/kotlin/com/bitwarden/network/service/BillingServiceTest.kt +++ b/network/src/test/kotlin/com/bitwarden/network/service/BillingServiceTest.kt @@ -43,7 +43,7 @@ class BillingServiceTest : BaseServiceTest() { fun `getPortalUrl when response is Failure should return Failure`() = runTest { val response = MockResponse().setResponseCode(400) server.enqueue(response) - val actual = service.getPortalUrl(returnUrl = RETURN_URL) + val actual = service.getPortalUrl() assertTrue(actual.isFailure) } @@ -53,7 +53,7 @@ class BillingServiceTest : BaseServiceTest() { .setBody(PORTAL_URL_RESPONSE_JSON) .setResponseCode(200) server.enqueue(response) - val actual = service.getPortalUrl(returnUrl = RETURN_URL) + val actual = service.getPortalUrl() assertEquals(PORTAL_URL_RESPONSE.asSuccess(), actual) } } @@ -77,5 +77,3 @@ private const val PORTAL_URL_RESPONSE_JSON = """ private val PORTAL_URL_RESPONSE = PortalUrlResponseJson( url = "https://billing.stripe.com/p/session/test_portal_456", ) - -private const val RETURN_URL = "https://vault.bitwarden.com/#/settings/subscription" From b1a6ab0dfcf23e023b5d122c330d8036a0f66b8b Mon Sep 17 00:00:00 2001 From: Patrick Honkonen Date: Wed, 18 Mar 2026 12:06:06 -0400 Subject: [PATCH 5/5] Move PLATFORM constant definition --- .../com/bitwarden/network/service/BillingServiceImpl.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/network/src/main/kotlin/com/bitwarden/network/service/BillingServiceImpl.kt b/network/src/main/kotlin/com/bitwarden/network/service/BillingServiceImpl.kt index 4cf978dbfff..0b52ba3ca8e 100644 --- a/network/src/main/kotlin/com/bitwarden/network/service/BillingServiceImpl.kt +++ b/network/src/main/kotlin/com/bitwarden/network/service/BillingServiceImpl.kt @@ -6,6 +6,8 @@ import com.bitwarden.network.model.CheckoutSessionResponseJson import com.bitwarden.network.model.PortalUrlResponseJson import com.bitwarden.network.util.toResult +private const val PLATFORM = "android" + /** * The default implementation of the [BillingService]. */ @@ -25,5 +27,3 @@ internal class BillingServiceImpl( .getPortalUrl() .toResult() } - -private const val PLATFORM = "android"