Skip to content

[PM-33853] feat: Add BillingAPIService for premium upgrade endpoints#2496

Merged
andrebispo5 merged 13 commits intomainfrom
pm-33853/premium-upgrade-billing-endpoint
Mar 27, 2026
Merged

[PM-33853] feat: Add BillingAPIService for premium upgrade endpoints#2496
andrebispo5 merged 13 commits intomainfrom
pm-33853/premium-upgrade-billing-endpoint

Conversation

@andrebispo5
Copy link
Copy Markdown
Contributor

🎟️ 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 Billing domain structure in Core/ with models, requests, and service integration into the ServiceContainer.

📸 Screenshots

N/A - API layer changes only, no UI modifications.

@andrebispo5 andrebispo5 requested review from a team and matt-livefront as code owners March 26, 2026 10:24
Copilot AI review requested due to automatic review settings March 26, 2026 10:24
@github-actions github-actions bot added app:password-manager Bitwarden Password Manager app context t:feature t:llm Change Type - LLM related change (e.g. CLAUDE.md files) labels Mar 26, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 26, 2026

Logo
Checkmarx One – Scan Summary & Details2705e769-655f-4bee-a84c-607b4d70ef2a

Great job! No new security vulnerabilities introduced in this pull request

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 BillingAPIService with createCheckoutSession() and getPortalUrl() endpoints and adds DI access via HasBillingAPIService.
  • Adds new Billing request/response models plus request tests and fixtures.
  • Updates .claude/CLAUDE.md architecture docs to include the new Billing/ 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
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
@testable import BitwardenShared
@testable import BitwardenShared
@testable import BitwardenSharedMocks

Copilot uses AI. Check for mistakes.
import TestHelpers
import XCTest

@testable import BitwardenShared
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
@testable import BitwardenShared
@testable import BitwardenShared
@testable import BitwardenSharedMocks

Copilot uses AI. Check for mistakes.
Comment on lines +7 to +12
struct PortalUrlResponseModel: JSONResponse, Equatable {
// MARK: Properties

/// The Stripe customer portal URL.
let url: String
}
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +7 to +12
struct CheckoutSessionResponseModel: JSONResponse, Equatable {
// MARK: Properties

/// The Stripe checkout URL for premium upgrade.
let checkoutSessionUrl: String
}
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +21 to +35
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())
}
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 26, 2026

Codecov Report

❌ Patch coverage is 97.05882% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.92%. Comparing base (b0d061c) to head (ee364d8).
⚠️ Report is 8 commits behind head on main.

Files with missing lines Patch % Lines
...ared/Core/Platform/Services/ServiceContainer.swift 0.00% 3 Missing ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown
Member

@fedemkr fedemkr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⛏️ 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 {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⛏️ 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 {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⛏️ 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

@github-actions github-actions bot removed the t:llm Change Type - LLM related change (e.g. CLAUDE.md files) label Mar 27, 2026
@andrebispo5 andrebispo5 enabled auto-merge (squash) March 27, 2026 17:12
@andrebispo5 andrebispo5 merged commit daaf1e8 into main Mar 27, 2026
16 checks passed
@andrebispo5 andrebispo5 deleted the pm-33853/premium-upgrade-billing-endpoint branch March 27, 2026 17:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app:password-manager Bitwarden Password Manager app context t:feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants