Skip to content

refactor(billing): adds default flag to payment methods#2314

Merged
ygrishajev merged 3 commits intomainfrom
feature/billing
Dec 3, 2025
Merged

refactor(billing): adds default flag to payment methods#2314
ygrishajev merged 3 commits intomainfrom
feature/billing

Conversation

@ygrishajev
Copy link
Contributor

@ygrishajev ygrishajev commented Dec 3, 2025

refs #1779

Summary by CodeRabbit

  • New Features

    • Payment methods now include a default indicator so users can identify their preferred card.
  • Improvements

    • Payment methods are merged with local records and marked as validated for consistent display.
    • Access-control-aware filtering is applied when fetching payment methods.
    • API/SDK types updated to expose the new default flag; obsolete error schema removed.
  • Reliability

    • Out-of-sync payment method situations are detected and logged.
  • Tests

    • Tests updated to cover default flags, validation, and access-control filtering.

✏️ Tip: You can customize this high-level summary in your review settings.

@ygrishajev ygrishajev requested a review from a team as a code owner December 3, 2025 13:35
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 3, 2025

Walkthrough

getPaymentMethods now accepts an ACL/ability parameter and returns Stripe payment methods merged with local DB records; each returned method includes boolean flags validated and isDefault. Controllers, schemas, tests, and seeders were updated to pass/handle the ability and isDefault.

Changes

Cohort / File(s) Summary
Stripe service core
apps/api/src/billing/services/stripe/stripe.service.ts
Signature changed: getPaymentMethods(userId, customerId, ability: AnyAbility); merges remote Stripe methods with local DB records (ACL-filtered), attaches validated and isDefault, uses lodash/difference/keyBy, and logs STRIPE_PAYMENT_METHOD_OUT_OF_SYNC when mismatches occur.
Controller call sites
apps/api/src/billing/controllers/stripe/stripe.controller.ts, apps/api/src/billing/controllers/wallet/wallet.controller.ts
Call sites updated to pass this.authService.ability as a third argument to StripeService.getPaymentMethods.
HTTP schema & SDK types
apps/api/src/billing/http-schemas/stripe.schema.ts, packages/http-sdk/src/stripe/stripe.types.ts
Added isDefault boolean to payment-method schema/type (optional in schema).
Tests
apps/api/src/billing/services/stripe/stripe.service.spec.ts, apps/api/src/billing/controllers/wallet/wallet.controller.spec.ts
Tests updated to supply an ability (via createMongoAbility) to getPaymentMethods, mock PaymentMethodRepository.accessibleBy(), and expect isDefault (and validated) in returned objects.
Seeders / test utilities
apps/api/test/seeders/payment-method.seeder.ts
generateMergedPaymentMethod signature updated to accept isDefault?: boolean and include it in generated merged payment-methods.
Manifest / package
manifest_file, package.json
Test/manifest entries updated (listed in summary).

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Controller
    participant AuthService
    participant StripeService
    participant StripeAPI
    participant LocalRepo

    Client->>Controller: Request payment methods
    Controller->>AuthService: get ability
    AuthService-->>Controller: ability
    Controller->>StripeService: getPaymentMethods(userId, customerId, ability)

    par fetch remote and local
        StripeService->>StripeAPI: fetch Stripe payment methods
        StripeAPI-->>StripeService: remote methods
        StripeService->>LocalRepo: query local payment methods (accessibleBy ability)
        LocalRepo-->>StripeService: local methods (ACL filtered)
    end

    StripeService->>StripeService: merge remote + local, set validated & isDefault
    alt out-of-sync detected
        StripeService-->>StripeService: log STRIPE_PAYMENT_METHOD_OUT_OF_SYNC
    end
    StripeService-->>Controller: merged PaymentMethod[] (with isDefault, validated)
    Controller-->>Client: return payment methods
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Pay attention to merge logic and out-of-sync detection in stripe.service.ts.
  • Verify correct use of ability with PaymentMethodRepository.accessibleBy() and ACL filtering.
  • Ensure schema/SDK type updates match runtime shapes and tests.

Possibly related PRs

Suggested reviewers

  • baktun14
  • stalniy

Poem

🐇 I hopped through code with nimble feet,

Merged Stripe and local—now they meet,
Ability checks who may be shown,
A tiny default flag finds its home,
I logged a squeak and left a joyful beat.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and concisely describes the main change: adding an isDefault flag to payment methods across the billing service.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/billing

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b086a89 and ea1f78c.

