[PM-33853] feat: Add BillingAPIService for premium upgrade endpoints#2496
[PM-33853] feat: Add BillingAPIService for premium upgrade endpoints#2496andrebispo5 merged 13 commits intomainfrom
Conversation
|
Great job! No new security vulnerabilities introduced in this pull request |
There was a problem hiding this comment.
Pull request overview
Adds a new Billing domain in BitwardenShared/Core to support the premium upgrade flow by introducing a dedicated BillingAPIService, request/response models, and wiring it into the dependency injection ServiceContainer.
Changes:
- Introduces
BillingAPIServicewithcreateCheckoutSession()andgetPortalUrl()endpoints and adds DI access viaHasBillingAPIService. - Adds new Billing request/response models plus request tests and fixtures.
- Updates
.claude/CLAUDE.mdarchitecture docs to include the newBilling/top-level domain.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| BitwardenShared/Core/Platform/Services/Services.swift | Adds HasBillingAPIService and includes it in the Services composition typealias. |
| BitwardenShared/Core/Platform/Services/ServiceContainer.swift | Exposes billingAPIService via the container (backed by apiService). |
| BitwardenShared/Core/Billing/Services/API/BillingAPIService.swift | Defines Billing API protocol + APIService conformance for premium upgrade endpoints. |
| BitwardenShared/Core/Billing/Services/API/Requests/CreateCheckoutSessionRequest.swift | Request for creating a Stripe checkout session. |
| BitwardenShared/Core/Billing/Services/API/Requests/CreateCheckoutSessionRequestTests.swift | Unit tests for request method/path/body. |
| BitwardenShared/Core/Billing/Services/API/Requests/GetPortalUrlRequest.swift | Request for creating a Stripe customer portal session. |
| BitwardenShared/Core/Billing/Services/API/Requests/GetPortalUrlRequestTests.swift | Unit tests for request method/path/body. |
| BitwardenShared/Core/Billing/Models/Request/CheckoutSessionRequestModel.swift | Request body model for checkout session creation. |
| BitwardenShared/Core/Billing/Models/Request/CheckoutSessionRequestModelTests.swift | Unit tests for encoding/initialization. |
| BitwardenShared/Core/Billing/Models/Response/CheckoutSessionResponseModel.swift | Response model for checkout session URL. |
| BitwardenShared/Core/Billing/Models/Response/CheckoutSessionResponseModelTests.swift | Decoding/init tests using fixtures. |
| BitwardenShared/Core/Billing/Models/Response/PortalUrlResponseModel.swift | Response model for portal URL. |
| BitwardenShared/Core/Billing/Models/Response/PortalUrlResponseModelTests.swift | Decoding/init tests using fixtures. |
| BitwardenShared/Core/Billing/Services/API/Fixtures/APITestData+Billing.swift | Adds Billing APITestData fixtures for checkout + portal sessions. |
| .claude/CLAUDE.md | Updates architecture documentation to reflect the new Billing domain. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| import TestHelpers | ||
| import XCTest | ||
|
|
||
| @testable import BitwardenShared |
There was a problem hiding this comment.
APITestData.portalUrl is defined in Core/Billing/Services/API/Fixtures/ which is excluded from the main framework and compiled into BitwardenSharedMocks. This test file doesn’t import BitwardenSharedMocks, so it won’t compile (the portalUrl fixture won’t be visible). Add @testable import BitwardenSharedMocks (consistent with other response model decoding tests that use APITestData).
| @testable import BitwardenShared | |
| @testable import BitwardenShared | |
| @testable import BitwardenSharedMocks |
| import TestHelpers | ||
| import XCTest | ||
|
|
||
| @testable import BitwardenShared |
There was a problem hiding this comment.
APITestData.checkoutSession is defined in Core/Billing/Services/API/Fixtures/ which is compiled into BitwardenSharedMocks. This test file doesn’t import BitwardenSharedMocks, so it won’t compile (the checkoutSession fixture won’t be visible). Add @testable import BitwardenSharedMocks (consistent with other response model decoding tests that use APITestData).
| @testable import BitwardenShared | |
| @testable import BitwardenShared | |
| @testable import BitwardenSharedMocks |
| struct PortalUrlResponseModel: JSONResponse, Equatable { | ||
| // MARK: Properties | ||
|
|
||
| /// The Stripe customer portal URL. | ||
| let url: String | ||
| } |
There was a problem hiding this comment.
This response is a URL but is modeled as String. Elsewhere in this codebase, response URLs are modeled as URL (e.g., SaveAttachmentResponse.url, DownloadAttachmentResponse.url, SendFileResponseModel.url) which gives type-safety and decoding validation. Consider changing url to URL and updating the related tests/fixtures accordingly.
| struct CheckoutSessionResponseModel: JSONResponse, Equatable { | ||
| // MARK: Properties | ||
|
|
||
| /// The Stripe checkout URL for premium upgrade. | ||
| let checkoutSessionUrl: String | ||
| } |
There was a problem hiding this comment.
This value is a URL but is modeled as String. Elsewhere in this codebase, response URLs are modeled as URL (e.g., SaveAttachmentResponse.url, DownloadAttachmentResponse.url, SendFileResponseModel.url) which gives type-safety and decoding validation. Consider changing checkoutSessionUrl to URL and updating the related tests/fixtures accordingly.
| extension APIService: BillingAPIService { | ||
| /// The platform identifier for iOS. | ||
| private static let platform = "ios" | ||
|
|
||
| func createCheckoutSession() async throws -> CheckoutSessionResponseModel { | ||
| try await apiService.send( | ||
| CreateCheckoutSessionRequest( | ||
| requestModel: CheckoutSessionRequestModel(platform: Self.platform), | ||
| ), | ||
| ) | ||
| } | ||
|
|
||
| func getPortalUrl() async throws -> PortalUrlResponseModel { | ||
| try await apiService.send(GetPortalUrlRequest()) | ||
| } |
There was a problem hiding this comment.
There are request/model tests, but there’s no unit test covering the APIService conformance to BillingAPIService to ensure it sends the expected request (path, method, and body content like platform). Other API services in this repo typically have service-level tests (e.g., ConfigAPIServiceTests, DeviceAPIServiceTests). Add BillingAPIServiceTests verifying createCheckoutSession() and getPortalUrl() build the correct HTTP requests.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #2496 +/- ##
==========================================
+ Coverage 86.90% 86.92% +0.01%
==========================================
Files 1845 1852 +7
Lines 163218 163591 +373
==========================================
+ Hits 141847 142200 +353
- Misses 21371 21391 +20 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
…ionUrl property to URL type
fedemkr
left a comment
There was a problem hiding this comment.
Looks good, just a few ⛏️ .
🤔 One thing I think we could remove unless I'm missing something is any reference to Stripe as we don't make any requests to Stripe directly so we can leave it here more generic, so if the server would change the service there won't be any need to change all the comments. What do you think?
|
|
||
| @testable import BitwardenShared | ||
|
|
||
| class CheckoutSessionRequestModelTests: BitwardenTestCase { |
There was a problem hiding this comment.
⛏️ No need for these tests as it's testing basic swift out of the box features like init without any logic. This file can be deleted
|
|
||
| // MARK: - CheckoutSessionResponseModelTests | ||
|
|
||
| class CheckoutSessionResponseModelTests: BitwardenTestCase { |
There was a problem hiding this comment.
⛏️ No need for these tests as it's testing basic swift out of the box features like init without any logic. This file can be deleted
|
|
||
| // MARK: - PortalUrlResponseModelTests | ||
|
|
||
| class PortalUrlResponseModelTests: BitwardenTestCase { |
There was a problem hiding this comment.
⛏️ No need for these tests as it's testing basic swift out of the box features like init without any logic. This file can be deleted

🎟️ Tracking
https://bitwarden.atlassian.net/browse/PM-33853
📔 Objective
Add new billing API service with endpoints for premium upgrade flow:
createCheckoutSession()- Creates a Stripe checkout session for premium upgrade (POST/account/billing/vnext/premium/checkout)getPortalUrl()- Creates a Stripe customer portal session for subscription management (POST/account/billing/vnext/portal-session)This establishes the new
Billingdomain structure inCore/with models, requests, and service integration into theServiceContainer.📸 Screenshots
N/A - API layer changes only, no UI modifications.