⛔ Files ignored due to path filters (1)
  • apps/api/test/functional/__snapshots__/docs.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (8)
  • apps/api/src/billing/controllers/stripe/stripe.controller.ts (1 hunks)
  • apps/api/src/billing/controllers/wallet/wallet.controller.spec.ts (1 hunks)
  • apps/api/src/billing/controllers/wallet/wallet.controller.ts (1 hunks)
  • apps/api/src/billing/http-schemas/stripe.schema.ts (1 hunks)
  • apps/api/src/billing/services/stripe/stripe.service.spec.ts (3 hunks)
  • apps/api/src/billing/services/stripe/stripe.service.ts (6 hunks)
  • apps/api/test/seeders/payment-method.seeder.ts (1 hunks)
  • packages/http-sdk/src/stripe/stripe.types.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • apps/api/src/billing/controllers/wallet/wallet.controller.ts
  • apps/api/src/billing/services/stripe/stripe.service.spec.ts
  • packages/http-sdk/src/stripe/stripe.types.ts
  • apps/api/src/billing/http-schemas/stripe.schema.ts
  • apps/api/src/billing/controllers/wallet/wallet.controller.spec.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.{ts,tsx,js}: Never use type any or cast to type any. Always define the proper TypeScript types.
Never use deprecated methods from libraries.
Don't add unnecessary comments to the code.

Files:

  • apps/api/test/seeders/payment-method.seeder.ts
  • apps/api/src/billing/services/stripe/stripe.service.ts
  • apps/api/src/billing/controllers/stripe/stripe.controller.ts
🧠 Learnings (1)
📚 Learning: 2025-11-12T16:36:02.543Z
Learnt from: baktun14
Repo: akash-network/console PR: 2203
File: apps/deploy-web/src/components/onboarding/steps/PaymentMethodContainer/PaymentMethodContainer.tsx:161-168
Timestamp: 2025-11-12T16:36:02.543Z
Learning: In apps/deploy-web/src/components/onboarding/steps/PaymentMethodContainer/PaymentMethodContainer.tsx, the organization field captured during payment method setup is internal metadata. Errors from stripe.updateCustomerOrganization should be logged to Sentry but not surfaced to users, and the flow should continue even if the organization update fails, as it's non-critical and not something users can fix.

Applied to files:

  • apps/api/src/billing/services/stripe/stripe.service.ts
🧬 Code graph analysis (1)
apps/api/test/seeders/payment-method.seeder.ts (3)
apps/api/src/billing/http-schemas/stripe.schema.ts (1)
  • PaymentMethod (236-236)
apps/api/src/billing/services/stripe/stripe.service.ts (1)
  • PaymentMethod (35-35)
packages/http-sdk/src/stripe/stripe.types.ts (1)
  • PaymentMethod (30-42)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: validate / validate-app
  • GitHub Check: validate / validate-app
  • GitHub Check: test-build
  • GitHub Check: test-build
  • GitHub Check: validate / validate-app
  • GitHub Check: test-build
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Validate local packages
🔇 Additional comments (8)
apps/api/src/billing/controllers/stripe/stripe.controller.ts (1)

74-74: LGTM!

The addition of this.authService.ability as the third argument correctly enables ACL-based filtering for payment methods, consistent with the pattern used in markAsDefault (line 55) and getDefaultPaymentMethod (line 62).

apps/api/src/billing/services/stripe/stripe.service.ts (6)

4-5: LGTM!

The lodash utility imports are correctly used in getPaymentMethods for data merging and sync detection.


35-35: LGTM!

The addition of isDefault: boolean to the PaymentMethod type is correctly implemented and ensures all payment methods consistently expose their default status.


110-141: LGTM!

The refactored implementation correctly:

  • Accepts the ability parameter for ACL-based filtering
  • Merges remote Stripe payment methods with local DB records
  • Enriches results with validated and isDefault flags
  • Detects out-of-sync payment methods (remote without local records)
  • Logs at warn level (addressing the past review feedback)

Note that accessibleBy(ability, "read") filters local records by ACL, so payment methods the user cannot read will appear with validated: false and isDefault: false even if local records exist. This is intentional ACL enforcement.


156-163: LGTM!

The addition of isDefault: local.isDefault correctly propagates the default flag, and the warning log appropriately handles the out-of-sync scenario at the right severity level.


193-193: LGTM!

Correctly propagates isDefault from the local record when marking a payment method as default.


208-208: LGTM!

Correctly propagates isDefault from the newly created local record when marking a payment method as default.

apps/api/test/seeders/payment-method.seeder.ts (1)

58-60: LGTM!

The seeder function correctly extends support for the isDefault field, maintaining consistency with the validated field pattern and aligning with the updated PaymentMethod type.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (4)
apps/api/src/billing/controllers/stripe/stripe.controller.ts (1)

71-76: Pass ability into getPaymentMethods for ACL-aware retrieval

Forwarding this.authService.ability here is consistent with the updated StripeService API and ensures local payment-method metadata is scoped by ACL.

If you want consistency with markAsDefault/getDefaultPaymentMethod, you could destructure once (const { ability } = this.authService;) and pass ability instead of reaching through the service directly.

apps/api/src/billing/services/stripe/stripe.service.spec.ts (1)

1-1: Cover ACL-aware getPaymentMethods and new flags in tests

Using createMongoAbility and stubbing paymentMethodRepository.accessibleBy to return the repository mock makes the test align with the new getPaymentMethods(userId, customerId, ability) contract, and the expectations on validated/isDefault defaulting to false look correct.

It would be worthwhile to add an extra test where findByUserId returns local rows (including isValidated/isDefault) to exercise the merge path and the out-of-sync logging case.

Also applies to: 774-855, 1471-1471

apps/api/src/billing/services/stripe/stripe.service.ts (2)

4-6: ACL-aware merge of remote/local payment methods with default flag

The new PaymentMethod type and getPaymentMethods(userId, customerId, ability) implementation look solid: local records are keyed by paymentMethodId, merged into Stripe data with validated/isDefault booleans, sorted by created desc, and out-of-sync remote IDs are logged via difference.

One nuance: ability currently scopes only the local repository (accessibleBy(ability, "read")), while all Stripe methods for the given customerId are still returned (with validated/isDefault defaulting to false when there is no accessible local row). If ACL is intended to control which payment methods are visible at all (not just which ones have local metadata attached), consider additionally filtering remotes.data based on the set of IDs present in the accessible locals, or otherwise confirming that “show all Stripe cards for this customer but hide local flags” is the intended behavior.

Also applies to: 35-36, 110-141


143-163: Propagate isDefault through default-payment flows and align repository ACL usage

Extending getDefaultPaymentMethod and markPaymentMethodAsDefault to return { ...remote, validated, isDefault } keeps the service type consistent and ensures callers always see both flags. Using accessibleBy(ability, "read" | "update" | "create") on the repository also correctly routes these operations through ACL.

It may be worth double-checking your CASL rules so that the endpoint guarded with action: "update", subject: "PaymentMethod" also grants whatever "create" permission is required for createAsDefault, and to ensure that out-of-sync logging in getDefaultPaymentMethod doesn’t become noisy purely due to ACL filtering (i.e., when a default exists in Stripe but is intentionally hidden by ability).

Also applies to: 182-209

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between de10e23 and 85faaba.

📒 Files selected for processing (6)
  • apps/api/src/billing/controllers/stripe/stripe.controller.ts (1 hunks)
  • apps/api/src/billing/controllers/wallet/wallet.controller.ts (1 hunks)
  • apps/api/src/billing/http-schemas/stripe.schema.ts (1 hunks)
  • apps/api/src/billing/services/stripe/stripe.service.spec.ts (3 hunks)
  • apps/api/src/billing/services/stripe/stripe.service.ts (6 hunks)
  • apps/api/test/seeders/payment-method.seeder.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.{ts,tsx,js}: Never use type any or cast to type any. Always define the proper TypeScript types.
Never use deprecated methods from libraries.
Don't add unnecessary comments to the code.

Files:

  • apps/api/src/billing/http-schemas/stripe.schema.ts
  • apps/api/test/seeders/payment-method.seeder.ts
  • apps/api/src/billing/controllers/stripe/stripe.controller.ts
  • apps/api/src/billing/services/stripe/stripe.service.spec.ts
  • apps/api/src/billing/services/stripe/stripe.service.ts
  • apps/api/src/billing/controllers/wallet/wallet.controller.ts
**/*.spec.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/no-jest-mock.mdc)

Don't use jest.mock() in test files. Instead, use jest-mock-extended to create mocks and pass mocks as dependencies to the service under test

Use setup function instead of beforeEach in test files. The setup function must be at the bottom of the root describe block, should create an object under test and return it, accept a single parameter with inline type definition, avoid shared state, and not have a specified return type.

**/*.spec.{ts,tsx}: Use <Subject>.name in the root describe suite description instead of hardcoded class/service name strings to enable automated refactoring tools to find all references
Use either a method name or a condition starting with 'when' for nested suite descriptions in tests
Use present simple, 3rd person singular for test descriptions without prepending 'should'

Files:

  • apps/api/src/billing/services/stripe/stripe.service.spec.ts
🧠 Learnings (2)
📚 Learning: 2025-09-25T14:31:44.914Z
Learnt from: baktun14
Repo: akash-network/console PR: 1969
File: apps/deploy-web/src/pages/payment.tsx:179-191
Timestamp: 2025-09-25T14:31:44.914Z
Learning: The payment confirmation endpoint in apps/api/src/billing/http-schemas/stripe.schema.ts uses zod schema validation with `amount: z.number().gte(20, "Amount must be greater or equal to $20")` to ensure all payment requests meet the minimum amount requirement, preventing zero-amount or invalid payments from reaching Stripe.

Applied to files:

  • apps/api/src/billing/http-schemas/stripe.schema.ts
📚 Learning: 2025-11-12T16:36:02.543Z
Learnt from: baktun14
Repo: akash-network/console PR: 2203
File: apps/deploy-web/src/components/onboarding/steps/PaymentMethodContainer/PaymentMethodContainer.tsx:161-168
Timestamp: 2025-11-12T16:36:02.543Z
Learning: In apps/deploy-web/src/components/onboarding/steps/PaymentMethodContainer/PaymentMethodContainer.tsx, the organization field captured during payment method setup is internal metadata. Errors from stripe.updateCustomerOrganization should be logged to Sentry but not surfaced to users, and the flow should continue even if the organization update fails, as it's non-critical and not something users can fix.

Applied to files:

  • apps/api/src/billing/controllers/stripe/stripe.controller.ts
🧬 Code graph analysis (3)
apps/api/test/seeders/payment-method.seeder.ts (3)
apps/api/src/billing/http-schemas/stripe.schema.ts (1)
  • PaymentMethod (242-242)
apps/api/src/billing/services/stripe/stripe.service.ts (1)
  • PaymentMethod (35-35)
packages/http-sdk/src/stripe/stripe.types.ts (1)
  • PaymentMethod (30-41)
apps/api/src/billing/controllers/stripe/stripe.controller.ts (1)
apps/api/src/auth/services/auth.service.ts (2)
  • currentUser (13-15)
  • currentUser (17-24)
apps/api/src/billing/services/stripe/stripe.service.spec.ts (1)
apps/api/test/seeders/stripe-test-data.seeder.ts (1)
  • TEST_CONSTANTS (5-12)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: validate / validate-app
  • GitHub Check: test-build
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (3)
apps/api/src/billing/http-schemas/stripe.schema.ts (1)

16-20: Expose isDefault flag on PaymentMethod schema

Adding isDefault?: boolean here cleanly extends the API without breaking existing consumers and aligns with the new service-level default flag.

apps/api/test/seeders/payment-method.seeder.ts (1)

58-61: Extend seeder to support validated and isDefault flags

The updated helper cleanly adds isDefault alongside validated, coercing both to booleans so generated test PaymentMethod objects match the service type.

apps/api/src/billing/controllers/wallet/wallet.controller.ts (1)

40-46: Propagate ability into getPaymentMethods in trial creation

Passing this.authService.ability into stripeService.getPaymentMethods keeps the duplicate-trial and “must have payment method” logic consistent with the new ACL-aware merge in the service.

@codecov
Copy link

codecov bot commented Dec 3, 2025

Codecov Report

❌ Patch coverage is 68.18182% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 48.03%. Comparing base (f771e34) to head (a999aea).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
.../api/src/billing/services/stripe/stripe.service.ts 70.00% 6 Missing ⚠️
...rc/billing/controllers/stripe/stripe.controller.ts 0.00% 1 Missing ⚠️

❌ Your patch status has failed because the patch coverage (68.18%) is below the target coverage (80.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2314      +/-   ##
==========================================
+ Coverage   47.69%   48.03%   +0.34%     
==========================================
  Files        1028     1038      +10     
  Lines       29318    29676     +358     
  Branches     7576     7652      +76     
==========================================
+ Hits        13982    14254     +272     
- Misses      14872    14936      +64     
- Partials      464      486      +22     
Flag Coverage Δ *Carryforward flag
api 81.19% <68.18%> (+0.04%) ⬆️
deploy-web 26.99% <ø> (-0.02%) ⬇️
log-collector 75.35% <ø> (?)
notifications 87.94% <ø> (ø)
provider-console 81.48% <ø> (ø) Carriedforward from 109902c
provider-proxy 84.35% <ø> (ø) Carriedforward from 109902c

*This pull request uses carry forward flags. Click here to find out more.

Files with missing lines Coverage Δ
...rc/billing/controllers/wallet/wallet.controller.ts 98.38% <100.00%> (ø)
apps/api/src/billing/http-schemas/stripe.schema.ts 100.00% <ø> (ø)
...rc/billing/controllers/stripe/stripe.controller.ts 19.80% <0.00%> (ø)
.../api/src/billing/services/stripe/stripe.service.ts 80.00% <70.00%> (+0.68%) ⬆️

... and 15 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
apps/api/src/billing/controllers/wallet/wallet.controller.spec.ts (1)

19-19: Consider using WalletController.name in describe block.

Per coding guidelines, use <Subject>.name in the root describe suite description instead of hardcoded class/service name strings to enable automated refactoring tools to find all references.

-describe("WalletController", () => {
+describe(WalletController.name, () => {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 85faaba and e5283b1.

⛔ Files ignored due to path filters (1)
  • apps/api/test/functional/__snapshots__/docs.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (7)
  • apps/api/src/billing/controllers/stripe/stripe.controller.ts (1 hunks)
  • apps/api/src/billing/controllers/wallet/wallet.controller.spec.ts (1 hunks)
  • apps/api/src/billing/controllers/wallet/wallet.controller.ts (1 hunks)
  • apps/api/src/billing/http-schemas/stripe.schema.ts (1 hunks)
  • apps/api/src/billing/services/stripe/stripe.service.spec.ts (3 hunks)
  • apps/api/src/billing/services/stripe/stripe.service.ts (6 hunks)
  • apps/api/test/seeders/payment-method.seeder.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.{ts,tsx,js}: Never use type any or cast to type any. Always define the proper TypeScript types.
Never use deprecated methods from libraries.
Don't add unnecessary comments to the code.

Files:

  • apps/api/src/billing/controllers/stripe/stripe.controller.ts
  • apps/api/test/seeders/payment-method.seeder.ts
  • apps/api/src/billing/http-schemas/stripe.schema.ts
  • apps/api/src/billing/services/stripe/stripe.service.ts
  • apps/api/src/billing/services/stripe/stripe.service.spec.ts
  • apps/api/src/billing/controllers/wallet/wallet.controller.spec.ts
  • apps/api/src/billing/controllers/wallet/wallet.controller.ts
**/*.spec.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/no-jest-mock.mdc)

Don't use jest.mock() in test files. Instead, use jest-mock-extended to create mocks and pass mocks as dependencies to the service under test

Use setup function instead of beforeEach in test files. The setup function must be at the bottom of the root describe block, should create an object under test and return it, accept a single parameter with inline type definition, avoid shared state, and not have a specified return type.

**/*.spec.{ts,tsx}: Use <Subject>.name in the root describe suite description instead of hardcoded class/service name strings to enable automated refactoring tools to find all references
Use either a method name or a condition starting with 'when' for nested suite descriptions in tests
Use present simple, 3rd person singular for test descriptions without prepending 'should'

Files:

  • apps/api/src/billing/services/stripe/stripe.service.spec.ts
  • apps/api/src/billing/controllers/wallet/wallet.controller.spec.ts
🧠 Learnings (2)
📚 Learning: 2025-11-12T16:36:02.543Z
Learnt from: baktun14
Repo: akash-network/console PR: 2203
File: apps/deploy-web/src/components/onboarding/steps/PaymentMethodContainer/PaymentMethodContainer.tsx:161-168
Timestamp: 2025-11-12T16:36:02.543Z
Learning: In apps/deploy-web/src/components/onboarding/steps/PaymentMethodContainer/PaymentMethodContainer.tsx, the organization field captured during payment method setup is internal metadata. Errors from stripe.updateCustomerOrganization should be logged to Sentry but not surfaced to users, and the flow should continue even if the organization update fails, as it's non-critical and not something users can fix.

Applied to files:

  • apps/api/src/billing/controllers/stripe/stripe.controller.ts
📚 Learning: 2025-09-25T14:31:44.914Z
Learnt from: baktun14
Repo: akash-network/console PR: 1969
File: apps/deploy-web/src/pages/payment.tsx:179-191
Timestamp: 2025-09-25T14:31:44.914Z
Learning: The payment confirmation endpoint in apps/api/src/billing/http-schemas/stripe.schema.ts uses zod schema validation with `amount: z.number().gte(20, "Amount must be greater or equal to $20")` to ensure all payment requests meet the minimum amount requirement, preventing zero-amount or invalid payments from reaching Stripe.

Applied to files:

  • apps/api/src/billing/http-schemas/stripe.schema.ts
🧬 Code graph analysis (6)
apps/api/src/billing/controllers/stripe/stripe.controller.ts (1)
apps/api/src/auth/services/auth.service.ts (2)
  • currentUser (13-15)
  • currentUser (17-24)
apps/api/test/seeders/payment-method.seeder.ts (3)
apps/api/src/billing/http-schemas/stripe.schema.ts (1)
  • PaymentMethod (242-242)
apps/api/src/billing/services/stripe/stripe.service.ts (1)
  • PaymentMethod (35-35)
packages/http-sdk/src/stripe/stripe.types.ts (1)
  • PaymentMethod (30-41)
apps/api/src/billing/services/stripe/stripe.service.ts (5)
apps/api/src/billing/http-schemas/stripe.schema.ts (1)
  • PaymentMethod (242-242)
packages/http-sdk/src/stripe/stripe.types.ts (1)
  • PaymentMethod (30-41)
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (2)
  • userId (92-135)
  • user (137-156)
apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts (2)
  • userId (58-75)
  • userId (77-105)
apps/api/src/auth/services/auth.service.ts (2)
  • ability (44-46)
  • ability (48-50)
apps/api/src/billing/services/stripe/stripe.service.spec.ts (1)
apps/api/test/seeders/stripe-test-data.seeder.ts (1)
  • TEST_CONSTANTS (5-12)
apps/api/src/billing/controllers/wallet/wallet.controller.spec.ts (1)
apps/api/test/seeders/payment-method.seeder.ts (1)
  • generatePaymentMethod (11-56)
apps/api/src/billing/controllers/wallet/wallet.controller.ts (1)
apps/api/src/auth/services/auth.service.ts (2)
  • currentUser (13-15)
  • currentUser (17-24)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: validate / validate-app
  • GitHub Check: test-build
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (12)
apps/api/test/seeders/payment-method.seeder.ts (1)

58-60: LGTM!

The isDefault flag is correctly added to the seeder, following the same pattern as the existing validated field. The double-negation (!!isDefault) ensures a proper boolean value.

apps/api/src/billing/services/stripe/stripe.service.ts (4)

110-114: LGTM on ACL integration.

The ability parameter is correctly integrated for ACL-based filtering of local payment method records using accessibleBy(ability, "read"). The parallel fetching of remote and local data is efficient.


35-35: LGTM!

The PaymentMethod type extension with isDefault is correctly added alongside validated.


155-164: LGTM!

The getDefaultPaymentMethod correctly returns both validated and isDefault flags from the local record, with appropriate error logging when data is out of sync.


191-208: LGTM!

The markPaymentMethodAsDefault method correctly propagates isDefault and validated flags from local records in both the update and create paths.

apps/api/src/billing/controllers/wallet/wallet.controller.spec.ts (1)

251-251: LGTM!

The mock correctly includes the new isDefault: false field, aligning with the updated PaymentMethod type.

apps/api/src/billing/http-schemas/stripe.schema.ts (1)

16-19: LGTM!

The isDefault field is correctly added to the schema as optional, consistent with the validated field pattern.

apps/api/src/billing/controllers/stripe/stripe.controller.ts (1)

74-74: LGTM!

The ability parameter is correctly passed to getPaymentMethods, following the same pattern used in markAsDefault (line 52) and getDefaultPaymentMethod (line 60).

apps/api/src/billing/controllers/wallet/wallet.controller.ts (1)

44-44: LGTM! Ability parameter properly added for ACL filtering.

The addition of this.authService.ability as the third parameter aligns with the updated getPaymentMethods signature for ACL-aware payment method retrieval. The usage is consistent with line 103 where ability is also accessed from authService.

apps/api/src/billing/services/stripe/stripe.service.spec.ts (3)

1-1: LGTM! Import added for ACL test utilities.

The createMongoAbility import is necessary for constructing ability objects in the updated test cases.


834-854: LGTM! Test properly updated for ACL-aware payment method retrieval.

The test correctly:

  • Passes an ability object as the third parameter using createMongoAbility([{ action: "read", subject: "PaymentMethod" }])
  • Updates expectations to include the new isDefault: false field on returned payment methods
  • Maintains the existing validated: false field

These changes align with the PR objective of adding a default flag to payment methods while introducing ACL-based filtering.


1470-1470: LGTM! Proper fluent API mock for CASL's accessibleBy.

The mock correctly implements the fluent API pattern by returning the repository itself, which matches CASL's accessibleBy method behavior for chaining.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
apps/api/src/billing/services/stripe/stripe.service.ts (2)

110-141: Merging logic looks solid with one consideration.

The implementation correctly:

  • Fetches remote and local payment methods in parallel
  • Uses accessibleBy(ability, "read") for ACL-based filtering
  • Merges remote methods with local state using keyBy for O(1) lookups
  • Defaults validated and isDefault to false when no local record exists
  • Sorts by creation timestamp (newest first)

Out-of-sync detection consideration:

The current logic detects remote payment methods without local DB records (difference(remoteIds, Object.keys(localById))). This is useful for identifying webhook delivery delays or failures. However, consider also detecting the inverse case: local records referencing payment methods that no longer exist remotely. This could indicate deleted payment methods or data integrity issues.

Optional enhancement to detect orphaned local records:

 const outOfSyncIds = difference(remoteIds, Object.keys(localById));
+const orphanedLocalIds = difference(Object.keys(localById), remoteIds);

 if (outOfSyncIds.length) {
   this.loggerService.warn({
     event: "STRIPE_PAYMENT_METHOD_OUT_OF_SYNC",
     userId,
     outOfSyncIds
   });
 }
+
+if (orphanedLocalIds.length) {
+  this.loggerService.warn({
+    event: "STRIPE_PAYMENT_METHOD_ORPHANED_LOCAL",
+    userId,
+    orphanedLocalIds
+  });
+}

143-165: Consider more specific out-of-sync logging.

The method correctly merges remote and local state when both exist (line 156). However, the else block (lines 158-163) logs STRIPE_PAYMENT_METHOD_OUT_OF_SYNC for multiple distinct scenarios:

  1. Remote default exists but no local record
  2. Local default exists but no remote default
  3. Neither exists

For better observability, consider logging different event names or including flags to distinguish these cases, making it easier to diagnose the specific synchronization issue.

Example refinement:

-    } else {
+    } else if (!remote && local) {
       this.loggerService.warn({
-        event: "STRIPE_PAYMENT_METHOD_OUT_OF_SYNC",
+        event: "STRIPE_DEFAULT_PAYMENT_METHOD_LOCAL_ONLY",
         userId: user.id,
-        remoteId: typeof remote === "string" ? remote : remote?.id,
         localId: local?.paymentMethodId
       });
+    } else if (remote && !local) {
+      this.loggerService.warn({
+        event: "STRIPE_DEFAULT_PAYMENT_METHOD_REMOTE_ONLY",
+        userId: user.id,
+        remoteId: typeof remote === "string" ? remote : remote?.id
+      });
     }
apps/api/src/billing/services/stripe/stripe.service.spec.ts (1)

1470-1470: Mock pattern for accessibleBy correctly enables method chaining.

The mock returns paymentMethodRepository itself, which properly implements the AccessibleModel pattern where accessibleBy(ability, action) returns a filtered repository instance for method chaining. This matches the production implementation in PaymentMethodRepository where accessibleBy(...abilityParams) returns this after calling withAbility(). However, for consistency with other test files in the codebase, consider using .mockReturnThis() instead of .mockReturnValue(paymentMethodRepository).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e5283b1 and b086a89.

⛔ Files ignored due to path filters (1)
  • apps/api/test/functional/__snapshots__/docs.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (8)
  • apps/api/src/billing/controllers/stripe/stripe.controller.ts (1 hunks)
  • apps/api/src/billing/controllers/wallet/wallet.controller.spec.ts (1 hunks)
  • apps/api/src/billing/controllers/wallet/wallet.controller.ts (1 hunks)
  • apps/api/src/billing/http-schemas/stripe.schema.ts (1 hunks)
  • apps/api/src/billing/services/stripe/stripe.service.spec.ts (3 hunks)
  • apps/api/src/billing/services/stripe/stripe.service.ts (6 hunks)
  • apps/api/test/seeders/payment-method.seeder.ts (1 hunks)
  • packages/http-sdk/src/stripe/stripe.types.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/api/src/billing/http-schemas/stripe.schema.ts
  • apps/api/src/billing/controllers/wallet/wallet.controller.spec.ts
  • apps/api/test/seeders/payment-method.seeder.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.{ts,tsx,js}: Never use type any or cast to type any. Always define the proper TypeScript types.
Never use deprecated methods from libraries.
Don't add unnecessary comments to the code.

Files:

  • apps/api/src/billing/services/stripe/stripe.service.spec.ts
  • packages/http-sdk/src/stripe/stripe.types.ts
  • apps/api/src/billing/controllers/stripe/stripe.controller.ts
  • apps/api/src/billing/services/stripe/stripe.service.ts
  • apps/api/src/billing/controllers/wallet/wallet.controller.ts
**/*.spec.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/no-jest-mock.mdc)

Don't use jest.mock() in test files. Instead, use jest-mock-extended to create mocks and pass mocks as dependencies to the service under test

Use setup function instead of beforeEach in test files. The setup function must be at the bottom of the root describe block, should create an object under test and return it, accept a single parameter with inline type definition, avoid shared state, and not have a specified return type.

**/*.spec.{ts,tsx}: Use <Subject>.name in the root describe suite description instead of hardcoded class/service name strings to enable automated refactoring tools to find all references
Use either a method name or a condition starting with 'when' for nested suite descriptions in tests
Use present simple, 3rd person singular for test descriptions without prepending 'should'

Files:

  • apps/api/src/billing/services/stripe/stripe.service.spec.ts
🧠 Learnings (1)
📚 Learning: 2025-11-12T16:36:02.543Z
Learnt from: baktun14
Repo: akash-network/console PR: 2203
File: apps/deploy-web/src/components/onboarding/steps/PaymentMethodContainer/PaymentMethodContainer.tsx:161-168
Timestamp: 2025-11-12T16:36:02.543Z
Learning: In apps/deploy-web/src/components/onboarding/steps/PaymentMethodContainer/PaymentMethodContainer.tsx, the organization field captured during payment method setup is internal metadata. Errors from stripe.updateCustomerOrganization should be logged to Sentry but not surfaced to users, and the flow should continue even if the organization update fails, as it's non-critical and not something users can fix.

Applied to files:

  • apps/api/src/billing/controllers/stripe/stripe.controller.ts
  • apps/api/src/billing/services/stripe/stripe.service.ts
🧬 Code graph analysis (4)
apps/api/src/billing/services/stripe/stripe.service.spec.ts (1)
apps/api/test/seeders/stripe-test-data.seeder.ts (1)
  • TEST_CONSTANTS (5-12)
apps/api/src/billing/controllers/stripe/stripe.controller.ts (1)
apps/api/src/auth/services/auth.service.ts (2)
  • currentUser (13-15)
  • currentUser (17-24)
apps/api/src/billing/services/stripe/stripe.service.ts (3)
apps/api/src/billing/http-schemas/stripe.schema.ts (1)
  • PaymentMethod (242-242)
packages/http-sdk/src/stripe/stripe.types.ts (1)
  • PaymentMethod (30-42)
apps/api/src/auth/services/auth.service.ts (2)
  • ability (44-46)
  • ability (48-50)
apps/api/src/billing/controllers/wallet/wallet.controller.ts (1)
apps/api/src/auth/services/auth.service.ts (2)
  • currentUser (13-15)
  • currentUser (17-24)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: validate (packages) / validate-unsafe
  • GitHub Check: validate (apps/deploy-web) / validate-unsafe
  • GitHub Check: validate / validate-app
  • GitHub Check: test-build
  • GitHub Check: validate / validate-app
  • GitHub Check: validate / validate-app
  • GitHub Check: test-build
  • GitHub Check: validate / validate-app
  • GitHub Check: test-build
  • GitHub Check: Validate local packages
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (8)
packages/http-sdk/src/stripe/stripe.types.ts (1)

35-35: LGTM! Clean type extension.

The addition of isDefault: boolean to the PaymentMethod interface is consistent with the existing validated field and aligns with the broader changes in this PR.

apps/api/src/billing/controllers/wallet/wallet.controller.ts (1)

44-44: LGTM! Consistent with StripeController changes.

The addition of this.authService.ability as the third argument aligns with the updated StripeService.getPaymentMethods signature and follows the same pattern as in StripeController.

apps/api/src/billing/services/stripe/stripe.service.spec.ts (2)

1-1: LGTM! Necessary import for ability creation in tests.


834-855: Test properly updated for new signature.

The test correctly:

  • Passes createMongoAbility([{ action: "read", subject: "PaymentMethod" }]) as the third argument
  • Expects validated: false and isDefault: false on returned payment methods
  • Mocks paymentMethodRepository.findByUserId to return empty array (no local records)

This validates the merging logic where remote methods without local DB records have both flags set to false.

apps/api/src/billing/services/stripe/stripe.service.ts (3)

4-5: LGTM! Appropriate lodash utilities for merging logic.


35-35: LGTM! Type properly extends Stripe.PaymentMethod.


183-209: LGTM! Proper handling of local record creation and ACL filtering.

The method correctly:

  • Uses accessibleBy(ability, "update") for the default update
  • Uses accessibleBy(ability, "create") when creating a new local record
  • Handles both cases: existing local record and missing local record
  • Returns the merged PaymentMethod with validated and isDefault flags in both paths
apps/api/src/billing/controllers/stripe/stripe.controller.ts (1)

69-79: Remove ability parameter from repository-level authorization filtering.

The @Protected decorator ensures ability is properly initialized before the controller method executes, so there are no runtime concerns about availability. However, passing ability to stripe.getPaymentMethods() which then applies it at the repository level via accessibleBy() violates the architectural principle that ability checks should be enforced at the controller level, not the repository level. Repository methods shouldn't enforce ability checks as they don't have context about where they're being called from and may be reused in different scenarios.

Likely an incorrect or invalid review comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

Comments