feat(billing): adds balance check job handler#2274
Conversation
WalkthroughImplements wallet auto-reload scheduling and handler, adds payment-method defaulting (DB, repo, service, controller, webhook), introduces paying-user utilities, fiat conversion and memoized balances, transactional/job-meta plumbing, schema migration snapshot, many repo/service API changes, tests, and docs. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant WalletSettings as WalletSettingService
participant WalletJob as WalletReloadJobService
participant JobQueue as JobQueueService
participant Worker as WalletBalanceReloadCheckHandler
participant Balances as BalancesService
participant StripeSvc as StripeService
participant StripeAPI as Stripe API
WalletSettings->>WalletJob: scheduleForWalletSetting(walletSetting)
WalletJob->>JobQueue: enqueue(WalletBalanceReloadCheck, singletonKey, startAfter)
JobQueue-->>WalletJob: jobId
WalletJob->>WalletSettings: persist autoReloadJobId(jobId)
JobQueue->>Worker: execute(job.payload, { id: jobId })
Worker->>WalletSettings: fetch walletSetting & user
Worker->>Balances: getFullBalanceInFiat(address, isOldWallet?)
Worker->>StripeSvc: getDefaultPaymentMethod(user, ability)
alt balance < threshold
Worker->>StripeSvc: createPaymentIntent(..., idempotencyKey)
StripeSvc->>StripeAPI: paymentIntents.create(...)
StripeAPI-->>StripeSvc: PaymentIntent
Worker->>WalletJob: scheduleForWalletSetting(next startAfter)
else balance >= threshold
Worker->>WalletJob: scheduleForWalletSetting(next startAfter)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
d8f2557 to
3852a68
Compare
Codecov Report❌ Patch coverage is ❌ Your patch status has failed because the patch coverage (58.72%) 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 #2274 +/- ##
==========================================
+ Coverage 47.61% 47.71% +0.10%
==========================================
Files 1036 1038 +2
Lines 29350 29600 +250
Branches 7630 7651 +21
==========================================
+ Hits 13976 14125 +149
- Misses 14877 15060 +183
+ Partials 497 415 -82
*This pull request uses carry forward flags. Click here to find out more.
🚀 New features to boost your workflow:
|
a34a233 to
b31182e
Compare
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (10)
apps/api/src/billing/model-schemas/payment-method/payment-method.schema.ts (1)
19-33: Consider adding an index for(userId, isDefault)lookupsThe new
isDefaultflag is likely to be used in queries like “get default payment method for user X”. To keep those queries efficient as the table grows, consider adding a composite index on(userId, isDefault)alongside the existinguserIdIsValidatedIdx.table => ({ @@ - userIdIsValidatedIdx: index("payment_methods_user_id_is_validated_idx").on(table.userId, table.isValidated), + userIdIsValidatedIdx: index("payment_methods_user_id_is_validated_idx").on(table.userId, table.isValidated), + userIdIsDefaultIdx: index("payment_methods_user_id_is_default_idx").on(table.userId, table.isDefault),apps/api/src/billing/routes/stripe-payment-methods/stripe-payment-methods.router.ts (1)
44-69: Route wiring is correct; consider a no-content responseThe new
POST /v1/stripe/payment-methods/defaultroute is wired correctly (validation, security, controller call). Since this is a side-effect-only operation with no response body, you might consider using204 No Content(or returning{}) instead ofc.json(undefined, 200), but this is purely a semantics/UX nit.-stripePaymentMethodsRouter.openapi(markAsDefaultRoute, async function markAsDefault(c) { - await container.resolve(StripeController).markAsDefault(c.req.valid("json")); - return c.json(undefined, 200); -}); +stripePaymentMethodsRouter.openapi(markAsDefaultRoute, async function markAsDefault(c) { + await container.resolve(StripeController).markAsDefault(c.req.valid("json")); + return c.body(null, 204); +});apps/api/src/billing/services/stripe-webhook/stripe-webhook.service.ts (1)
144-157: Make “first default payment method” selection atomic to avoid racesThe
countByUserId→isDefault = count === 0logic works in the single-event case, but under concurrentpayment_method.attachedevents for the same user, both transactions could seecount === 0and insert records withisDefault = true. That can leave multiple local defaults, even if Stripe ultimately has only one.To harden this:
- Prefer a repository-level atomic operation (e.g.,
createAsDefault(userId, fingerprint, paymentMethodId)) that:
- Inserts the first method as default if none exist, and/or
- Ensures only one default per user (e.g., via a transaction that clears previous defaults or a partial unique index).
- Keep the Stripe
markRemotePaymentMethodAsDefaultcall coupled to that atomic path so local and remote defaults stay in sync.The current behavior is acceptable for most cases but may produce subtle inconsistencies under rare concurrent attach scenarios.
Also applies to: 159-165
apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts (1)
48-69: Consider reusingtoOutputto avoid duplication.The
parseFloatconversion logic on lines 66-67 duplicates the same logic intoOutput(lines 75-76). Consider extracting or reusingtoOutputfor consistency and maintainability.Also, the method lacks an explicit return type annotation. Consider adding one for better type safety:
- async findInternalByUserIdWithRelations(userId: WalletSettingOutput["userId"]) { + async findInternalByUserIdWithRelations(userId: WalletSettingOutput["userId"]): Promise<{ + wallet: { address: string | null; isOldWallet: boolean | null }; + user: /* UserOutput type */; + autoReloadThreshold: number | undefined; + autoReloadAmount: number | undefined; + // ... other fields + } | undefined> {apps/api/src/billing/repositories/payment-method/payment-method.repository.ts (1)
3-3: Consider using the standarduuidpackage instead ofunleash-client.Importing
uuidv4fromunleash-client/lib/uuidv4is unconventional. The codebase already usesuuid(as seen inwallet-settings.service.tswithimport { v4 as uuidv4 } from "uuid"). Using a consistent UUID source improves maintainability.-import { uuidv4 } from "unleash-client/lib/uuidv4"; +import { v4 as uuidv4 } from "uuid";apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts (1)
20-21: Consider using dependency injection instead ofjest.mock("uuid").Per coding guidelines,
jest.mock()should be avoided in favor ofjest-mock-extendedand dependency injection. Theuuidv4function could be injected as a dependency intoWalletSettingServiceto enable cleaner testing without module mocking.However, since this is pre-existing code and refactoring the service may be out of scope for this PR, this is a nice-to-have improvement for future consideration.
apps/api/src/billing/services/stripe/stripe.service.ts (2)
32-33: Potential type naming conflict with existingPaymentMethodtypes.This exported
PaymentMethodtype may conflict withPaymentMethoddefined inapps/api/src/billing/http-schemas/stripe.schema.ts(line 236) andpackages/http-sdk/src/stripe/stripe.types.ts(lines 29-40). Consider using a more specific name likeStripePaymentMethodWithValidationorValidatedPaymentMethodto avoid ambiguity when both types are imported in the same file.-export type PaymentMethod = Stripe.PaymentMethod & { validated: boolean }; +export type ValidatedPaymentMethod = Stripe.PaymentMethod & { validated: boolean };
189-210: Consider precision-safe currency conversion.
Math.round(params.amount * 100)may produce unexpected results due to floating-point arithmetic (e.g.,19.99 * 100yields1998.9999999999998). WhileMath.roundmitigates this in most cases, consider using a more robust approach for financial calculations.-const amountInCents = Math.round(params.amount * 100); +const amountInCents = Math.round((params.amount * 100 + Number.EPSILON) * 100) / 100;Alternatively, require the caller to pass
amountInCentsdirectly to avoid conversion ambiguity.apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (2)
20-22: Duplicate utility type definition.The
Requiretype utility is already defined inapps/api/src/billing/services/paying-user/paying-user.tsat line 5. Consider importing it from there to avoid duplication and maintain consistency.-type Require<T, K extends keyof T> = Omit<T, K> & { - [P in K]-?: NonNullable<T[P]>; -}; +import { Require } from "../paying-user/paying-user";Note: You may need to export
Requirefrompaying-user.tsif it's not already exported.
226-234: Placeholder implementation for next check date calculation.The method currently returns a fixed 1-day delay with a note indicating "Implementation pending...". This is acceptable for initial development but should be tracked for completion before production use, as the scheduling logic likely needs to be based on wallet activity or balance burn rate.
Would you like me to open an issue to track the implementation of proper next-check-date calculation logic?
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
apps/api/test/functional/__snapshots__/docs.spec.ts.snapis excluded by!**/*.snap
📒 Files selected for processing (33)
apps/api/drizzle/0023_sad_adam_warlock.sql(1 hunks)apps/api/drizzle/0024_thankful_stick.sql(1 hunks)apps/api/drizzle/meta/0023_snapshot.json(1 hunks)apps/api/drizzle/meta/0024_snapshot.json(1 hunks)apps/api/drizzle/meta/_journal.json(1 hunks)apps/api/src/auth/services/auth.service.ts(2 hunks)apps/api/src/billing/controllers/stripe/stripe.controller.ts(3 hunks)apps/api/src/billing/http-schemas/stripe.schema.ts(2 hunks)apps/api/src/billing/http-schemas/wallet.schema.ts(0 hunks)apps/api/src/billing/model-schemas/payment-method/payment-method.schema.ts(1 hunks)apps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.ts(1 hunks)apps/api/src/billing/model-schemas/wallet-setting/wallet-setting.schema.ts(2 hunks)apps/api/src/billing/repositories/payment-method/payment-method.repository.ts(2 hunks)apps/api/src/billing/repositories/user-wallet/user-wallet.repository.ts(2 hunks)apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts(1 hunks)apps/api/src/billing/routes/stripe-payment-methods/stripe-payment-methods.router.ts(5 hunks)apps/api/src/billing/services/balances/balances.service.ts(3 hunks)apps/api/src/billing/services/paying-user/paying-user.ts(1 hunks)apps/api/src/billing/services/refill/refill.service.ts(1 hunks)apps/api/src/billing/services/stripe-webhook/stripe-webhook.service.ts(2 hunks)apps/api/src/billing/services/stripe/stripe.service.ts(6 hunks)apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts(1 hunks)apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts(2 hunks)apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.ts(2 hunks)apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts(4 hunks)apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts(5 hunks)apps/api/src/core/services/job-queue/job-queue.service.spec.ts(9 hunks)apps/api/src/core/services/job-queue/job-queue.service.ts(3 hunks)apps/api/src/core/types/require.type.ts(1 hunks)apps/api/test/seeders/balance.seeder.ts(2 hunks)apps/api/test/seeders/database-payment-method.seeder.ts(1 hunks)apps/api/test/seeders/payment-method.seeder.ts(2 hunks)packages/http-sdk/src/stripe/stripe.service.ts(0 hunks)
💤 Files with no reviewable changes (2)
- packages/http-sdk/src/stripe/stripe.service.ts
- apps/api/src/billing/http-schemas/wallet.schema.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (.cursor/rules/general.mdc)
**/*.{ts,tsx,js}: Never use typeanyor cast to typeany. 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/model-schemas/payment-method/payment-method.schema.tsapps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.tsapps/api/test/seeders/database-payment-method.seeder.tsapps/api/test/seeders/balance.seeder.tsapps/api/src/billing/services/refill/refill.service.tsapps/api/src/core/types/require.type.tsapps/api/src/billing/services/paying-user/paying-user.tsapps/api/src/billing/model-schemas/wallet-setting/wallet-setting.schema.tsapps/api/src/billing/repositories/user-wallet/user-wallet.repository.tsapps/api/test/seeders/payment-method.seeder.tsapps/api/src/billing/services/wallet-settings/wallet-settings.service.tsapps/api/src/core/services/job-queue/job-queue.service.tsapps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.tsapps/api/src/billing/controllers/stripe/stripe.controller.tsapps/api/src/auth/services/auth.service.tsapps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.tsapps/api/src/billing/routes/stripe-payment-methods/stripe-payment-methods.router.tsapps/api/src/billing/services/balances/balances.service.tsapps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.tsapps/api/src/billing/services/wallet-initializer/wallet-initializer.service.tsapps/api/src/billing/services/stripe-webhook/stripe-webhook.service.tsapps/api/src/billing/http-schemas/stripe.schema.tsapps/api/src/billing/services/stripe/stripe.service.tsapps/api/src/billing/repositories/payment-method/payment-method.repository.tsapps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.tsapps/api/src/core/services/job-queue/job-queue.service.spec.ts
**/*.spec.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/no-jest-mock.mdc)
Don't use
jest.mock()in test files. Instead, usejest-mock-extendedto create mocks and pass mocks as dependencies to the service under testUse
setupfunction instead ofbeforeEachin test files. Thesetupfunction must be at the bottom of the rootdescribeblock, 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>.namein 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/wallet-settings/wallet-settings.service.spec.tsapps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.tsapps/api/src/core/services/job-queue/job-queue.service.spec.ts
🧠 Learnings (9)
📚 Learning: 2025-07-30T10:18:24.469Z
Learnt from: baktun14
Repo: akash-network/console PR: 1750
File: apps/api/drizzle/meta/0017_snapshot.json:17-49
Timestamp: 2025-07-30T10:18:24.469Z
Learning: In the Akash Network console billing system, the `user_wallets.user_id` column can be NULL to support anonymous user wallets, allowing users to interact with the system before authentication. Multiple anonymous wallets with NULL user_id values are permitted by design.
Applied to files:
apps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.tsapps/api/drizzle/0023_sad_adam_warlock.sql
📚 Learning: 2025-09-29T02:40:22.090Z
Learnt from: stalniy
Repo: akash-network/console PR: 1951
File: apps/api/src/user/services/user/user.service.ts:48-63
Timestamp: 2025-09-29T02:40:22.090Z
Learning: In the Akash Network Console codebase, the users table was originally created by Sequelize and later migrated to Drizzle. Due to this migration history, the unique constraint on the userId field is named "userSetting_userId_unique" rather than following the typical "users_user_id_unique" pattern.
Applied to files:
apps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.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/controllers/stripe/stripe.controller.tsapps/api/src/billing/routes/stripe-payment-methods/stripe-payment-methods.router.tsapps/api/src/billing/http-schemas/stripe.schema.ts
📚 Learning: 2025-10-15T16:39:55.348Z
Learnt from: jzsfkzm
Repo: akash-network/console PR: 2039
File: apps/deploy-web/tests/ui/change-wallets.spec.ts:4-10
Timestamp: 2025-10-15T16:39:55.348Z
Learning: In the Akash Console E2E tests using the context-with-extension fixture, the first wallet is automatically created during fixture setup via `importWalletToLeap` in `apps/deploy-web/tests/ui/fixture/wallet-setup.ts`, so tests that call `frontPage.createWallet()` are creating a second wallet to test wallet switching functionality.
Applied to files:
apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
📚 Learning: 2025-11-25T17:45:52.965Z
Learnt from: CR
Repo: akash-network/console PR: 0
File: .cursor/rules/setup-instead-of-before-each.mdc:0-0
Timestamp: 2025-11-25T17:45:52.965Z
Learning: Applies to **/*.spec.{ts,tsx} : 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.
Applied to files:
apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.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/services/stripe/stripe.service.ts
📚 Learning: 2025-09-04T04:21:26.067Z
Learnt from: stalniy
Repo: akash-network/console PR: 1868
File: apps/api/src/app/services/trial-deployment-lease-created/trial-deployment-lease-created.handler.ts:73-76
Timestamp: 2025-09-04T04:21:26.067Z
Learning: The apps/api codebase consistently uses .toISOString() for startAfter values when scheduling jobs with JobQueueService to ensure UTC timezone consistency. This pattern is used throughout trial-started.handler.ts, trial-deployment-lease-created.handler.ts and other job scheduling code. Do not suggest changing to Date objects as ISO strings provide explicit UTC representation for database storage.
Applied to files:
apps/api/src/core/services/job-queue/job-queue.service.spec.ts
📚 Learning: 2025-09-04T04:21:26.067Z
Learnt from: stalniy
Repo: akash-network/console PR: 1868
File: apps/api/src/app/services/trial-deployment-lease-created/trial-deployment-lease-created.handler.ts:73-76
Timestamp: 2025-09-04T04:21:26.067Z
Learning: In apps/api job scheduling with JobQueueService, use .toISOString() for startAfter values instead of Date objects to ensure UTC timezone consistency when dates are persisted to the database. Date objects can be ambiguous about timezone during serialization, while ISO strings with .toISOString() explicitly represent UTC.
Applied to files:
apps/api/src/core/services/job-queue/job-queue.service.spec.ts
📚 Learning: 2025-11-25T17:45:44.790Z
Learnt from: CR
Repo: akash-network/console PR: 0
File: .cursor/rules/no-jest-mock.mdc:0-0
Timestamp: 2025-11-25T17:45:44.790Z
Learning: Applies to **/*.spec.{ts,tsx} : 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
Applied to files:
apps/api/src/core/services/job-queue/job-queue.service.spec.ts
🧬 Code graph analysis (13)
apps/api/test/seeders/database-payment-method.seeder.ts (1)
apps/api/src/billing/repositories/payment-method/payment-method.repository.ts (1)
PaymentMethodOutput(11-11)
apps/api/test/seeders/balance.seeder.ts (2)
apps/api/src/billing/http-schemas/balance.schema.ts (1)
GetBalancesResponseOutput(17-17)apps/deploy-web/src/utils/apiUtils.ts (1)
balance(44-46)
apps/api/src/billing/services/paying-user/paying-user.ts (2)
apps/api/src/core/types/require.type.ts (1)
Require(1-3)apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (1)
user(144-169)
apps/api/src/billing/model-schemas/wallet-setting/wallet-setting.schema.ts (2)
apps/api/src/user/model-schemas/user/user.schema.ts (1)
Users(8-28)apps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.ts (1)
UserWallets(5-18)
apps/api/src/billing/repositories/user-wallet/user-wallet.repository.ts (1)
apps/api/src/core/providers/postgres.provider.ts (1)
ApiPgTables(80-80)
apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts (3)
apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts (2)
WalletSettingInput(12-17)WalletSettingOutput(19-22)apps/api/src/core/repositories/base.repository.ts (1)
isUniqueViolation(227-229)apps/api/src/billing/events/wallet-balance-reload-check.ts (1)
WalletBalanceReloadCheck(5-15)
apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts (1)
apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts (3)
userId(59-76)userId(78-106)walletSetting(144-160)
apps/api/src/billing/controllers/stripe/stripe.controller.ts (2)
apps/api/src/auth/services/auth.service.ts (5)
Protected(62-78)currentUser(13-15)currentUser(17-24)ability(44-46)ability(48-50)apps/api/src/billing/http-schemas/stripe.schema.ts (2)
PaymentMethodMarkAsDefaultInput(236-236)PaymentMethodsResponse(238-238)
apps/api/src/auth/services/auth.service.ts (1)
apps/api/src/billing/services/paying-user/paying-user.ts (3)
PayingUser(6-6)assertIsPayingUser(8-10)isPayingUser(12-14)
apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts (4)
apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts (1)
walletSetting(144-160)apps/api/test/seeders/payment-method.seeder.ts (1)
generatePaymentMethod(11-56)apps/api/src/billing/http-schemas/stripe.schema.ts (1)
PaymentMethod(237-237)apps/api/src/billing/services/stripe/stripe.service.ts (1)
PaymentMethod(33-33)
apps/api/src/billing/services/stripe-webhook/stripe-webhook.service.ts (2)
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (1)
user(144-169)apps/api/src/billing/services/paying-user/paying-user.ts (1)
assertIsPayingUser(8-10)
apps/api/src/billing/services/stripe/stripe.service.ts (5)
apps/api/src/billing/http-schemas/stripe.schema.ts (2)
PaymentMethod(237-237)PaymentIntentResult(240-240)packages/http-sdk/src/stripe/stripe.types.ts (1)
PaymentMethod(30-41)apps/api/src/billing/services/paying-user/paying-user.ts (1)
PayingUser(6-6)apps/api/src/user/repositories/user/user.repository.ts (1)
UserOutput(10-12)apps/api/src/core/services/tx/tx.service.ts (1)
WithTransaction(33-45)
apps/api/src/billing/repositories/payment-method/payment-method.repository.ts (1)
apps/api/src/core/providers/postgres.provider.ts (1)
ApiPgTables(80-80)
⏰ 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). (4)
- GitHub Check: validate / validate-app
- GitHub Check: test-build
- GitHub Check: validate / validate-app
- GitHub Check: test-build
🔇 Additional comments (50)
apps/api/drizzle/0024_thankful_stick.sql (1)
1-1: LGTM!The migration safely adds the
is_defaultcolumn with a sensible default value (false) and NOT NULL constraint, ensuring existing rows are handled correctly.apps/api/drizzle/meta/_journal.json (1)
166-179: LGTM!Migration journal entries are correctly formatted and follow the existing pattern. The sequential indices and tags align with the new migration files.
apps/api/src/core/types/require.type.ts (1)
1-3: LGTM!The
Requireutility type is correctly implemented. It makes the specified properties both required and non-nullable while preserving the rest of the type.apps/api/drizzle/meta/0024_snapshot.json (1)
1-798: Schema snapshot acknowledged.This is a generated snapshot file documenting the database schema state after migration 0024. The snapshot correctly reflects the addition of the
is_defaultcolumn topayment_methodsand the NOT NULL constraint onuser_wallets.user_id.apps/api/drizzle/meta/0023_snapshot.json (1)
1-791: Schema snapshot acknowledged.This is a generated snapshot file documenting the database schema state after migration 0023, reflecting the NOT NULL constraint on
user_wallets.user_id.apps/api/src/billing/services/refill/refill.service.ts (1)
10-11: LGTM!The import path changes are a standard refactoring from barrel exports to explicit module paths. No logic or behavior changes.
apps/api/src/core/services/job-queue/job-queue.service.ts (1)
208-215: JobMeta and handler signature extension look goodExposing only
job.idto handlers viaJobMetakeeps handlers decoupled from the queue implementation while still enabling better logging and tracing. The updatedhandle(payload, job)signature is clear and focused.apps/api/test/seeders/balance.seeder.ts (1)
21-34: generateBalance helper is well-typed and consistentThe
generateBalanceutility cleanly modelsGetBalancesResponseOutput["data"], computestotalfrombalance + deploymentswith proper rounding, and still allows overriding any field. This is a solid, reusable test helper.apps/api/test/seeders/payment-method.seeder.ts (1)
58-61: generateMergedPaymentMethod correctly composes Stripe data withvalidatedflagThe helper cleanly layers an optional
validatedflag on top of a generatedStripe.PaymentMethod, returning the project-levelPaymentMethodtype. The destructuring ofvalidatedfrom overrides and the!!validatedcoercion both look good.apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.ts (1)
53-63: initialize(userId: string) implementation is consistent with existing wallet flowsThe simplified
initialize(userId: string)method correctly creates a wallet record, provisions a wallet viawalletManager, and persists the resulting address. The sequence mirrors the trial-initialization flow and looks coherent.apps/api/src/billing/services/paying-user/paying-user.ts (1)
1-14: PayingUser type guard and assertion are well-designedThe
PayingUseralias plusisPayingUser/assertIsPayingUserpair provide a clear, type-safe way to gate payment-specific flows. UsingRequire<..., "stripeCustomerId">and a 402 status for “payments not set up” is a reasonable and consistent approach.apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts (1)
38-46: LGTM!The explicit undefined guard and
toOutputconversion are correctly implemented.apps/api/src/billing/model-schemas/wallet-setting/wallet-setting.schema.ts (1)
39-48: LGTM!The Drizzle ORM relations are correctly defined with proper field references and will enable efficient joined loading for the wallet balance reload check handler.
apps/api/src/billing/http-schemas/stripe.schema.ts (2)
10-14: LGTM!The schema follows the established pattern with nested
dataobject structure, consistent with other request schemas in this file.
236-236: LGTM!Type export correctly infers from the schema.
apps/api/test/seeders/database-payment-method.seeder.ts (1)
3-15: LGTM!Good refactor to use
PaymentMethodOutputfrom the repository as the single source of truth, and the addition ofisDefault: falsealigns with the new schema column.apps/api/src/auth/services/auth.service.ts (1)
26-42: LGTM!Well-designed method overloads providing type-safe access to paying users with flexible strictness options. The implementation correctly:
- Always validates user presence (401)
- In strict mode, asserts paying user status via
assertIsPayingUser(402)- In non-strict mode, returns
undefinedfor non-paying usersapps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts (4)
19-20: LGTM!Good use of
WalletBalanceReloadCheckHandler.namefor the describe suite and"handle"for the nested describe, following the coding guidelines.
256-305: LGTM!The
setupfunction correctly follows the coding guidelines:
- Placed at the bottom of the root
describeblock- Creates and returns the object under test with all dependencies
- Uses
jest-mock-extendedfor mocks- Accepts optional overrides via inline type (implicitly via spread)
- Avoids shared state
21-62: LGTM!Comprehensive test coverage for the reload trigger scenario with proper assertions on the Stripe payment intent parameters including the idempotency key.
145-253: LGTM!Excellent coverage of all validation error paths with proper event logging assertions.
apps/api/src/billing/controllers/stripe/stripe.controller.ts (2)
45-51: LGTM!The new
markAsDefaultmethod correctly:
- Uses the
updateaction onStripePaymentsubject for authorization- Retrieves the paying user (strict mode ensures 402 if not configured)
- Delegates to the service layer with proper ability context
54-63: LGTM!Good defensive pattern using
getCurrentPayingUser({ strict: false })to gracefully handle non-paying users by returning an empty array instead of throwing.apps/api/src/billing/services/balances/balances.service.ts (1)
112-130: I'll help you verify this review comment. Let me start by checking the DEPLOYMENT_GRANT_DENOM usage and understanding the coin mapping logic.
<function_calls>
#!/bin/bashCheck for DEPLOYMENT_GRANT_DENOM usage and possible values
rg -n "DEPLOYMENT_GRANT_DENOM" --type ts -C 3
</function_calls>Now let me also check the structure of the balances service to understand the context better:
<function_calls>
#!/bin/bashGet the file and check its overall structure
wc -l apps/api/src/billing/services/balances/balances.service.ts
</function_calls><function_calls>
#!/bin/bashRead the balances service file to understand the context
head -150 apps/api/src/billing/services/balances/balances.service.ts | tail -60
</function_calls>apps/api/src/billing/repositories/user-wallet/user-wallet.repository.ts (2)
9-10: LGTM!The new type definitions properly separate the full insert input type (
DbCreateUserWalletInput) from the partial update input type (DbUserWalletInput). This improves type safety and clarifies the intended usage of each type.
72-83: LGTM!The updated
createmethod signature usingPick<DbCreateUserWalletInput, "userId" | "address">enforces that only the required insert fields are accepted, aligning with the new type structure and improving API clarity.apps/api/src/core/services/job-queue/job-queue.service.spec.ts (6)
22-25: LGTM!Queue configuration expectations updated to match new defaults: 5-minute retry delay max and explicit policy field.
67-76: LGTM!Good test coverage for the new validation that prevents multiple handlers from registering for the same queue. The test correctly verifies the error message.
95-100: LGTM!Log expectation updated to include
jobIdin the JOB_ENQUEUED event, improving observability.
151-156: LGTM!Mock implementation updated to use a single job object pattern with
idfield, matching the updated service behavior.
174-179: LGTM!Handler invocation now correctly passes the job context
{ id: job.id }as a second argument, enabling handlers to access job metadata.
284-313: LGTM!The
setupfunction is correctly placed at the bottom of the rootdescribeblock, creates the service under test, avoids shared state, and returns all necessary mocks. TherunWithContextmock type is properly cast toExecutionContextService["runWithContext"]. As per coding guidelines, this follows the recommended test setup pattern.apps/api/src/billing/repositories/payment-method/payment-method.repository.ts (2)
10-11: LGTM!Exporting
PaymentMethodInputandPaymentMethodOutputtypes enables type-safe usage across the codebase.
70-77: LGTM!The
countByUserIdmethod correctly usescount()withwhereAccessibleByfor proper access control and handles the null case with a fallback to 0.apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts (7)
8-11: LGTM!New dependencies (
StripeService,UserRepository,isUniqueViolation) are properly imported to support the enhanced validation and error handling logic.
13-17: LGTM!The
WalletSettingInputinterface clearly defines the expected input shape for wallet settings, improving API contract clarity.
45-57: LGTM!The refactored
upsertWalletSettingcleanly delegates to private methods (#update,#create,#arrangeSchedule), improving readability and separation of concerns.
59-76: LGTM!The
#updatemethod correctly handles the case where no previous setting exists by returning early, and properly validates before updating.
78-106: LGTM!The
#createmethod properly validates input, handles the race condition on unique constraint violation by retrying via#update, and uses theisUniqueViolationhelper for clean error detection.
108-131: LGTM!Comprehensive validation ensures that auto-reload can only be enabled when: (1) threshold and amount are set, (2) user exists, (3) Stripe customer is set up, and (4) a default payment method exists. This prevents configuration errors.
134-142: LGTM!The
#arrangeSchedulemethod correctly handles state transitions: schedules a job when auto-reload is newly enabled, and cancels the existing job when disabled.apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts (1)
279-322: LGTM!The
setupfunction correctly:
- Creates
userWithStripewithstripeCustomerIdfor Stripe integration tests- Mocks
UserRepository.findByIdto return the user with Stripe ID- Mocks
StripeService.getDefaultPaymentMethodwith a generated payment method- Instantiates
WalletSettingServicewith all new dependencies- Returns all necessary test fixtures
The function is properly placed at the bottom of the root
describeblock as per coding guidelines.apps/api/src/billing/services/stripe/stripe.service.ts (4)
122-138: LGTM!The method correctly fetches both Stripe customer data and local payment method in parallel, with proper assertions for each failure case. The merged return type preserves the validated status from the local database.
140-145: LGTM!Simple and correct ownership verification that handles both string and object forms of the customer reference.
174-178: LGTM!Simple and focused method for updating the Stripe customer's default payment method.
388-390: LGTM!Clean simplification of the method.
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (4)
55-64: LGTM!Clean orchestration of the handler flow with proper Result-based error handling. The separation of concerns between resource collection, reload attempt, and scheduling is well-structured.
86-142: LGTM!Comprehensive validation with clear error messages for each failure case. The use of type narrowing through conditional checks properly ensures the returned
Resourcestype is fully initialized.
178-213: LGTM!The reload logic correctly triggers when balance drops to or below the threshold. Good use of the job ID for idempotency to prevent duplicate charges, and proper error logging before re-throwing.
144-156: Verify intentional bypass of user permissions.The ability is hardcoded with
createMongoAbility([{ action: "read", subject: "PaymentMethod" }])rather than deriving it from the actual user's permissions. Since this is a background job handler, this may be intentional (system-level access), but please confirm this is the expected behavior and not a security gap.
apps/api/src/billing/repositories/payment-method/payment-method.repository.ts
Show resolved
Hide resolved
apps/api/src/billing/repositories/payment-method/payment-method.repository.ts
Show resolved
Hide resolved
.../api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (13)
apps/api/src/billing/repositories/user-wallet/user-wallet.repository.ts (1)
72-83:createsignature now correctly uses DB insert type; consider small type-level cleanupsSwitching
createtoPick<DbCreateUserWalletInput, "userId" | "address">is a nice tightening that matches the NOT NULL DB constraint and avoids nullable userIds at the boundary. Two optional follow-ups you might consider:
- Extract a shared
type UserWalletId = DbCreateUserWalletInput["userId"];and reuse it both here and ingetOrCreateinstead ofExclude<UserWalletInput["userId"], undefined | null>, to keep the “valid userId” notion in one place.- Add an explicit return type, e.g.
async create(...): Promise<UserWalletOutput>, to lock the external contract of this repository method and make refactors safer.These are non-blocking polish items; the current change is sound.
apps/api/src/billing/model-schemas/payment-method/payment-method.schema.ts (1)
19-19: Consider adding an index on (user_id, is_default) for query performance.The new
isDefaultfield enables marking a payment method as the default for a user. If the application frequently queries for the default payment method per user, consider adding a composite index on(userId, isDefault)to optimize these lookups.Apply this diff to add the index:
table => ({ fingerprintPaymentMethodIdUnique: uniqueIndex("payment_methods_fingerprint_payment_method_id_unique").on(table.fingerprint, table.paymentMethodId), fingerprintIdx: index("payment_methods_fingerprint_idx").on(table.fingerprint), userIdIdx: index("payment_methods_user_id_idx").on(table.userId), userIdIsValidatedIdx: index("payment_methods_user_id_is_validated_idx").on(table.userId, table.isValidated), + userIdIsDefaultIdx: index("payment_methods_user_id_is_default_idx").on(table.userId, table.isDefault), userIdFingerprintPaymentMethodIdIdx: index("payment_methods_user_id_fingerprint_payment_method_id_idx").on( table.userId, table.fingerprint, table.paymentMethodId ) })apps/api/src/billing/routes/stripe-payment-methods/stripe-payment-methods.router.ts (1)
44-69: LGTM!The new route for marking a payment method as default is correctly implemented:
- Proper schema validation with
PaymentMethodMarkAsDefaultInputSchema- Security middleware applied via
SECURITY_BEARER_OR_API_KEY- Handler delegates to the controller appropriately
- Follows existing patterns in the file
Optionally, consider using a 204 No Content response instead of 200 with undefined body:
stripePaymentMethodsRouter.openapi(markAsDefaultRoute, async function markAsDefault(c) { await container.resolve(StripeController).markAsDefault(c.req.valid("json")); - return c.json(undefined, 200); + return c.body(null, 204); });And update the route definition:
responses: { - 200: { - description: "Payment method is marked as the default successfully." + 204: { + description: "Payment method marked as default successfully" } }apps/api/src/billing/services/stripe-webhook/stripe-webhook.service.ts (1)
6-6: Default-payment-method flow is sound but has consistency/race edge casesThe logic to treat the first payment method as default and to assert a paying user before calling Stripe is good. Two follow-ups to consider:
Concurrency on first default
If multiplepayment_method.attachedevents for the same user are processed concurrently,countByUserIdcan return0in both handlers, and both inserts may attemptisDefault = true. If the repository/DB does not enforce “at most one default per user”, this can leave you with multiple defaults. If it does enforce this, one insert will fail—verify that this is intentional and that failures are logged/handled the way you want. A repository-levelcreateAsDefaultthat encapsulates this invariant might be safer.External Stripe call inside the transaction + Promise.all
Running the StripemarkRemotePaymentMethodAsDefaultcall in parallel with the DB insert under@WithTransaction()means:
- You hold DB locks while waiting on the network.
- On partial failure you can end up with Stripe state changed but the DB rolled back (or vice versa), depending on which promise fails first.
If stronger consistency is needed, consider:- Committing the DB transaction first, then updating Stripe (possibly via an out-of-band job), or
- Making the Stripe call idempotent and adding compensating logic for retry/error paths.
Also applies to: 144-157, 163-165
apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts (1)
38-46: Repository behaviour is correct; consider de-duplicating numeric parsingThe new
findByUserIdreturn type and explicitundefinedearly return look good and are clearer for callers. The relation-loading helper also does the right conversions onautoReloadThreshold/autoReloadAmount, but that logic is now duplicated withtoOutput. Extracting a small internal helper likeparseAutoReloadFields(...)that bothtoOutputandfindInternalByUserIdWithRelationscan use would keep these conversions in one place and reduce drift if the shape ever changes.Also applies to: 48-69
apps/api/src/billing/services/balances/balances.service.ts (1)
9-9: Fiat conversion math is fine; naming and rounding could be clearerThe overall calculation (micro-denom → base units → fiat using
stats.price) is correct, and memoizing onaverageBlockTimemakes sense. Two small nits:
#toFiatAmount(uTokenAmount: number)is currently called withfullBalance.* * stats.price, i.e. a value already scaled by price. Either change the helper to accept pure uTokens and multiply bystats.priceoutside, or rename the parameter to better reflect that it’s “micro-value to be divided by 1e6”, not strictly a uToken count.- You round to 2 decimals inside
#toFiatAmountand then roundtotalagain. If you ever care about minimizing rounding error, you might prefer to keep full precision until the very end and only round once.These are stylistic/precision improvements; current behaviour is acceptable.
Also applies to: 19-21, 112-130
apps/api/src/billing/http-schemas/stripe.schema.ts (1)
10-14: New mark-as-default schema is fine; check naming consistency with other endpointsThe
PaymentMethodMarkAsDefaultInputSchema+ inferred type are straightforward and follow your{ data: ... }convention. One thing to double-check is the field name: this schema usesdata.idwhileRemovePaymentMethodParamsSchemausespaymentMethodId. If both refer to the same logical identifier in your public API, consider aligning the name (paymentMethodIdorid) for consistency, or confirm that the difference is intentional and documented.Also applies to: 236-236
apps/api/src/auth/services/auth.service.ts (1)
5-5: getCurrentPayingUser overloads and implementation look soundThe overload set and implementation correctly model the “strict vs non-strict” contract, and using
assertIsPayingUser/isPayingUsergives you proper narrowing at call sites. Behaviour-wise, the method consistently returnsundefinedonly for authenticated but non-paying users and throws 401 when there is no current user, which is a sensible default. If you ever want to keep the 401 logic in a single place, you could implement this in terms ofthis.currentUser, but that’s optional.Also applies to: 26-42
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts (1)
19-305: Comprehensive coverage; consider optionalsetupparameter to match house style.The test suite exercises the handler’s main branches and error paths very well (balance thresholds, enqueue behavior, and validation logging). To fully align with the stated guideline for
setupin*.spec.ts, you could later evolvesetup()to accept a single (optional) config parameter with an inline type (e.g., for overrides), but this is not blocking.apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts (1)
279-321: Stripe/user mocks wiring forWalletSettingServicelooks correct; legacyas anycasts can be cleaned up later.The updated
setup()correctly constructsuserWithStripe,userRepository, andstripeServiceso the new validation logic (user lookup + default payment method check) is exercised via mocks. This matches the updatedWalletSettingServiceconstructor signature.There are some older
... as anycasts on mockedupdateByIdresults elsewhere in this file; they aren’t new in this PR but do violate the “noanycast” guideline and would be good candidates for a future typed helper or explicit mock type.apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (1)
178-205: Auto‑reload treats non-throwingcreatePaymentIntentoutcomes as success.
#tryToReloadonly distinguishes between “no error thrown” and “error thrown” fromcreatePaymentIntent, butcreatePaymentIntentcan return{ success: false, requiresAction: true, ... }without throwing. In that case the handler will logWALLET_BALANCE_RELOADEDeven though the payment actually requires 3DS and may not complete.If that distinction matters for monitoring or UX, consider checking the returned
PaymentIntentResult.successand logging a different event (or treating it as a failure) whensuccessis false.apps/api/src/billing/repositories/payment-method/payment-method.repository.ts (1)
70-122: Default-payment-method repository helpers look consistent; watchcount()return type.The new helpers (
countByUserId,findDefaultByUserId,markAsDefault,createAsDefault,#unmarkAsDefaultExcluding) form a coherent default-management layer:
markAsDefaultsets the target’sisDefaultand unmarks all other defaults via#unmarkAsDefaultExcluding.createAsDefaultatomically creates a new default and unmarks others, which is especially safe when called inside a transaction (as done inStripeService.markPaymentMethodAsDefault).One minor point:
countByUserIdreturnsresult?.count ?? 0, and depending on the driver/schemacount()may yield abigint. If this value is later used in arithmetic or serialized, you may want to normalize it tonumberexplicitly to avoid surprises in consumers.apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts (1)
45-57:upsertWalletSettingrefactor and Stripe-based validation look good; consider tightening typing aroundPayingUser.The refactor to
upsertWalletSettingvia#update/#createplus#arrangeScheduleis clear and keeps scheduling concerns separate from persistence. The new#validatelogic for the “enable auto-reload” case (requiring both threshold and amount, confirming the user exists with a Stripe customer ID, and checking that a default payment method exists viastripeService.getDefaultPaymentMethod) matches the business intent.One type-level improvement you could make (non-blocking):
getDefaultPaymentMethodexpects aPayingUser, but#validatecurrently passes{ ...user, stripeCustomerId }wherestripeCustomerIdis not narrowed tostringin TypeScript by the precedingassert. If you want stricter typing, you can explicitly construct aPayingUserafter the assert:const stripeCustomerId = user.stripeCustomerId; assert(stripeCustomerId, 404, "User payments not set up"); const payingUser: PayingUser = { ...user, stripeCustomerId }; await this.stripeService.getDefaultPaymentMethod(payingUser, ability);Functionally this doesn’t change behavior (since
getDefaultPaymentMethodalready throws 404s for missing defaults), but it keeps the types aligned with thePayingUserhelper.Also applies to: 78-106, 108-131
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
apps/api/test/functional/__snapshots__/docs.spec.ts.snapis excluded by!**/*.snap
📒 Files selected for processing (33)
apps/api/drizzle/0023_sad_adam_warlock.sql(1 hunks)apps/api/drizzle/0024_thankful_stick.sql(1 hunks)apps/api/drizzle/meta/0023_snapshot.json(1 hunks)apps/api/drizzle/meta/0024_snapshot.json(1 hunks)apps/api/drizzle/meta/_journal.json(1 hunks)apps/api/src/auth/services/auth.service.ts(2 hunks)apps/api/src/billing/controllers/stripe/stripe.controller.ts(3 hunks)apps/api/src/billing/http-schemas/stripe.schema.ts(2 hunks)apps/api/src/billing/http-schemas/wallet.schema.ts(0 hunks)apps/api/src/billing/model-schemas/payment-method/payment-method.schema.ts(1 hunks)apps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.ts(1 hunks)apps/api/src/billing/model-schemas/wallet-setting/wallet-setting.schema.ts(2 hunks)apps/api/src/billing/repositories/payment-method/payment-method.repository.ts(2 hunks)apps/api/src/billing/repositories/user-wallet/user-wallet.repository.ts(2 hunks)apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts(1 hunks)apps/api/src/billing/routes/stripe-payment-methods/stripe-payment-methods.router.ts(5 hunks)apps/api/src/billing/services/balances/balances.service.ts(3 hunks)apps/api/src/billing/services/paying-user/paying-user.ts(1 hunks)apps/api/src/billing/services/refill/refill.service.ts(1 hunks)apps/api/src/billing/services/stripe-webhook/stripe-webhook.service.ts(2 hunks)apps/api/src/billing/services/stripe/stripe.service.ts(6 hunks)apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts(1 hunks)apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts(2 hunks)apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.ts(2 hunks)apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts(4 hunks)apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts(5 hunks)apps/api/src/core/services/job-queue/job-queue.service.spec.ts(9 hunks)apps/api/src/core/services/job-queue/job-queue.service.ts(3 hunks)apps/api/src/core/types/require.type.ts(1 hunks)apps/api/test/seeders/balance.seeder.ts(2 hunks)apps/api/test/seeders/database-payment-method.seeder.ts(1 hunks)apps/api/test/seeders/payment-method.seeder.ts(2 hunks)packages/http-sdk/src/stripe/stripe.service.ts(0 hunks)
💤 Files with no reviewable changes (2)
- packages/http-sdk/src/stripe/stripe.service.ts
- apps/api/src/billing/http-schemas/wallet.schema.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (.cursor/rules/general.mdc)
**/*.{ts,tsx,js}: Never use typeanyor cast to typeany. 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/balance.seeder.tsapps/api/test/seeders/database-payment-method.seeder.tsapps/api/test/seeders/payment-method.seeder.tsapps/api/src/billing/routes/stripe-payment-methods/stripe-payment-methods.router.tsapps/api/src/billing/services/stripe-webhook/stripe-webhook.service.tsapps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.tsapps/api/src/billing/services/paying-user/paying-user.tsapps/api/src/billing/services/wallet-initializer/wallet-initializer.service.tsapps/api/src/billing/repositories/user-wallet/user-wallet.repository.tsapps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.tsapps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.tsapps/api/src/core/services/job-queue/job-queue.service.tsapps/api/src/billing/controllers/stripe/stripe.controller.tsapps/api/src/billing/http-schemas/stripe.schema.tsapps/api/src/auth/services/auth.service.tsapps/api/src/core/services/job-queue/job-queue.service.spec.tsapps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.tsapps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.tsapps/api/src/billing/repositories/payment-method/payment-method.repository.tsapps/api/src/billing/model-schemas/payment-method/payment-method.schema.tsapps/api/src/billing/model-schemas/wallet-setting/wallet-setting.schema.tsapps/api/src/billing/services/stripe/stripe.service.tsapps/api/src/billing/services/refill/refill.service.tsapps/api/src/billing/services/balances/balances.service.tsapps/api/src/billing/services/wallet-settings/wallet-settings.service.tsapps/api/src/core/types/require.type.ts
**/*.spec.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/no-jest-mock.mdc)
Don't use
jest.mock()in test files. Instead, usejest-mock-extendedto create mocks and pass mocks as dependencies to the service under testUse
setupfunction instead ofbeforeEachin test files. Thesetupfunction must be at the bottom of the rootdescribeblock, 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>.namein 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/wallet-settings/wallet-settings.service.spec.tsapps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.tsapps/api/src/core/services/job-queue/job-queue.service.spec.ts
🧠 Learnings (9)
📚 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/routes/stripe-payment-methods/stripe-payment-methods.router.tsapps/api/src/billing/controllers/stripe/stripe.controller.tsapps/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/services/stripe-webhook/stripe-webhook.service.tsapps/api/src/billing/services/stripe/stripe.service.ts
📚 Learning: 2025-10-15T16:39:55.348Z
Learnt from: jzsfkzm
Repo: akash-network/console PR: 2039
File: apps/deploy-web/tests/ui/change-wallets.spec.ts:4-10
Timestamp: 2025-10-15T16:39:55.348Z
Learning: In the Akash Console E2E tests using the context-with-extension fixture, the first wallet is automatically created during fixture setup via `importWalletToLeap` in `apps/deploy-web/tests/ui/fixture/wallet-setup.ts`, so tests that call `frontPage.createWallet()` are creating a second wallet to test wallet switching functionality.
Applied to files:
apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.tsapps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts
📚 Learning: 2025-11-25T17:45:52.965Z
Learnt from: CR
Repo: akash-network/console PR: 0
File: .cursor/rules/setup-instead-of-before-each.mdc:0-0
Timestamp: 2025-11-25T17:45:52.965Z
Learning: Applies to **/*.spec.{ts,tsx} : 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.
Applied to files:
apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
📚 Learning: 2025-09-04T04:21:26.067Z
Learnt from: stalniy
Repo: akash-network/console PR: 1868
File: apps/api/src/app/services/trial-deployment-lease-created/trial-deployment-lease-created.handler.ts:73-76
Timestamp: 2025-09-04T04:21:26.067Z
Learning: In apps/api job scheduling with JobQueueService, use .toISOString() for startAfter values instead of Date objects to ensure UTC timezone consistency when dates are persisted to the database. Date objects can be ambiguous about timezone during serialization, while ISO strings with .toISOString() explicitly represent UTC.
Applied to files:
apps/api/src/core/services/job-queue/job-queue.service.tsapps/api/src/core/services/job-queue/job-queue.service.spec.ts
📚 Learning: 2025-09-04T04:21:26.067Z
Learnt from: stalniy
Repo: akash-network/console PR: 1868
File: apps/api/src/app/services/trial-deployment-lease-created/trial-deployment-lease-created.handler.ts:73-76
Timestamp: 2025-09-04T04:21:26.067Z
Learning: The apps/api codebase consistently uses .toISOString() for startAfter values when scheduling jobs with JobQueueService to ensure UTC timezone consistency. This pattern is used throughout trial-started.handler.ts, trial-deployment-lease-created.handler.ts and other job scheduling code. Do not suggest changing to Date objects as ISO strings provide explicit UTC representation for database storage.
Applied to files:
apps/api/src/core/services/job-queue/job-queue.service.tsapps/api/src/core/services/job-queue/job-queue.service.spec.ts
📚 Learning: 2025-11-25T17:45:44.790Z
Learnt from: CR
Repo: akash-network/console PR: 0
File: .cursor/rules/no-jest-mock.mdc:0-0
Timestamp: 2025-11-25T17:45:44.790Z
Learning: Applies to **/*.spec.{ts,tsx} : 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
Applied to files:
apps/api/src/core/services/job-queue/job-queue.service.spec.ts
📚 Learning: 2025-07-30T10:18:24.469Z
Learnt from: baktun14
Repo: akash-network/console PR: 1750
File: apps/api/drizzle/meta/0017_snapshot.json:17-49
Timestamp: 2025-07-30T10:18:24.469Z
Learning: In the Akash Network console billing system, the `user_wallets.user_id` column can be NULL to support anonymous user wallets, allowing users to interact with the system before authentication. Multiple anonymous wallets with NULL user_id values are permitted by design.
Applied to files:
apps/api/drizzle/0023_sad_adam_warlock.sqlapps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.ts
📚 Learning: 2025-09-29T02:40:22.090Z
Learnt from: stalniy
Repo: akash-network/console PR: 1951
File: apps/api/src/user/services/user/user.service.ts:48-63
Timestamp: 2025-09-29T02:40:22.090Z
Learning: In the Akash Network Console codebase, the users table was originally created by Sequelize and later migrated to Drizzle. Due to this migration history, the unique constraint on the userId field is named "userSetting_userId_unique" rather than following the typical "users_user_id_unique" pattern.
Applied to files:
apps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.ts
🧬 Code graph analysis (16)
apps/api/test/seeders/balance.seeder.ts (1)
apps/api/src/billing/http-schemas/balance.schema.ts (1)
GetBalancesResponseOutput(17-17)
apps/api/test/seeders/database-payment-method.seeder.ts (1)
apps/api/src/billing/repositories/payment-method/payment-method.repository.ts (1)
PaymentMethodOutput(11-11)
apps/api/test/seeders/payment-method.seeder.ts (3)
apps/api/src/billing/http-schemas/stripe.schema.ts (1)
PaymentMethod(237-237)apps/api/src/billing/services/stripe/stripe.service.ts (1)
PaymentMethod(33-33)packages/http-sdk/src/stripe/stripe.types.ts (1)
PaymentMethod(30-41)
apps/api/src/billing/routes/stripe-payment-methods/stripe-payment-methods.router.ts (4)
apps/api/src/billing/controllers/stripe/stripe.controller.ts (1)
markAsDefault(46-51)apps/api/src/core/services/openapi-docs/openapi-security.ts (1)
SECURITY_BEARER_OR_API_KEY(5-5)apps/api/src/billing/http-schemas/stripe.schema.ts (1)
PaymentMethodMarkAsDefaultInputSchema(10-14)apps/api/src/billing/repositories/payment-method/payment-method.repository.ts (1)
markAsDefault(83-98)
apps/api/src/billing/services/stripe-webhook/stripe-webhook.service.ts (1)
apps/api/src/billing/services/paying-user/paying-user.ts (1)
assertIsPayingUser(8-10)
apps/api/src/billing/services/paying-user/paying-user.ts (2)
apps/api/src/core/types/require.type.ts (1)
Require(1-3)apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (1)
user(144-169)
apps/api/src/billing/repositories/user-wallet/user-wallet.repository.ts (1)
apps/api/src/core/providers/postgres.provider.ts (1)
ApiPgTables(80-80)
apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts (5)
apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts (1)
walletSetting(144-160)apps/api/test/seeders/user-wallet.seeder.ts (1)
UserWalletSeeder(6-30)apps/api/test/seeders/payment-method.seeder.ts (1)
generatePaymentMethod(11-56)apps/api/src/billing/http-schemas/stripe.schema.ts (1)
PaymentMethod(237-237)apps/api/src/billing/services/stripe/stripe.service.ts (1)
PaymentMethod(33-33)
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts (8)
apps/api/test/seeders/payment-method.seeder.ts (1)
generatePaymentMethod(11-56)apps/api/test/seeders/balance.seeder.ts (1)
generateBalance(21-34)apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (4)
job(66-84)wallet(227-234)error(171-176)user(144-169)apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts (1)
walletSetting(144-160)apps/api/src/billing/events/wallet-balance-reload-check.ts (1)
WalletBalanceReloadCheck(5-15)apps/api/test/seeders/user-wallet.seeder.ts (1)
UserWalletSeeder(6-30)apps/api/test/seeders/user.seeder.ts (1)
UserSeeder(5-45)apps/api/test/seeders/wallet-setting.seeder.ts (1)
generateWalletSetting(5-18)
apps/api/src/core/services/job-queue/job-queue.service.ts (1)
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (1)
job(66-84)
apps/api/src/billing/controllers/stripe/stripe.controller.ts (2)
apps/api/src/auth/services/auth.service.ts (5)
Protected(62-78)currentUser(13-15)currentUser(17-24)ability(44-46)ability(48-50)apps/api/src/billing/http-schemas/stripe.schema.ts (2)
PaymentMethodMarkAsDefaultInput(236-236)PaymentMethodsResponse(238-238)
apps/api/src/billing/repositories/payment-method/payment-method.repository.ts (1)
apps/api/src/core/providers/postgres.provider.ts (1)
ApiPgTables(80-80)
apps/api/src/billing/model-schemas/wallet-setting/wallet-setting.schema.ts (2)
apps/api/src/user/model-schemas/user/user.schema.ts (1)
Users(8-28)apps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.ts (1)
UserWallets(5-18)
apps/api/src/billing/services/stripe/stripe.service.ts (5)
packages/http-sdk/src/stripe/stripe.types.ts (1)
PaymentMethod(30-41)apps/api/src/billing/services/paying-user/paying-user.ts (1)
PayingUser(6-6)apps/api/src/auth/services/auth.service.ts (2)
ability(44-46)ability(48-50)apps/api/src/user/repositories/user/user.repository.ts (1)
UserOutput(10-12)apps/api/src/core/services/tx/tx.service.ts (1)
WithTransaction(33-45)
apps/api/src/billing/services/balances/balances.service.ts (2)
apps/api/src/caching/helpers.ts (1)
Memoize(21-37)apps/api/src/utils/constants.ts (1)
averageBlockTime(4-4)
apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts (3)
apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts (2)
WalletSettingInput(12-17)WalletSettingOutput(19-22)apps/api/src/auth/services/auth.service.ts (2)
ability(44-46)ability(48-50)apps/api/src/core/repositories/base.repository.ts (1)
isUniqueViolation(227-229)
⏰ 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). (2)
- GitHub Check: validate / validate-app
- GitHub Check: validate / validate-app
🔇 Additional comments (24)
apps/api/src/billing/repositories/user-wallet/user-wallet.repository.ts (1)
9-10: Good move to base wallet input types on Drizzle’s inferred insert typeUsing
ApiPgTables["UserWallets"]["$inferInsert"]forDbCreateUserWalletInputand derivingDbUserWalletInputfrom it keeps repository types aligned with the actual schema and avoids hand-maintained duplication when columns change.apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.ts (2)
5-5: Import cleanup and reduced coupling look goodDropping
UserWalletInputfrom the imports and keeping onlyUserWalletPublicOutputandUserWalletRepositorykeeps this service decoupled from repository input types and focused on the minimal surface it actually needs. No issues here.
53-63: Add explicit return type toinitializemethod for API clarity.The parameter change to
stringis correct—all callers includingRefillService.getOrCreateUserWallet(line 95 of refill.service.ts) passstring(specificallyUserWalletOutput["userId"]), not nullable values. However, the method lacks an explicit return type annotation. SinceupdateById(..., { returning: true })returnsPromise<UserWalletOutput>, add the annotation:async initialize(userId: string): Promise<UserWalletOutput> {apps/api/src/billing/services/refill/refill.service.ts (2)
10-11: Explicit service imports instead of barrel are an improvementImporting
ManagedUserWalletServiceandWalletInitializerServicefrom their concrete paths instead of the@src/billing/servicesbarrel makes dependencies clearer and reduces coupling to the barrel module. No functional concerns here.
64-75: The review comment is incorrect – no mismatch exists between the JSDoc and code.Verification confirms the parameter, documentation, and implementation are all consistent:
- JSDoc is correct:
amountUsdis documented as "in USD cents" with example "10000 = $100" ✓- All callers pass amounts in cents:
stripe.service.tspassescoupon.amount_off(Stripe API, always cents)stripe-webhook.service.tspassescheckoutSession.amount_subtotal(Stripe API, always cents)stripe-webhook.service.tspassespaymentIntent.amount(Stripe API, always cents)- Code multiplication is correct: The
* 10000factor converts from cents to microunits (the blockchain's internal unit), where 1 USD = 1,000,000 microunits, thus 1 cent = 10,000 microunits- Test validation confirms: With
amountUsd = 100cents andcurrentLimit = 5000microunits, the expected result is1,005,000microunits (5000 + 100 × 10000) ✓The unit system is consistent throughout: Stripe amounts (cents) → topUpWallet → blockchain limits (microunits).
apps/api/test/seeders/balance.seeder.ts (1)
21-34: LGTM!The
generateBalancehelper follows the established seeder pattern and correctly handles default value computation with proper precision for thetotalfield.apps/api/src/core/services/job-queue/job-queue.service.ts (3)
82-96: LGTM!The
enqueuemethod now properly captures and returns thejobId, enabling callers to track enqueued jobs. Logging includes both the job payload and assigned ID for improved traceability.
145-145: LGTM!Passing job metadata to handlers enables them to access the job ID for logging, scheduling follow-up jobs, or cancellation purposes.
208-214: LGTM!The
JobMetatype and updatedJobHandlersignature provide a clean, minimal interface for job context. UsingPick<PgBoss.Job, "id">ensures type safety with the underlying library.apps/api/src/core/services/job-queue/job-queue.service.spec.ts (4)
68-76: LGTM!Good addition testing the guard against duplicate handler registration. This ensures the error message is properly thrown.
95-100: LGTM!Correctly verifies the updated
JOB_ENQUEUEDlog structure including bothjobandjobId.
151-179: LGTM!The test properly verifies that handlers receive the job metadata
{ id: job.id }as the second parameter, aligning with the updatedJobHandlerinterface.
284-313: LGTM!The
setupfunction follows the coding guidelines: placed at the bottom of the describe block, creates the object under test, returns it with dependencies, uses inline parameter typing, and avoids shared state.apps/api/drizzle/0024_thankful_stick.sql (1)
1-1: LGTM!The migration is safe—the
DEFAULT falseensures existing rows receive a valid value without constraint violations.Consider whether business logic requires exactly one default payment method per user. If so, a partial unique index may be beneficial:
CREATE UNIQUE INDEX payment_methods_user_id_is_default_unique ON payment_methods (user_id) WHERE is_default = true;This would prevent multiple default payment methods for the same user at the database level.
apps/api/drizzle/meta/_journal.json (1)
166-179: LGTM!The migration journal entries are correctly formatted and sequentially ordered. The new entries (idx 23 and 24) properly reference the corresponding migration files for user_wallets user_id constraint and payment_methods is_default column.
apps/api/src/core/types/require.type.ts (1)
1-3: LGTM!The
Requireutility type is correctly implemented. It properly combinesOmit<T, K>to preserve non-required properties with a mapped type using the-?modifier andNonNullableto make the specified keys required and non-nullable.apps/api/drizzle/meta/0023_snapshot.json (1)
1-791: LGTM!The snapshot file is correctly generated and reflects the schema state after migration 0023 (user_id NOT NULL constraint on user_wallets). The structure is consistent with Drizzle ORM's snapshot format.
apps/api/test/seeders/payment-method.seeder.ts (1)
58-61: LGTM!The
generateMergedPaymentMethodhelper is correctly implemented. It properly:
- Destructures the
validatedflag from overrides- Coerces it to a boolean with
!!- Merges it with the base payment method from
generatePaymentMethod- Returns the correct
PaymentMethodtypeapps/api/drizzle/meta/0024_snapshot.json (1)
1-798: LGTM!The snapshot file correctly reflects the schema state after migration 0024, including the new
is_defaultfield on the payment_methods table. The structure is consistent with Drizzle ORM's snapshot format.apps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.ts (1)
7-10: I'll help you verify this review comment about theuserIdNOT NULL constraint change. Let me investigate the codebase to confirm the concerns raised.
<function_calls>
#!/bin/bashFirst, let's verify the current state of the user-wallet schema file
echo "=== Checking user-wallet schema file ==="
cat -n apps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.tsecho ""
echo "=== Line count for reference ==="
wc -l apps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.ts
</function_calls>#!/bin/bash # Search for the migration file mentioned in the review echo "=== Searching for migration file 0023_sad_adam_warlock.sql ===" find . -name "*0023*" -o -name "*sad_adam_warlock*" 2>/dev/null | head -20 echo "" echo "=== Listing all migration files to locate the relevant one ===" fd "\.sql$" apps/api/drizzle/migrations/ 2>/dev/null | sort | tail -10 </parameter> </invoke> </function_calls> ```shell #!/bin/bash # Search for patterns where wallets are created with NULL user_id (anonymous wallets) echo "=== Searching for anonymous wallet creation patterns ===" rg -nP --type=ts "user_id.*null|userId.*null|anonymous.*wallet" -C2 -g '!**/test/**' -g '!**/*.spec.ts' apps/api/src/ echo "" echo "=== Searching for UserWallets insert/create operations ===" rg -nP --type=ts "UserWallets|user_wallets" -C2 -g '!**/test/**' -g '!**/*.spec.ts' apps/api/src/ | head -50 </parameter> </invoke> </function_calls> ```shell #!/bin/bash # Search for any code that handles NULL user_id in wallet context echo "=== Searching for NULL user_id handling ===" rg -nP --type=ts "isNull|notNull.*user" -C3 -g '!**/test/**' apps/api/src/billing/ echo "" echo "=== Check wallet repository for insert/create methods ===" fd -e ".ts" -e ".tsx" wallet apps/api/src/ 2>/dev/null | xargs rg -l "insert|create" 2>/dev/null | head -5 </parameter> </invoke> </function_calls> ```shell #!/bin/bash # Look at the billing directory structure echo "=== Billing directory structure ===" find apps/api/src/billing -type f -name "*.ts" | head -20 echo "" echo "=== Check for wallet-related repositories/services ===" rg -l "UserWallets\|UserWallet" apps/api/src/billing/ --type=ts 2>/dev/null </parameter> </invoke> </function_calls> </blockquote></details> <details> <summary>apps/api/src/billing/services/paying-user/paying-user.ts (1)</summary><blockquote> `6-13`: **PayingUser type + guards look correct and consistent** The Require-based PayingUser type and the `isPayingUser` / `assertIsPayingUser` helpers line up well with the existing `UserOutput` shape and give you clean narrowing at call sites; no issues spotted here. </blockquote></details> <details> <summary>apps/api/src/billing/model-schemas/wallet-setting/wallet-setting.schema.ts (1)</summary><blockquote> `1-1`: **WalletSetting relations match the schema and FKs** The new `WalletSettingRelations` correctly wires `userId` → `Users.id` and `walletId` → `UserWallets.id`, aligning with the table’s foreign keys and the repository’s `with: { wallet, user }` usage. No issues here. Also applies to: 39-48 </blockquote></details> <details> <summary>apps/api/test/seeders/database-payment-method.seeder.ts (1)</summary><blockquote> `3-13`: **Seeder update to PaymentMethodOutput and isDefault looks correct** Referencing `PaymentMethodOutput` directly and seeding `isDefault: false` by default keeps tests aligned with the actual DB schema while still allowing overrides via the `overrides` argument. This is a clean, type-safe change. </blockquote></details> <details> <summary>apps/api/src/billing/services/stripe/stripe.service.ts (1)</summary><blockquote> `33-34`: **New payment-method APIs and idempotent `createPaymentIntent` integration look coherent.** - The exported `PaymentMethod` type and `getPaymentMethods` implementation correctly combine Stripe data with local validation state and sort by recency. - `getDefaultPaymentMethod` and `markPaymentMethodAsDefault` align with the repository’s default-handling helpers and use CASL abilities appropriately; the transactional wrapper plus remote default update cover the typical “local/remote in sync” case. - `hasPaymentMethod` is a simple, correct ownership check used by the controller once the missing `await` is added. - The `createPaymentIntent` refactor to accept an optional `idempotencyKey` and to centralize the Stripe call arguments is clean and matches how you use idempotency from the wallet balance reload handler. Also applies to: 106-138, 140-178, 187-211 </blockquote></details> </blockquote></details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
.../api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts
Show resolved
Hide resolved
71539db to
90b339f
Compare
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (2)
apps/api/src/core/services/job-queue/job-queue.service.spec.ts (1)
80-101: Align enqueue log expectations with narrowed, non-PII payloadThis test currently expects
logger.infoto receive the entirejobobject inJOB_ENQUEUED, which mirrors the PII-risky logging in the service. Once the service is updated to log only metadata (e.g. jobName, version, jobId), this expectation should be adjusted to assert the narrower payload instead of the full job object.For example:
- expect(logger.info).toHaveBeenCalledWith({ - event: "JOB_ENQUEUED", - job, - jobId: "job-id-123" - }); + expect(logger.info).toHaveBeenCalledWith({ + event: "JOB_ENQUEUED", + jobName: job.name, + version: job.version, + jobId: "job-id-123" + });apps/api/src/core/services/job-queue/job-queue.service.ts (1)
83-95: Avoid logging entire job payload inJOB_ENQUEUED(PII risk)
logger.infocurrently logs the fulljobobject, which for many jobs will contain user identifiers or message content. Prefer logging only non-sensitive metadata (e.g. job name, version, and jobId) to avoid leaking PII at info level.A minimal change would look like:
- this.logger.info({ - event: "JOB_ENQUEUED", - job, - jobId - }); + this.logger.info({ + event: "JOB_ENQUEUED", + jobName: job.name, + version: job.version, + jobId + });Tests expecting the log payload should be updated accordingly.
🧹 Nitpick comments (6)
apps/api/src/core/repositories/base.repository.ts (1)
8-67: Consider delegating new transactions toTxServiceto keepcursor‑based code transactional
ensureTransactioncorrectly reuses an existing tx fromtxManager.getPgTx(), but in the fallback branch it starts a rawthis.pg.transactionwithout updatingTxService/AsyncLocalStorage. That means any repository calls insidecbthat rely onthis.cursor(or other code usingTxService.getPgTx()) won’t run inside the same transaction whenensureTransactionhas to create one.To make this helper safer and future‑proof, you can delegate transaction creation to
TxService.transaction, then read the tx back fromgetPgTx():- protected async ensureTransaction<T>(cb: (tx: ApiTransaction) => Promise<T>) { - const txCursor = this.txManager.getPgTx(); - - if (txCursor) { - return await cb(txCursor); - } - - return await this.pg.transaction(async tx => await cb(tx)); - } + protected async ensureTransaction<T>(cb: (tx: ApiTransaction) => Promise<T>) { + const existingTx = this.txManager.getPgTx(); + if (existingTx) { + return cb(existingTx); + } + + return this.txManager.transaction(async () => { + const tx = this.txManager.getPgTx(); + if (!tx) { + throw new Error("Transaction was not initialized"); + } + return cb(tx); + }); + }This way, both explicit
txusage andthis.cursor/TxServiceconsumers share the same transaction context.apps/api/src/billing/services/balances/balances.service.ts (1)
9-21: Fiat balance computation looks correct; consider includingisOldWalletin memoization keyThe new
getFullBalanceInFiatflow—reusinggetFullBalance, pulling market data, then normalizing via/ 1_000_000and rounding to 2 decimals—produces the expected fiat values for u‑denominated balances. The StatsService integration and return type (GetBalancesResponseOutput["data"]) also line up.One nuance:
@Memoizecurrently only keys on string/number arguments, so theisOldWalletboolean parameter is ignored for bothgetFullBalanceandgetFullBalanceInFiat. If the same address can be queried with differentisOldWalletvalues, those calls will share a cache entry. If that’s not intended, consider customizing the memoization key to incorporateisOldWallet(e.g., pass akeyoption or stringify the boolean in a wrapper).Also applies to: 96-130
apps/api/src/billing/services/stripe-webhook/stripe-webhook.service.ts (1)
6-6: Default payment method handling and paying-user guard are well integrated (watch concurrency edge cases)The updated
handlePaymentMethodAttachedflow looks solid:
- It derives
isDefaultfromcountByUserId, making the first method default.- It asserts a paying user before persisting anything, so non‑paying users can’t accumulate payment methods.
- It performs DB creation and (when applicable) remote default promotion in parallel, and logs
fingerprint/isDefaultfor better observability.One thing to keep in mind is concurrent
payment_method.attachedevents for the same user: multiple in‑flight transactions could each seecount === 0and try to mark different methods as default. If this becomes an issue in practice, adding a DB‑level uniqueness constraint for the default flag or a serialized “make default” path would close that gap.Also applies to: 144-157, 163-165
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (1)
21-23: Avoid duplicating theRequirehelper and consider toning down the “pending” logYou re‑implement a generic
Requiretype locally and use it forInitializedWallet/ActionableWalletSetting. Prefer importing the shared helper (e.g. from@src/core/types/require.type) to keep this consistent and avoid type drift. Also, theCALCULATING_NEXT_CHECK_DATElog with"Implementation pending..."will fire on every scheduling; once the heuristic is decided, consider removing or updating that note to avoid long‑term noise.Also applies to: 25-36, 230-237
apps/api/src/billing/repositories/payment-method/payment-method.repository.ts (1)
83-100: Consider simplifyingcreateAsDefaultflow inside the transactionWithin
ensureTransaction,createAsDefaultrunsthis.createand#unmarkAsDefaultExcludingviaPromise.all. Given you don’t depend on parallelism here and both operate on the same logical record set, a simple sequential update (unmark others, then create the new default, or vice versa) would make the intent clearer and easier to reason about, while still benefiting from the transaction wrapper.Also applies to: 102-116, 118-126
apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts (1)
13-17: Minor service-level type and transaction-usage refinementsTwo small maintainability tweaks you might consider:
- The local
WalletSettingInputinterface here overlaps in name with the repository‑levelWalletSettingInputtype but has a different shape. Renaming this service‑input (or importing and building on the repo type) would reduce confusion between the two layers.upsertWalletSettingis decorated with@WithTransaction()and#schedulealso wraps work inthis.txService.transaction. IfTxServicealready collapses nested transactions onto a single underlying one (which is likely), you could potentially rely on just one of these mechanisms to simplify mental model and avoid redundant nesting.Both are non‑blocking and can be revisited later.
Also applies to: 19-29, 145-163
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
apps/api/test/functional/__snapshots__/docs.spec.ts.snapis excluded by!**/*.snap
📒 Files selected for processing (35)
apps/api/drizzle/0023_sad_adam_warlock.sql(1 hunks)apps/api/drizzle/0024_thankful_stick.sql(1 hunks)apps/api/drizzle/meta/0023_snapshot.json(1 hunks)apps/api/drizzle/meta/0024_snapshot.json(1 hunks)apps/api/drizzle/meta/_journal.json(1 hunks)apps/api/src/auth/services/auth.service.ts(2 hunks)apps/api/src/billing/controllers/stripe/stripe.controller.ts(2 hunks)apps/api/src/billing/http-schemas/stripe.schema.ts(2 hunks)apps/api/src/billing/http-schemas/wallet.schema.ts(0 hunks)apps/api/src/billing/model-schemas/payment-method/payment-method.schema.ts(1 hunks)apps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.ts(1 hunks)apps/api/src/billing/model-schemas/wallet-setting/wallet-setting.schema.ts(2 hunks)apps/api/src/billing/repositories/payment-method/payment-method.repository.ts(2 hunks)apps/api/src/billing/repositories/user-wallet/user-wallet.repository.ts(2 hunks)apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts(1 hunks)apps/api/src/billing/routes/stripe-payment-methods/stripe-payment-methods.router.ts(5 hunks)apps/api/src/billing/services/balances/balances.service.ts(3 hunks)apps/api/src/billing/services/paying-user/paying-user.ts(1 hunks)apps/api/src/billing/services/refill/refill.service.ts(1 hunks)apps/api/src/billing/services/stripe-webhook/stripe-webhook.service.ts(2 hunks)apps/api/src/billing/services/stripe/stripe.service.ts(6 hunks)apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts(1 hunks)apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts(2 hunks)apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.ts(2 hunks)apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts(4 hunks)apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts(5 hunks)apps/api/src/core/repositories/base.repository.ts(2 hunks)apps/api/src/core/services/job-queue/job-queue.service.spec.ts(9 hunks)apps/api/src/core/services/job-queue/job-queue.service.ts(3 hunks)apps/api/src/core/services/tx/tx.service.ts(1 hunks)apps/api/src/core/types/require.type.ts(1 hunks)apps/api/test/seeders/balance.seeder.ts(2 hunks)apps/api/test/seeders/database-payment-method.seeder.ts(1 hunks)apps/api/test/seeders/payment-method.seeder.ts(2 hunks)packages/http-sdk/src/stripe/stripe.service.ts(0 hunks)
💤 Files with no reviewable changes (2)
- packages/http-sdk/src/stripe/stripe.service.ts
- apps/api/src/billing/http-schemas/wallet.schema.ts
✅ Files skipped from review due to trivial changes (1)
- apps/api/drizzle/meta/0023_snapshot.json
🚧 Files skipped from review as they are similar to previous changes (15)
- apps/api/src/billing/http-schemas/stripe.schema.ts
- apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.ts
- apps/api/drizzle/meta/_journal.json
- apps/api/src/billing/model-schemas/payment-method/payment-method.schema.ts
- apps/api/src/billing/routes/stripe-payment-methods/stripe-payment-methods.router.ts
- apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts
- apps/api/src/core/types/require.type.ts
- apps/api/test/seeders/balance.seeder.ts
- apps/api/test/seeders/payment-method.seeder.ts
- apps/api/src/billing/controllers/stripe/stripe.controller.ts
- apps/api/drizzle/0024_thankful_stick.sql
- apps/api/src/billing/model-schemas/wallet-setting/wallet-setting.schema.ts
- apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts
- apps/api/drizzle/0023_sad_adam_warlock.sql
- apps/api/src/billing/services/refill/refill.service.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (.cursor/rules/general.mdc)
**/*.{ts,tsx,js}: Never use typeanyor cast to typeany. Always define the proper TypeScript types.
Never use deprecated methods from libraries.
Don't add unnecessary comments to the code.
Files:
apps/api/src/core/services/tx/tx.service.tsapps/api/src/core/repositories/base.repository.tsapps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.tsapps/api/src/billing/services/balances/balances.service.tsapps/api/src/billing/services/paying-user/paying-user.tsapps/api/test/seeders/database-payment-method.seeder.tsapps/api/src/auth/services/auth.service.tsapps/api/src/billing/services/stripe-webhook/stripe-webhook.service.tsapps/api/src/core/services/job-queue/job-queue.service.tsapps/api/src/billing/repositories/user-wallet/user-wallet.repository.tsapps/api/src/core/services/job-queue/job-queue.service.spec.tsapps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.tsapps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.tsapps/api/src/billing/repositories/payment-method/payment-method.repository.tsapps/api/src/billing/services/wallet-settings/wallet-settings.service.tsapps/api/src/billing/services/stripe/stripe.service.ts
**/*.spec.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/no-jest-mock.mdc)
Don't use
jest.mock()in test files. Instead, usejest-mock-extendedto create mocks and pass mocks as dependencies to the service under testUse
setupfunction instead ofbeforeEachin test files. Thesetupfunction must be at the bottom of the rootdescribeblock, 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>.namein 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/core/services/job-queue/job-queue.service.spec.tsapps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
🧠 Learnings (11)
📚 Learning: 2025-11-28T09:17:28.821Z
Learnt from: ygrishajev
Repo: akash-network/console PR: 2274
File: apps/api/drizzle/0023_sad_adam_warlock.sql:1-1
Timestamp: 2025-11-28T09:17:28.821Z
Learning: In the Akash Network console billing system, the `user_wallets.user_id` column is required (NOT NULL). All user wallets must be associated with a user ID, and there are no anonymous wallets without a user_id in production.
Applied to files:
apps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.ts
📚 Learning: 2025-11-28T09:21:45.580Z
Learnt from: ygrishajev
Repo: akash-network/console PR: 2274
File: apps/api/drizzle/0023_sad_adam_warlock.sql:1-1
Timestamp: 2025-11-28T09:21:45.580Z
Learning: In the Akash Network console, `userSettings.userId` can be NULL for anonymous users, but `user_wallets.user_id` is always required (NOT NULL). Anonymous users are identified by userId=null in the userSettings table, not in the user_wallets table.
Applied to files:
apps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.ts
📚 Learning: 2025-09-29T02:40:22.090Z
Learnt from: stalniy
Repo: akash-network/console PR: 1951
File: apps/api/src/user/services/user/user.service.ts:48-63
Timestamp: 2025-09-29T02:40:22.090Z
Learning: In the Akash Network Console codebase, the users table was originally created by Sequelize and later migrated to Drizzle. Due to this migration history, the unique constraint on the userId field is named "userSetting_userId_unique" rather than following the typical "users_user_id_unique" pattern.
Applied to files:
apps/api/src/billing/model-schemas/user-wallet/user-wallet.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/services/stripe-webhook/stripe-webhook.service.tsapps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.tsapps/api/src/billing/services/stripe/stripe.service.ts
📚 Learning: 2025-09-04T04:21:26.067Z
Learnt from: stalniy
Repo: akash-network/console PR: 1868
File: apps/api/src/app/services/trial-deployment-lease-created/trial-deployment-lease-created.handler.ts:73-76
Timestamp: 2025-09-04T04:21:26.067Z
Learning: In apps/api job scheduling with JobQueueService, use .toISOString() for startAfter values instead of Date objects to ensure UTC timezone consistency when dates are persisted to the database. Date objects can be ambiguous about timezone during serialization, while ISO strings with .toISOString() explicitly represent UTC.
Applied to files:
apps/api/src/core/services/job-queue/job-queue.service.tsapps/api/src/core/services/job-queue/job-queue.service.spec.ts
📚 Learning: 2025-09-04T04:21:26.067Z
Learnt from: stalniy
Repo: akash-network/console PR: 1868
File: apps/api/src/app/services/trial-deployment-lease-created/trial-deployment-lease-created.handler.ts:73-76
Timestamp: 2025-09-04T04:21:26.067Z
Learning: The apps/api codebase consistently uses .toISOString() for startAfter values when scheduling jobs with JobQueueService to ensure UTC timezone consistency. This pattern is used throughout trial-started.handler.ts, trial-deployment-lease-created.handler.ts and other job scheduling code. Do not suggest changing to Date objects as ISO strings provide explicit UTC representation for database storage.
Applied to files:
apps/api/src/core/services/job-queue/job-queue.service.spec.ts
📚 Learning: 2025-11-25T17:45:44.790Z
Learnt from: CR
Repo: akash-network/console PR: 0
File: .cursor/rules/no-jest-mock.mdc:0-0
Timestamp: 2025-11-25T17:45:44.790Z
Learning: Applies to **/*.spec.{ts,tsx} : 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
Applied to files:
apps/api/src/core/services/job-queue/job-queue.service.spec.ts
📚 Learning: 2025-10-31T11:26:42.138Z
Learnt from: stalniy
Repo: akash-network/console PR: 2138
File: apps/api/src/billing/lib/batch-signing-client/batch-signing-client.service.ts:379-382
Timestamp: 2025-10-31T11:26:42.138Z
Learning: In TypeScript/JavaScript, the pattern of checking a cached value and then performing an async operation to fetch it without proper synchronization is race condition unsafe:
```typescript
private async getAddress() {
if (!this.address) {
this.address = await this.wallet.getFirstAddress();
}
return this.address;
}
```
Multiple concurrent calls can all pass the `if (!this.address)` check before any of them sets the value, leading to duplicate async operations. This should be flagged as a race condition. Proper synchronization (mutex, atomic promise caching, or guaranteed single-threaded execution) is required.
Applied to files:
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.tsapps/api/src/billing/repositories/payment-method/payment-method.repository.tsapps/api/src/billing/services/stripe/stripe.service.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/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts
📚 Learning: 2025-10-15T16:39:55.348Z
Learnt from: jzsfkzm
Repo: akash-network/console PR: 2039
File: apps/deploy-web/tests/ui/change-wallets.spec.ts:4-10
Timestamp: 2025-10-15T16:39:55.348Z
Learning: In the Akash Console E2E tests using the context-with-extension fixture, the first wallet is automatically created during fixture setup via `importWalletToLeap` in `apps/deploy-web/tests/ui/fixture/wallet-setup.ts`, so tests that call `frontPage.createWallet()` are creating a second wallet to test wallet switching functionality.
Applied to files:
apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
📚 Learning: 2025-11-25T17:45:52.965Z
Learnt from: CR
Repo: akash-network/console PR: 0
File: .cursor/rules/setup-instead-of-before-each.mdc:0-0
Timestamp: 2025-11-25T17:45:52.965Z
Learning: Applies to **/*.spec.{ts,tsx} : 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.
Applied to files:
apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
🧬 Code graph analysis (13)
apps/api/src/core/repositories/base.repository.ts (1)
apps/api/src/core/services/tx/tx.service.ts (1)
ApiTransaction(11-11)
apps/api/src/billing/services/balances/balances.service.ts (3)
apps/api/src/caching/helpers.ts (1)
Memoize(21-37)apps/api/src/utils/constants.ts (1)
averageBlockTime(4-4)apps/api/src/billing/http-schemas/balance.schema.ts (1)
GetBalancesResponseOutput(17-17)
apps/api/src/billing/services/paying-user/paying-user.ts (2)
apps/api/src/core/types/require.type.ts (1)
Require(1-3)apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (1)
user(145-169)
apps/api/test/seeders/database-payment-method.seeder.ts (1)
apps/api/src/billing/repositories/payment-method/payment-method.repository.ts (1)
PaymentMethodOutput(11-11)
apps/api/src/auth/services/auth.service.ts (1)
apps/api/src/billing/services/paying-user/paying-user.ts (3)
PayingUser(6-6)assertIsPayingUser(8-10)isPayingUser(12-14)
apps/api/src/billing/services/stripe-webhook/stripe-webhook.service.ts (1)
apps/api/src/billing/services/paying-user/paying-user.ts (1)
assertIsPayingUser(8-10)
apps/api/src/billing/repositories/user-wallet/user-wallet.repository.ts (1)
apps/api/src/core/providers/postgres.provider.ts (1)
ApiPgTables(80-80)
apps/api/src/core/services/job-queue/job-queue.service.spec.ts (1)
apps/api/src/core/services/job-queue/job-queue.service.ts (2)
setup(175-182)Job(189-197)
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (8)
apps/api/src/core/types/require.type.ts (1)
Require(1-3)apps/api/src/billing/repositories/user-wallet/user-wallet.repository.ts (1)
UserWalletOutput(18-22)apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts (1)
WalletSettingOutput(19-22)apps/api/src/billing/services/paying-user/paying-user.ts (2)
PayingUser(6-6)isPayingUser(12-14)apps/api/src/billing/http-schemas/balance.schema.ts (1)
GetBalancesResponseOutput(17-17)apps/api/src/billing/http-schemas/stripe.schema.ts (1)
PaymentMethod(237-237)packages/http-sdk/src/stripe/stripe.service.ts (1)
StripeService(18-94)apps/api/src/billing/events/wallet-balance-reload-check.ts (1)
WalletBalanceReloadCheck(5-15)
apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts (4)
apps/api/test/seeders/payment-method.seeder.ts (1)
generatePaymentMethod(11-56)packages/http-sdk/src/stripe/stripe.service.ts (1)
StripeService(18-94)apps/api/src/billing/http-schemas/stripe.schema.ts (1)
PaymentMethod(237-237)apps/api/src/billing/services/stripe/stripe.service.ts (1)
PaymentMethod(33-33)
apps/api/src/billing/repositories/payment-method/payment-method.repository.ts (2)
apps/api/src/core/providers/postgres.provider.ts (1)
ApiPgTables(80-80)apps/api/src/core/services/tx/tx.service.ts (1)
ApiTransaction(11-11)
apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts (3)
apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts (2)
WalletSettingInput(12-17)WalletSettingOutput(19-22)apps/api/src/auth/services/auth.service.ts (2)
ability(44-46)ability(48-50)apps/api/src/core/repositories/base.repository.ts (1)
isUniqueViolation(237-239)
apps/api/src/billing/services/stripe/stripe.service.ts (7)
apps/api/src/billing/http-schemas/stripe.schema.ts (2)
PaymentMethod(237-237)PaymentIntentResult(240-240)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 (1)
user(145-169)apps/api/src/billing/services/paying-user/paying-user.ts (1)
PayingUser(6-6)apps/api/src/auth/services/auth.service.ts (2)
ability(44-46)ability(48-50)apps/api/src/user/repositories/user/user.repository.ts (1)
UserOutput(10-12)apps/api/src/core/services/tx/tx.service.ts (1)
WithTransaction(39-51)
⏰ 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: test-build
- GitHub Check: validate / validate-app
- GitHub Check: test-build
- GitHub Check: test-build
- GitHub Check: validate / validate-app
- GitHub Check: Validate local packages
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (19)
apps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.ts (1)
9-10: Schema change aligns with confirmed architecture.Based on retrieved learnings, the
user_wallets.user_idNOT NULL constraint is intentional and consistent with the system's design where all wallets must be associated with a user. Anonymous users (identified byuserId=nullinuserSettings) do not have wallet records inuser_wallets.The change is properly reflected in migration 0023 and repository methods, as verified in the previous review.
apps/api/drizzle/meta/0024_snapshot.json (1)
1-798: Generated snapshot file is consistent with migrations.The snapshot correctly reflects:
- user_wallets.user_id with
notNull: true(migration 0023)- payment_methods.is_default with boolean type, NOT NULL constraint, and false default (migration 0024)
- Proper foreign key relationships with cascade deletes
- wallet_settings table supporting the auto-reload feature with appropriate nullable fields for optional configuration
The metadata structure is valid and consistent with the schema changes in this PR.
apps/api/src/core/services/job-queue/job-queue.service.ts (2)
119-158: PassingJobMetainto handlers is a solid extension of the APIWiring
handler.handle(job.data, { id: job.id })through the worker callback cleanly exposes the job id to handlers without expanding the payload type, and remains runtime-backwards compatible for handlers that ignore the second parameter.
208-215:JobMetaalias keeps handler context minimal and well-typedDefining
JobMetaas a minimal{ id }projection and updatingJobHandler.handleto accept(payload, job: JobMeta)provides enough context for logging/diagnostics while keeping the handler contract small and focused.apps/api/src/core/services/job-queue/job-queue.service.spec.ts (5)
22-25: Queue registration tests now robustly cover config and duplicate handlersThe expectations for
retryBackoff,retryDelayMax,retryLimit, andpolicyplus the new test that rejects when two handlers register the same queue give good protection around queue setup behavior and prevent accidental multiple-handler registration for a single queue.Also applies to: 55-58, 68-76
122-137: Cancel logging expectations match updated log shapeThe
canceltest correctly asserts theJOB_CANCELLEDlog withidandnamefields, in line with the service change; nothing further needed here.
146-207: Worker tests give solid coverage of success and failure paths with job metaThe
startWorkerstests:
- Simulate processing a single job object via the mocked
workhandler.- Verify
JOB_STARTED,JOB_DONE, andJOB_FAILEDlogs usejob.id.- Assert that handlers are invoked with the payload plus
{ id: job.id }.- Check concurrency wiring by asserting
workcall counts.This provides good confidence in the worker orchestration and the new
JobMeta-aware handler contract.
209-227: Default worker options test correctly validates implicit concurrencyThe
"uses default options when none provided"test cleanly verifies that two workers are started (default concurrency) and that each invokes the handler with payload + job meta once, which accurately captures the intended default behavior.
284-303: TypedrunWithContextstub accurately simulates execution context behaviorStubbing
runWithContextasasync (cb: () => Promise<unknown>) => await cb()and typing it asExecutionContextService["runWithContext"]both matches the real signature and ensures the callback is actually executed in tests, which keeps behavior realistic without introducing shared state.apps/api/src/core/services/tx/tx.service.ts (1)
11-24: ApiTransaction alias and nested-transaction reuse look goodCentralizing the
PgTransactiontyping intoApiTransactionand short‑circuiting when a tx is already inAsyncLocalStoragekeeps nestedWithTransactioncalls cheap and type‑safe. No issues here.apps/api/src/billing/repositories/user-wallet/user-wallet.repository.ts (1)
9-11: Stronger insert typing for user wallets is aligned with the schemaBasing
DbCreateUserWalletInputon$inferInsertand tighteningcreateto requireuserIdandaddressmatches the new NOT NULL constraint onuser_idand keeps the repo API close to the actual table shape. TheUserWalletInput→DbUserWalletInputmapping still looks coherent.Also applies to: 72-83
apps/api/src/billing/services/paying-user/paying-user.ts (1)
1-14: PayingUser type and guards are clean and composableThe
Require<UserOutput, "stripeCustomerId">alias plusisPayingUser/assertIsPayingUsergive a nice, reusable way to enforce “paying user” semantics both at runtime and in types. The 402 status/message also clearly expresses the billing prerequisite.apps/api/test/seeders/database-payment-method.seeder.ts (1)
3-6: Seeder now matches the real payment method output shapeTyping the seeder against
PaymentMethodOutputand seedingisDefault: falsekeeps test fixtures in sync with the repository’s output type and the newisDefaultcolumn. This should reduce drift between tests and the actual DB model.Also applies to: 12-12
apps/api/src/auth/services/auth.service.ts (1)
5-5: Paying-user accessor overloads are sound and preserve auth semanticsThe
getCurrentPayingUseroverloads make the “strict by default, optional non‑strict” behavior clear, and the implementation correctly:
- 401s when there is no current user, regardless of
strict.- Uses
assertIsPayingUserwhenstrictis true.- Falls back to
undefinedonly for non‑paying users whenstrictis false.This keeps error semantics predictable while giving callers a flexible way to gate on paying users.
Also applies to: 26-42
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (1)
56-85: Wallet balance reload orchestration and error handling look solidThe end‑to‑end flow (resource collection, validation, reload attempt, logging, and rescheduling) is cohesive and well‑guarded with typed
Result/ValidationErrorpaths, idempotent Stripe charge creation, and structured logging for all decision branches. No functional issues spotted here.Also applies to: 179-217
apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts (1)
10-12: Test setup correctly reflects new Stripe and user dependenciesThe updated
setupwiring (paying user viauserWithStripe,userRepository.findById,stripeService.getDefaultPaymentMethod, andtxService.transactionpassthrough) aligns with the new service constructor and#validatelogic, so specs now properly cover the “enabled auto‑reload requires a default payment method” behavior without leaking real Stripe calls.Also applies to: 15-16, 279-332
apps/api/src/billing/services/stripe/stripe.service.ts (2)
147-174: Improved default-payment-method update ordering looks correct
markPaymentMethodAsDefaultnow performs local repo updates inside a transaction and only callsmarkRemotePaymentMethodAsDefaultafter local state is established, avoiding the previous pattern of mixing the external Stripe update into the initialPromise.all. This reduces the window where Stripe and the DB can diverge due to mid-flight failures while keeping the API surface clean.Also applies to: 176-176
182-213: I'll verify the concerns aboutcreatePaymentIntentcallers passing amounts in the correct units. Let me start by finding all usages of this method and analyzing how amounts are computed.
<function_calls>
<function_calls_item>
<function_calls_invoke name="shell">
#!/bin/bashFind all createPaymentIntent usages and their context
rg -n "createPaymentIntent\s*(" apps -A 5 -B 2 --type ts --type tsx
</function_calls_item>
</function_calls>
</function_calls_invoke>apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts (1)
45-58: Auto-reload validation and scheduling logic are cohesive and consistentThe
upsertWalletSettingflow (#update/#create+#validate+#arrangeSchedule) looks well-structured:
- Enforces
autoReloadThreshold/autoReloadAmountpresence only when enabling auto‑reload.- Ensures the user exists, has a
stripeCustomerId, and has a default payment method viaStripeService.getDefaultPaymentMethodbefore allowing auto‑reload.- Handles race-y create vs. existing row via
isUniqueViolationand a retry through#update.- Schedules/cancels the
WalletBalanceReloadCheckjob in a single place based onautoReloadEnabledtransitions, keepingautoReloadJobIdin sync.No functional issues stand out in this path.
Also applies to: 60-77, 79-107, 109-133, 135-143, 145-163
90b339f to
47469f8
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
apps/api/src/core/services/job-queue/job-queue.service.ts (1)
83-95: Avoid logging full job payload; log non‑sensitive metadata only
JOB_ENQUEUEDis still logging the entirejobobject, which can include PII or other sensitive payload fields atinfolevel. This repeats the concern from the earlier review and should be tightened.Recommend logging just minimal metadata (e.g., name, id, version) instead of the full payload:
async enqueue(job: Job, options?: EnqueueOptions): Promise<string | null> { const jobId = await this.pgBoss.send({ name: job.name, data: { ...job.data, version: job.version }, options }); - this.logger.info({ - event: "JOB_ENQUEUED", - job, - jobId - }); + this.logger.info({ + event: "JOB_ENQUEUED", + jobName: job.name, + jobId, + version: job.version + }); return jobId; }
🧹 Nitpick comments (3)
apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.ts (1)
53-63: Consider adding an explicit return type and aligning with public DTOs
initialize(userId: string)currently has an implicit return type derived fromupdateById(..., { returning: true }), which is likely the internal entity, whileinitializeAndGrantTrialLimitsreturnsUserWalletPublicOutput. For API clarity and consistency, consider:
- Declaring an explicit return type for
initialize(either the entity orUserWalletPublicOutput), and- Potentially returning
toPublic(...)here as well if this method is used at the API boundary.This is not a blocker but would make the service contract clearer and more robust to repository-level changes.
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (1)
15-23: Reuse sharedRequireutility instead of redefining locallyYou’re re‑defining a
Requirehelper that already exists inapps/api/src/core/types/require.type.ts. Importing and reusing the shared type would avoid duplication and keep the semantics consistent across the codebase.apps/api/src/billing/repositories/payment-method/payment-method.repository.ts (1)
83-126: Default‑flag updates are now scoped in a transaction, with a minor style tweakWrapping
markAsDefaultandcreateAsDefaultinensureTransactionand centralizing the “unmark others” logic in#unmarkAsDefaultExcludingensures consistent default status per user at commit time.In
createAsDefault, you might want to use the providedtxfor the insert as well (instead ofthis.create) so all DB writes in the callback clearly share the same transactional context.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
apps/api/test/functional/__snapshots__/docs.spec.ts.snapis excluded by!**/*.snap
📒 Files selected for processing (35)
apps/api/drizzle/0023_sad_adam_warlock.sql(1 hunks)apps/api/drizzle/0024_thankful_stick.sql(1 hunks)apps/api/drizzle/meta/0023_snapshot.json(1 hunks)apps/api/drizzle/meta/0024_snapshot.json(1 hunks)apps/api/drizzle/meta/_journal.json(1 hunks)apps/api/src/auth/services/auth.service.ts(2 hunks)apps/api/src/billing/controllers/stripe/stripe.controller.ts(2 hunks)apps/api/src/billing/http-schemas/stripe.schema.ts(2 hunks)apps/api/src/billing/http-schemas/wallet.schema.ts(0 hunks)apps/api/src/billing/model-schemas/payment-method/payment-method.schema.ts(1 hunks)apps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.ts(1 hunks)apps/api/src/billing/model-schemas/wallet-setting/wallet-setting.schema.ts(2 hunks)apps/api/src/billing/repositories/payment-method/payment-method.repository.ts(2 hunks)apps/api/src/billing/repositories/user-wallet/user-wallet.repository.ts(2 hunks)apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts(1 hunks)apps/api/src/billing/routes/stripe-payment-methods/stripe-payment-methods.router.ts(5 hunks)apps/api/src/billing/services/balances/balances.service.ts(3 hunks)apps/api/src/billing/services/paying-user/paying-user.ts(1 hunks)apps/api/src/billing/services/refill/refill.service.ts(1 hunks)apps/api/src/billing/services/stripe-webhook/stripe-webhook.service.ts(2 hunks)apps/api/src/billing/services/stripe/stripe.service.ts(6 hunks)apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts(1 hunks)apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts(2 hunks)apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.ts(2 hunks)apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts(4 hunks)apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts(5 hunks)apps/api/src/core/repositories/base.repository.ts(2 hunks)apps/api/src/core/services/job-queue/job-queue.service.spec.ts(9 hunks)apps/api/src/core/services/job-queue/job-queue.service.ts(3 hunks)apps/api/src/core/services/tx/tx.service.ts(1 hunks)apps/api/src/core/types/require.type.ts(1 hunks)apps/api/test/seeders/balance.seeder.ts(2 hunks)apps/api/test/seeders/database-payment-method.seeder.ts(1 hunks)apps/api/test/seeders/payment-method.seeder.ts(2 hunks)packages/http-sdk/src/stripe/stripe.service.ts(0 hunks)
💤 Files with no reviewable changes (2)
- packages/http-sdk/src/stripe/stripe.service.ts
- apps/api/src/billing/http-schemas/wallet.schema.ts
🚧 Files skipped from review as they are similar to previous changes (17)
- apps/api/src/billing/repositories/user-wallet/user-wallet.repository.ts
- apps/api/test/seeders/payment-method.seeder.ts
- apps/api/src/core/services/tx/tx.service.ts
- apps/api/src/billing/services/paying-user/paying-user.ts
- apps/api/drizzle/meta/0023_snapshot.json
- apps/api/test/seeders/balance.seeder.ts
- apps/api/drizzle/0024_thankful_stick.sql
- apps/api/src/core/types/require.type.ts
- apps/api/src/billing/model-schemas/wallet-setting/wallet-setting.schema.ts
- apps/api/src/core/repositories/base.repository.ts
- apps/api/drizzle/meta/_journal.json
- apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts
- apps/api/src/billing/services/refill/refill.service.ts
- apps/api/test/seeders/database-payment-method.seeder.ts
- apps/api/drizzle/meta/0024_snapshot.json
- apps/api/drizzle/0023_sad_adam_warlock.sql
- apps/api/src/core/services/job-queue/job-queue.service.spec.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (.cursor/rules/general.mdc)
**/*.{ts,tsx,js}: Never use typeanyor cast to typeany. 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.tsapps/api/src/billing/services/balances/balances.service.tsapps/api/src/billing/routes/stripe-payment-methods/stripe-payment-methods.router.tsapps/api/src/billing/model-schemas/payment-method/payment-method.schema.tsapps/api/src/billing/services/stripe-webhook/stripe-webhook.service.tsapps/api/src/billing/controllers/stripe/stripe.controller.tsapps/api/src/core/services/job-queue/job-queue.service.tsapps/api/src/billing/services/wallet-initializer/wallet-initializer.service.tsapps/api/src/billing/repositories/payment-method/payment-method.repository.tsapps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.tsapps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.tsapps/api/src/billing/services/stripe/stripe.service.tsapps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.tsapps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.tsapps/api/src/auth/services/auth.service.tsapps/api/src/billing/services/wallet-settings/wallet-settings.service.ts
**/*.spec.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/no-jest-mock.mdc)
Don't use
jest.mock()in test files. Instead, usejest-mock-extendedto create mocks and pass mocks as dependencies to the service under testUse
setupfunction instead ofbeforeEachin test files. Thesetupfunction must be at the bottom of the rootdescribeblock, 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>.namein 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/wallet-settings/wallet-settings.service.spec.ts
🧠 Learnings (8)
📚 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.tsapps/api/src/billing/routes/stripe-payment-methods/stripe-payment-methods.router.tsapps/api/src/billing/controllers/stripe/stripe.controller.tsapps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.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/services/stripe-webhook/stripe-webhook.service.tsapps/api/src/billing/controllers/stripe/stripe.controller.tsapps/api/src/billing/services/stripe/stripe.service.tsapps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts
📚 Learning: 2025-10-31T11:26:42.138Z
Learnt from: stalniy
Repo: akash-network/console PR: 2138
File: apps/api/src/billing/lib/batch-signing-client/batch-signing-client.service.ts:379-382
Timestamp: 2025-10-31T11:26:42.138Z
Learning: In TypeScript/JavaScript, the pattern of checking a cached value and then performing an async operation to fetch it without proper synchronization is race condition unsafe:
```typescript
private async getAddress() {
if (!this.address) {
this.address = await this.wallet.getFirstAddress();
}
return this.address;
}
```
Multiple concurrent calls can all pass the `if (!this.address)` check before any of them sets the value, leading to duplicate async operations. This should be flagged as a race condition. Proper synchronization (mutex, atomic promise caching, or guaranteed single-threaded execution) is required.
Applied to files:
apps/api/src/billing/controllers/stripe/stripe.controller.tsapps/api/src/billing/repositories/payment-method/payment-method.repository.tsapps/api/src/billing/services/stripe/stripe.service.tsapps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts
📚 Learning: 2025-11-28T09:17:28.821Z
Learnt from: ygrishajev
Repo: akash-network/console PR: 2274
File: apps/api/drizzle/0023_sad_adam_warlock.sql:1-1
Timestamp: 2025-11-28T09:17:28.821Z
Learning: In the Akash Network console billing system, the `user_wallets.user_id` column is required (NOT NULL). All user wallets must be associated with a user ID, and there are no anonymous wallets without a user_id in production.
Applied to files:
apps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.ts
📚 Learning: 2025-11-28T09:21:45.580Z
Learnt from: ygrishajev
Repo: akash-network/console PR: 2274
File: apps/api/drizzle/0023_sad_adam_warlock.sql:1-1
Timestamp: 2025-11-28T09:21:45.580Z
Learning: In the Akash Network console, `userSettings.userId` can be NULL for anonymous users, but `user_wallets.user_id` is always required (NOT NULL). Anonymous users are identified by userId=null in the userSettings table, not in the user_wallets table.
Applied to files:
apps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.ts
📚 Learning: 2025-09-29T02:40:22.090Z
Learnt from: stalniy
Repo: akash-network/console PR: 1951
File: apps/api/src/user/services/user/user.service.ts:48-63
Timestamp: 2025-09-29T02:40:22.090Z
Learning: In the Akash Network Console codebase, the users table was originally created by Sequelize and later migrated to Drizzle. Due to this migration history, the unique constraint on the userId field is named "userSetting_userId_unique" rather than following the typical "users_user_id_unique" pattern.
Applied to files:
apps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.ts
📚 Learning: 2025-10-15T16:39:55.348Z
Learnt from: jzsfkzm
Repo: akash-network/console PR: 2039
File: apps/deploy-web/tests/ui/change-wallets.spec.ts:4-10
Timestamp: 2025-10-15T16:39:55.348Z
Learning: In the Akash Console E2E tests using the context-with-extension fixture, the first wallet is automatically created during fixture setup via `importWalletToLeap` in `apps/deploy-web/tests/ui/fixture/wallet-setup.ts`, so tests that call `frontPage.createWallet()` are creating a second wallet to test wallet switching functionality.
Applied to files:
apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
📚 Learning: 2025-11-25T17:45:52.965Z
Learnt from: CR
Repo: akash-network/console PR: 0
File: .cursor/rules/setup-instead-of-before-each.mdc:0-0
Timestamp: 2025-11-25T17:45:52.965Z
Learning: Applies to **/*.spec.{ts,tsx} : 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.
Applied to files:
apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
🧬 Code graph analysis (8)
apps/api/src/billing/services/balances/balances.service.ts (3)
apps/api/src/caching/helpers.ts (1)
Memoize(21-37)apps/api/src/utils/constants.ts (1)
averageBlockTime(4-4)apps/api/src/billing/http-schemas/balance.schema.ts (1)
GetBalancesResponseOutput(17-17)
apps/api/src/billing/routes/stripe-payment-methods/stripe-payment-methods.router.ts (4)
apps/api/src/billing/controllers/stripe/stripe.controller.ts (1)
markAsDefault(46-51)apps/api/src/core/services/openapi-docs/openapi-security.ts (1)
SECURITY_BEARER_OR_API_KEY(5-5)apps/api/src/billing/http-schemas/stripe.schema.ts (1)
PaymentMethodMarkAsDefaultInputSchema(10-14)apps/api/src/billing/repositories/payment-method/payment-method.repository.ts (1)
markAsDefault(83-100)
apps/api/src/billing/services/stripe-webhook/stripe-webhook.service.ts (1)
apps/api/src/billing/services/paying-user/paying-user.ts (1)
assertIsPayingUser(8-10)
apps/api/src/billing/controllers/stripe/stripe.controller.ts (4)
apps/api/src/auth/services/auth.service.ts (1)
Protected(62-78)apps/api/src/billing/http-schemas/stripe.schema.ts (4)
PaymentMethodMarkAsDefaultInput(236-236)PaymentMethodsResponse(238-238)ConfirmPaymentRequest(239-239)ConfirmPaymentResponse(242-242)apps/api/src/core/lib/semaphore.decorator.ts (1)
Semaphore(3-28)packages/http-sdk/src/stripe/stripe.types.ts (1)
ConfirmPaymentResponse(104-109)
apps/api/src/billing/repositories/payment-method/payment-method.repository.ts (2)
apps/api/src/core/providers/postgres.provider.ts (1)
ApiPgTables(80-80)apps/api/src/core/services/tx/tx.service.ts (1)
ApiTransaction(11-11)
apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts (2)
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (1)
userId(87-143)apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts (3)
userId(60-77)userId(79-107)walletSetting(145-163)
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (9)
apps/api/src/core/types/require.type.ts (1)
Require(1-3)apps/api/src/billing/repositories/user-wallet/user-wallet.repository.ts (1)
UserWalletOutput(18-22)apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts (1)
WalletSettingOutput(19-22)apps/api/src/billing/services/paying-user/paying-user.ts (2)
PayingUser(6-6)isPayingUser(12-14)apps/api/src/billing/http-schemas/balance.schema.ts (1)
GetBalancesResponseOutput(17-17)apps/api/src/billing/http-schemas/stripe.schema.ts (1)
PaymentMethod(237-237)apps/api/src/billing/services/stripe/stripe.service.ts (1)
PaymentMethod(33-33)packages/http-sdk/src/stripe/stripe.service.ts (1)
StripeService(18-94)apps/api/src/billing/events/wallet-balance-reload-check.ts (1)
WalletBalanceReloadCheck(5-15)
apps/api/src/auth/services/auth.service.ts (1)
apps/api/src/billing/services/paying-user/paying-user.ts (3)
PayingUser(6-6)assertIsPayingUser(8-10)isPayingUser(12-14)
⏰ 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: test-build
- 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 (29)
apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.ts (1)
5-5: Import cleanup and type usage look goodThe narrowed import set (
UserWalletPublicOutput,UserWalletRepository) matches actual usage in this file and keeps the dependency surface minimal. No issues here.apps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.ts (1)
7-10: Schema correctly aligned with NOT NULL invariant foruser_wallets.user_id.Making
userId.notNull()(while keeping itunique()and FK‑constrained) is consistent with migration0023_sad_adam_warlock.sqland the documented business rule that all wallets must belong to a concrete user, with anonymous state tracked viauserSettings.userId = nullinstead of null wallets. This removes ORM/DB drift and encodes the actual domain invariant in the model schema. Based on learnings, this looks intentional and sound.apps/api/src/billing/controllers/stripe/stripe.controller.ts (4)
8-8: Typed Stripe schemas integration looks consistentImporting
PaymentMethodMarkAsDefaultInputandPaymentMethodsResponsefrom the HTTP schema keeps controller contracts aligned with validated types; no issues here.
45-51: markAsDefault wiring matches service and auth expectationsUsing
Protectedwithaction: "update"plusgetCurrentPayingUser()and delegating tostripe.markPaymentMethodAsDefaultis a clean, minimal controller; behavior and types line up with the new service API.
54-62: Graceful handling of missing paying user in getPaymentMethodsUsing
getCurrentPayingUser({ strict: false })and returning{ data: [] }when no paying user is resolved avoids hard failures in edge cases and keeps the response shape stable.
68-73: Ownership check in confirmPayment is now effectiveFetching the (possibly non‑strict) paying user, asserting its presence, and awaiting
this.stripe.hasPaymentMethodbefore proceeding correctly closes the previous security hole around unauthorized payment method usage.apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts (2)
10-16: Test wiring for new Stripe and user dependencies looks correctBringing in
PaymentMethod,StripeService,JobQueueService,TxService,UserRepository, andgeneratePaymentMethodand usinguserWithStripeinsetup()aligns the spec with the updatedWalletSettingServiceconstructor and validation behavior.
279-332: setup helper matches the repository’s testing conventionsUsing a
setup()factory at the bottom of the rootdescribe, returning the SUT plus all mocks (includinguserRepository,stripeService, andtxService), is consistent with the project’s testing style and keeps tests free of shared mutable state.apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (5)
67-85: Resource collection and validation are cleanly separated
#collectResourcescomposing#getValidWalletResources,#getDefaultPaymentMethod, andbalancesService.getFullBalanceInFiatinto a singleResult<AllResources, ValidationError>makes the mainhandleflow straightforward and centralizes failure modes nicely.
87-143: Comprehensive preconditions before attempting auto‑reload
#getValidWalletResourcescovers all the key guards (wallet setting existence, auto‑reload enabled, threshold and amount set, wallet initialized, user being aPayingUser) and returns structuredValidationErrors. This should significantly reduce noisy failures in the job handler.
145-169: Default payment method resolution integrates Stripe and CASL correctlyUsing
getDefaultPaymentMethodwith a minimalcreateMongoAbilityinstance and mappinghttp-errorsintoValidationErrorviaisHttpErrorcleanly bridges authorization, Stripe state, and your job-level error taxonomy.
178-213: Auto‑reload Stripe call is guarded and idempotentThe reload attempt only fires when
autoReloadThreshold >= balance, uses the newcreatePaymentIntentwrapper (with amounts in base units) and passes anidempotencyKeyderived from the job id, which is a solid pattern for preventing duplicate charges on retries.
215-233: Next‑check scheduling and persistence are consistent with wallet settings
scheduleNextCheckenqueues a newWalletBalanceReloadCheckwith a singleton key per user and, when a job id is returned, persists it back to the wallet setting;#calculateNextCheckDatecurrently implements a simple “+1 day” strategy while logging the decision, which is a reasonable starting point.apps/api/src/billing/repositories/payment-method/payment-method.repository.ts (2)
10-12: ExportingPaymentMethodInput/Outputimproves type reuseMaking the insert/select types public will simplify typing across services, seeders, and tests that need to construct or assert on payment methods.
70-81: Helper methods for counts and default lookup are straightforward
countByUserIdandfindDefaultByUserIdare thin, readable wrappers around Drizzle queries and existing repository helpers; they should make higher‑level Stripe and billing logic easier to express.apps/api/src/billing/services/stripe/stripe.service.ts (7)
106-120: getPaymentMethods correctly merges Stripe and local validation stateFetching Stripe payment methods and joining them with DB records to set a
validatedflag, then sorting bycreateddescending, cleanly exposes exactly what the API needs without overloading the controller.
122-138: getDefaultPaymentMethod enforces both local and remote invariantsRetrieving the Stripe customer (with expanded default payment method) alongside the locally stored default, then asserting on both, ensures you don’t silently drift between DB and Stripe state and provides clear error sourcing via
{ source: "database" | "stripe" }.
140-145: hasPaymentMethod implements a proper ownership checkLooking up the Stripe payment method by id and comparing its
customertouser.stripeCustomerIdis the right primitive for the controller’s authorization check; this closes the gap where arbitrary IDs could previously be used.
390-392: Simplified getCoupon implementationReturning
this.coupons.retrieve(couponId)directly keeps the method minimal and consistent with other simple wrappers in this service.
147-174: Review comment verified as accurate and well-foundedThe implementation correctly uses
customers.updatewithinvoice_settings.default_payment_method(lines 176-179), which aligns with current Stripe API documentation. The sequencing is safe: local database operations complete within the transaction first, thenmarkRemotePaymentMethodAsDefaultis called to update Stripe, ensuring the remote call operates on confirmed database state.
182-235: The review comment accurately describes the Stripe SDK integration — verification confirms the implementation is correct.The Stripe Node.js SDK (v19.3.0) signature for
paymentIntents.createiscreate(params, options)whereidempotencyKeybelongs in the options object (second parameter). The code correctly:
- Types
createOptionsasParameters<Stripe["paymentIntents"]["create"]>(tuple array)- Places payment parameters in
createOptions[0]- Conditionally pushes
{ idempotencyKey }tocreateOptions[1]- Spreads the array with
...createOptions, expanding tocreate(params, options)when presentThe
Math.round(params.amount * 100)conversion to cents is also correct for Stripe's API expectations.
1-18: New PaymentMethod type and PayingUser integration are well factoredDefining
export type PaymentMethod = Stripe.PaymentMethod & { validated: boolean }and threadingAnyAbility/PayingUserthrough the service public API makes the Stripe layer more expressive and easier to consume from controllers and jobs.✓ Verified:
Stripe.PaymentMethod.createdis a numeric field (Unix epoch seconds), andpaymentIntents.create(params, options?)accepts the two-argument form as used increatePaymentIntent(line 212, wherecreateOptionsis spread to pass idempotencyKey in RequestOptions).apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts (6)
8-12: New dependencies and WalletSettingInput interface align with the extended behaviorInjecting
UserRepository,StripeService,JobQueueService, andTxService, plus introducingWalletSettingInput, matches the new responsibilities (validating payments setup and orchestrating auto‑reload jobs) without leaking implementation details to callers.Also applies to: 21-29
45-58: upsertWalletSetting’s update/create + schedule flow is well structuredUsing
#updatefirst, falling back to#createon missing settings, and then centralizing job orchestration in#arrangeScheduleunder@WithTransaction()keeps the public API simple while handling all edge cases internally.
60-77: Update path cleanly separates read, validate, and write
#updatereads the existing setting with ability checks, validates the combination of previous and next values, then performs anupdateByIdwithreturning: true, returning bothprevandnextfor downstream scheduling logic; no issues spotted.
79-107: Create path now handles unique-constraint races via retry
#createvalidates input, asserts the presence of a user wallet, and onisUniqueViolationretries via#update, ensuring a single setting per user even under concurrent upserts while preserving meaningful 404/500 errors.
135-163: Scheduling and cancellation logic are coherent and transactional
#arrangeScheduleonly creates a schedule when transitioning from disabled→enabled and cancels any existing job when disabling;#schedulewraps cancellation, job id persistence, and enqueueing intxService.transaction, asserting that a job id is actually created.
109-132: Validation correctly gates auto‑reload on payments configuration—confirmedThe review comment is accurate. Verification confirms:
- CASL ability compatibility:
@casl/abilityv6.7.1 is current;AnyAbilitytype is properly imported and used in bothStripeService.getDefaultPaymentMethod(stripe.service.ts:122) and the call site (wallet-settings.service.ts:126-128)- StripeService.getDefaultPaymentMethod: Exists with correct signature
async getDefaultPaymentMethod(user: PayingUser, ability: AnyAbility): Promise<PaymentMethod>, matching the usage pattern- Type safety: No
anytypes;PaymentMethodis properly typed asStripe.PaymentMethod & { validated: boolean }The
#validatemethod correctly enforces that auto‑reload requires threshold, amount, a valid user withstripeCustomerId, and an active default payment method before enabling.apps/api/src/core/services/job-queue/job-queue.service.ts (1)
145-145: JobMeta wiring looks good; ensure all handlers are updated to the new signaturePassing
{ id: job.id }into handlers viaJobMetais a clean way to give handlers job context without exposing the full PgBoss.Job.Since
JobHandler.handlenow has a breaking signature (handle(payload: JobPayload<T>, job: JobMeta)), please verify that all existing handlers have been updated to accept the secondjobparameter and that no overrides still use the old single‑argument form.Also applies to: 208-215
apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
Show resolved
Hide resolved
47469f8 to
af05421
Compare
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts (1)
219-219: Removeas anycast.Additional
as anycast found that should be removed for consistency.Apply this diff:
- walletSettingRepository.updateById.mockResolvedValue(updatedSetting as any); + walletSettingRepository.updateById.mockResolvedValue(updatedSetting);Based on coding guidelines.
♻️ Duplicate comments (4)
apps/api/src/billing/repositories/payment-method/payment-method.repository.ts (1)
79-126: Default payment-method update flow still has concurrency and scoping pitfalls; simplify and scope by userThe default-handling code is much closer with
ensureTransaction, but two issues remain:
createAsDefaultrunscreate(...)and#unmarkAsDefaultExcluding(...)in parallel viaPromise.allon the same logical operation, which complicates reasoning about ordering inside the transaction and is unnecessary.#unmarkAsDefaultExcludingonly constrains by{ isDefault: true }(plus whateverqueryToWhereinjects), not explicitly byuserId, so its behavior depends on ability filters and is less explicit than it could be.You can address both by:
- Running operations sequentially inside the transaction.
- Passing
userIdinto#unmarkAsDefaultExcludingand scoping the UPDATE explicitly to that user.- Dropping the redundant
this.toOutput(output)increateAsDefault, sincethis.create()already returns aPaymentMethodOutput.For example:
async markAsDefault(paymentMethodId: string) { return this.ensureTransaction(async tx => { const [output] = await tx .update(this.table) .set({ isDefault: true, updatedAt: sql`now()` }) .where(this.queryToWhere({ paymentMethodId })) .returning(); if (output) { - await this.#unmarkAsDefaultExcluding(output.id, tx); - - return this.toOutput(output); + await this.#unmarkAsDefaultExcluding(output.userId, output.id, tx); + + return this.toOutput(output); } }); } async createAsDefault(input: Omit<PaymentMethodInput, "id" | "isDefault">) { return this.ensureTransaction(async tx => { const id = uuidv4(); - const [output] = await Promise.all([ - this.create({ - ...input, - isDefault: true, - id - }), - this.#unmarkAsDefaultExcluding(id, tx) - ]); - - return this.toOutput(output); + const output = await this.create({ + ...input, + isDefault: true, + id + }); + + await this.#unmarkAsDefaultExcluding(output.userId, output.id, tx); + + return output; }); } - async #unmarkAsDefaultExcluding(excludedId: PaymentMethodOutput["id"], tx: ApiTransaction) { - await tx - .update(this.table) - .set({ - isDefault: false, - updatedAt: sql`now()` - }) - .where(and(this.queryToWhere({ isDefault: true }), ne(this.table.id, excludedId))); - } + async #unmarkAsDefaultExcluding( + userId: PaymentMethodOutput["userId"], + excludedId: PaymentMethodOutput["id"], + tx: ApiTransaction + ) { + await tx + .update(this.table) + .set({ + isDefault: false, + updatedAt: sql`now()` + }) + .where( + and( + this.queryToWhere({ userId, isDefault: true }), + ne(this.table.id, excludedId) + ) + ); + }This makes the mutation order inside the transaction deterministic, avoids concurrent queries on the same operation, and guarantees that only the current user’s payment methods are affected, independent of how
queryToWherecomposes ability filters. There is still a theoretical window where two concurrent requests for the same user can race, so if “exactly one default per user” is a hard invariant, you may also want a partial unique index on(user_id)whereis_default = true.apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts (3)
114-114: Removeas anycast.Same issue as line 54 - this unresolved cast should be removed.
Apply this diff:
- walletSettingRepository.updateById.mockResolvedValue(newSetting as any); + walletSettingRepository.updateById.mockResolvedValue(newSetting);Based on coding guidelines, as per previous review comments.
185-185: Removeas anycast.Same issue - third occurrence of the unresolved cast.
Apply this diff:
- walletSettingRepository.updateById.mockResolvedValue(updatedSetting as any); + walletSettingRepository.updateById.mockResolvedValue(updatedSetting);Based on coding guidelines, as per previous review comments.
54-54: Removeas anycast.This
as anycast was flagged in a previous review but remains unaddressed. The mock should return the properWalletSettingOutputtype without casting.Apply this diff:
- walletSettingRepository.updateById.mockResolvedValue(updatedSetting as any); + walletSettingRepository.updateById.mockResolvedValue(updatedSetting);If type errors occur, ensure
generateWalletSettingreturnsWalletSettingOutputor properly type the variable:const updatedSetting: WalletSettingOutput = generateWalletSetting({...});Based on coding guidelines, as per previous review comments.
🧹 Nitpick comments (9)
apps/api/src/core/repositories/base.repository.ts (1)
59-67: Rename the generic type parameter to avoid shadowing.The method's generic type parameter
Tshadows the class's genericT(which represents the table type). While this works, it reduces code clarity and can confuse developers.Consider renaming it to
TResultorRfor better readability:-protected async ensureTransaction<T>(cb: (tx: ApiTransaction) => Promise<T>) { +protected async ensureTransaction<TResult>(cb: (tx: ApiTransaction) => Promise<TResult>) { const txCursor = this.txManager.getPgTx(); if (txCursor) { return await cb(txCursor); } return await this.pg.transaction(async tx => await cb(tx)); }Otherwise, the transaction logic is correct—it properly reuses existing transactions when available or creates new ones when needed.
apps/api/src/auth/services/auth.service.ts (1)
26-42: getCurrentPayingUser overloads and behavior look sound; minor DRY opportunityThe overloads correctly model the runtime behavior (strict by default, 401 when no user, 402 when strict and not paying,
undefinedonly in non‑strict mode), and the implementation is consistent with the paying-user helpers.Optionally, consider reusing the existing
currentUseraccessor or a small private helper to avoid duplicating the"CURRENT_USER"key lookup and 401 assertion logic in multiple places.apps/api/src/billing/model-schemas/payment-method/payment-method.schema.ts (1)
19-21:isDefaultcolumn addition looks correct; consider indexing by user + default for lookupsThe NOT NULL + default(false) mapping is aligned with how
PaymentMethodsis used. If you expect frequentfindDefaultByUserIdqueries, consider adding a composite index on(user_id, is_default)in the migration to keep lookups efficient.apps/api/src/billing/routes/stripe-payment-methods/stripe-payment-methods.router.ts (1)
5-11: New “mark default payment method” route is wired correctly; 204 could be a cleaner statusThe POST
/v1/stripe/payment-methods/defaultroute correctly validates the body againstPaymentMethodMarkAsDefaultInputSchema, delegates toStripeController.markAsDefault, and is secured consistently with other payment routes.Since the operation doesn’t return a body, you might consider mirroring the delete route and using
204+c.body(null, 204)instead of200+c.json(undefined, 200), but this is mostly a taste/consistency choice.Also applies to: 44-69
apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts (1)
48-69: Consider reusingtoOutputto avoid duplicating numeric field parsing.Lines 66-67 duplicate the
parseFloatlogic fromtoOutput(lines 75-76). This could lead to drift if the transformation logic changes.if (!walletSetting) return undefined; + const output = this.toOutput(walletSetting); return { - ...walletSetting, - autoReloadThreshold: walletSetting.autoReloadThreshold === null ? undefined : parseFloat(walletSetting.autoReloadThreshold), - autoReloadAmount: walletSetting.autoReloadAmount === null ? undefined : parseFloat(walletSetting.autoReloadAmount) + ...output, + wallet: walletSetting.wallet, + user: walletSetting.user };Alternatively, extract the numeric parsing into a shared helper.
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (1)
226-233: Placeholder implementation noted—ensure this is addressed before production use.The
#calculateNextCheckDatemethod logs "Implementation pending..." and returns a hardcoded 1-day interval. This should be tracked for completion.Would you like me to open an issue to track implementing the proper next-check-date calculation based on wallet activity or deployment costs?
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts (1)
256-306: LGTM! Comprehensive test coverage.The test suite follows the project's testing guidelines well, using
jest-mock-extended, placing the setup function at the bottom, and using proper naming conventions.As per coding guidelines, the setup function should accept a single parameter with inline type definition to allow for future customization, even if not currently needed. Consider:
- function setup() { + function setup(overrides: { user?: Partial<ReturnType<typeof UserSeeder.create>> } = {}) { - const user = UserSeeder.create(); + const user = UserSeeder.create(overrides.user);apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts (1)
279-333: Consider adding parameter to setup function.As per coding guidelines, setup functions should accept a single parameter with inline type definition to allow future customization.
- function setup() { + function setup(overrides: { userId?: string; walletSettingOverrides?: Partial<WalletSettingOutput> } = {}) { - const user = UserSeeder.create(); + const user = UserSeeder.create(overrides.userId ? { id: overrides.userId } : undefined);Based on coding guidelines.
apps/api/src/billing/services/stripe/stripe.service.ts (1)
189-212: Refactor idempotencyKey handling for better readability.The current implementation using an array with conditional spreading works but is not idiomatic for the Stripe API, which accepts two distinct arguments: params and options.
Apply this diff for clearer intent:
- const amountInCents = Math.round(params.amount * 100); - - const createOptions: Parameters<Stripe["paymentIntents"]["create"]> = [ - { - customer: params.customer, - payment_method: params.payment_method, - amount: amountInCents, - currency: params.currency, - confirm: params.confirm, - metadata: params.metadata, - automatic_payment_methods: { - enabled: true, - allow_redirects: "never" - } - } - ]; - - if (params.idempotencyKey) { - createOptions.push({ idempotencyKey: params.idempotencyKey }); - } - - const paymentIntent = await this.paymentIntents.create(...createOptions); + const amountInCents = Math.round(params.amount * 100); + + const paymentIntent = await this.paymentIntents.create( + { + customer: params.customer, + payment_method: params.payment_method, + amount: amountInCents, + currency: params.currency, + confirm: params.confirm, + metadata: params.metadata, + automatic_payment_methods: { + enabled: true, + allow_redirects: "never" + } + }, + params.idempotencyKey ? { idempotencyKey: params.idempotencyKey } : undefined + );
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
apps/api/test/functional/__snapshots__/docs.spec.ts.snapis excluded by!**/*.snap
📒 Files selected for processing (35)
apps/api/drizzle/0023_sad_adam_warlock.sql(1 hunks)apps/api/drizzle/0024_thankful_stick.sql(1 hunks)apps/api/drizzle/meta/0023_snapshot.json(1 hunks)apps/api/drizzle/meta/0024_snapshot.json(1 hunks)apps/api/drizzle/meta/_journal.json(1 hunks)apps/api/src/auth/services/auth.service.ts(2 hunks)apps/api/src/billing/controllers/stripe/stripe.controller.ts(2 hunks)apps/api/src/billing/http-schemas/stripe.schema.ts(2 hunks)apps/api/src/billing/http-schemas/wallet.schema.ts(0 hunks)apps/api/src/billing/model-schemas/payment-method/payment-method.schema.ts(1 hunks)apps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.ts(1 hunks)apps/api/src/billing/model-schemas/wallet-setting/wallet-setting.schema.ts(2 hunks)apps/api/src/billing/repositories/payment-method/payment-method.repository.ts(2 hunks)apps/api/src/billing/repositories/user-wallet/user-wallet.repository.ts(2 hunks)apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts(1 hunks)apps/api/src/billing/routes/stripe-payment-methods/stripe-payment-methods.router.ts(5 hunks)apps/api/src/billing/services/balances/balances.service.ts(3 hunks)apps/api/src/billing/services/paying-user/paying-user.ts(1 hunks)apps/api/src/billing/services/refill/refill.service.ts(1 hunks)apps/api/src/billing/services/stripe-webhook/stripe-webhook.service.ts(2 hunks)apps/api/src/billing/services/stripe/stripe.service.ts(6 hunks)apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts(1 hunks)apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts(2 hunks)apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.ts(2 hunks)apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts(4 hunks)apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts(5 hunks)apps/api/src/core/repositories/base.repository.ts(2 hunks)apps/api/src/core/services/job-queue/job-queue.service.spec.ts(9 hunks)apps/api/src/core/services/job-queue/job-queue.service.ts(3 hunks)apps/api/src/core/services/tx/tx.service.ts(1 hunks)apps/api/src/core/types/require.type.ts(1 hunks)apps/api/test/seeders/balance.seeder.ts(2 hunks)apps/api/test/seeders/database-payment-method.seeder.ts(1 hunks)apps/api/test/seeders/payment-method.seeder.ts(2 hunks)packages/http-sdk/src/stripe/stripe.service.ts(0 hunks)
💤 Files with no reviewable changes (2)
- packages/http-sdk/src/stripe/stripe.service.ts
- apps/api/src/billing/http-schemas/wallet.schema.ts
🚧 Files skipped from review as they are similar to previous changes (12)
- apps/api/drizzle/0024_thankful_stick.sql
- apps/api/src/billing/services/refill/refill.service.ts
- apps/api/src/billing/services/stripe-webhook/stripe-webhook.service.ts
- apps/api/src/billing/services/paying-user/paying-user.ts
- apps/api/drizzle/meta/0024_snapshot.json
- apps/api/test/seeders/balance.seeder.ts
- apps/api/test/seeders/payment-method.seeder.ts
- apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.ts
- apps/api/src/core/services/job-queue/job-queue.service.ts
- apps/api/drizzle/0023_sad_adam_warlock.sql
- apps/api/drizzle/meta/_journal.json
- apps/api/src/core/services/tx/tx.service.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (.cursor/rules/general.mdc)
**/*.{ts,tsx,js}: Never use typeanyor cast to typeany. 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/model-schemas/payment-method/payment-method.schema.tsapps/api/src/billing/services/balances/balances.service.tsapps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.tsapps/api/src/billing/routes/stripe-payment-methods/stripe-payment-methods.router.tsapps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.tsapps/api/src/billing/repositories/user-wallet/user-wallet.repository.tsapps/api/src/billing/controllers/stripe/stripe.controller.tsapps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.tsapps/api/src/core/types/require.type.tsapps/api/src/billing/model-schemas/wallet-setting/wallet-setting.schema.tsapps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.tsapps/api/src/auth/services/auth.service.tsapps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.tsapps/api/src/core/services/job-queue/job-queue.service.spec.tsapps/api/src/billing/services/wallet-settings/wallet-settings.service.tsapps/api/src/billing/http-schemas/stripe.schema.tsapps/api/test/seeders/database-payment-method.seeder.tsapps/api/src/billing/repositories/payment-method/payment-method.repository.tsapps/api/src/core/repositories/base.repository.tsapps/api/src/billing/services/stripe/stripe.service.ts
**/*.spec.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/no-jest-mock.mdc)
Don't use
jest.mock()in test files. Instead, usejest-mock-extendedto create mocks and pass mocks as dependencies to the service under testUse
setupfunction instead ofbeforeEachin test files. Thesetupfunction must be at the bottom of the rootdescribeblock, 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>.namein 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/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.tsapps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.tsapps/api/src/core/services/job-queue/job-queue.service.spec.ts
🧠 Learnings (14)
📚 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/routes/stripe-payment-methods/stripe-payment-methods.router.tsapps/api/src/billing/controllers/stripe/stripe.controller.tsapps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.tsapps/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.tsapps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.tsapps/api/src/billing/services/stripe/stripe.service.ts
📚 Learning: 2025-10-31T11:26:42.138Z
Learnt from: stalniy
Repo: akash-network/console PR: 2138
File: apps/api/src/billing/lib/batch-signing-client/batch-signing-client.service.ts:379-382
Timestamp: 2025-10-31T11:26:42.138Z
Learning: In TypeScript/JavaScript, the pattern of checking a cached value and then performing an async operation to fetch it without proper synchronization is race condition unsafe:
```typescript
private async getAddress() {
if (!this.address) {
this.address = await this.wallet.getFirstAddress();
}
return this.address;
}
```
Multiple concurrent calls can all pass the `if (!this.address)` check before any of them sets the value, leading to duplicate async operations. This should be flagged as a race condition. Proper synchronization (mutex, atomic promise caching, or guaranteed single-threaded execution) is required.
Applied to files:
apps/api/src/billing/controllers/stripe/stripe.controller.tsapps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.tsapps/api/src/billing/repositories/payment-method/payment-method.repository.tsapps/api/src/billing/services/stripe/stripe.service.ts
📚 Learning: 2025-11-28T09:17:28.821Z
Learnt from: ygrishajev
Repo: akash-network/console PR: 2274
File: apps/api/drizzle/0023_sad_adam_warlock.sql:1-1
Timestamp: 2025-11-28T09:17:28.821Z
Learning: In the Akash Network console billing system, the `user_wallets.user_id` column is required (NOT NULL). All user wallets must be associated with a user ID, and there are no anonymous wallets without a user_id in production.
Applied to files:
apps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.ts
📚 Learning: 2025-11-28T09:21:45.580Z
Learnt from: ygrishajev
Repo: akash-network/console PR: 2274
File: apps/api/drizzle/0023_sad_adam_warlock.sql:1-1
Timestamp: 2025-11-28T09:21:45.580Z
Learning: In the Akash Network console, `userSettings.userId` can be NULL for anonymous users, but `user_wallets.user_id` is always required (NOT NULL). Anonymous users are identified by userId=null in the userSettings table, not in the user_wallets table.
Applied to files:
apps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.ts
📚 Learning: 2025-09-29T02:40:22.090Z
Learnt from: stalniy
Repo: akash-network/console PR: 1951
File: apps/api/src/user/services/user/user.service.ts:48-63
Timestamp: 2025-09-29T02:40:22.090Z
Learning: In the Akash Network Console codebase, the users table was originally created by Sequelize and later migrated to Drizzle. Due to this migration history, the unique constraint on the userId field is named "userSetting_userId_unique" rather than following the typical "users_user_id_unique" pattern.
Applied to files:
apps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.ts
📚 Learning: 2025-10-15T16:39:55.348Z
Learnt from: jzsfkzm
Repo: akash-network/console PR: 2039
File: apps/deploy-web/tests/ui/change-wallets.spec.ts:4-10
Timestamp: 2025-10-15T16:39:55.348Z
Learning: In the Akash Console E2E tests using the context-with-extension fixture, the first wallet is automatically created during fixture setup via `importWalletToLeap` in `apps/deploy-web/tests/ui/fixture/wallet-setup.ts`, so tests that call `frontPage.createWallet()` are creating a second wallet to test wallet switching functionality.
Applied to files:
apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
📚 Learning: 2025-11-25T17:45:44.790Z
Learnt from: CR
Repo: akash-network/console PR: 0
File: .cursor/rules/no-jest-mock.mdc:0-0
Timestamp: 2025-11-25T17:45:44.790Z
Learning: Applies to **/*.spec.{ts,tsx} : 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
Applied to files:
apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.tsapps/api/src/core/services/job-queue/job-queue.service.spec.ts
📚 Learning: 2025-11-25T17:45:39.561Z
Learnt from: CR
Repo: akash-network/console PR: 0
File: .cursor/rules/general.mdc:0-0
Timestamp: 2025-11-25T17:45:39.561Z
Learning: Applies to **/*.{ts,tsx,js} : Never use type `any` or cast to type `any`. Always define the proper TypeScript types.
Applied to files:
apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
📚 Learning: 2025-11-25T17:45:52.965Z
Learnt from: CR
Repo: akash-network/console PR: 0
File: .cursor/rules/setup-instead-of-before-each.mdc:0-0
Timestamp: 2025-11-25T17:45:52.965Z
Learning: Applies to **/*.spec.{ts,tsx} : 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.
Applied to files:
apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
📚 Learning: 2025-11-19T15:15:07.283Z
Learnt from: ygrishajev
Repo: akash-network/console PR: 2254
File: apps/api/test/functional/sign-and-broadcast-tx.spec.ts:4-4
Timestamp: 2025-11-19T15:15:07.283Z
Learning: In the Akash Network Console project, when tests use native Node.js fetch (available in Node 18+), fetch-mock should be used for HTTP mocking instead of nock, as nock does not support intercepting native fetch calls. This applies to apps/api/test/functional/sign-and-broadcast-tx.spec.ts and any other tests using native fetch.
Applied to files:
apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
📚 Learning: 2025-11-25T17:45:49.180Z
Learnt from: CR
Repo: akash-network/console PR: 0
File: .cursor/rules/query-by-in-tests.mdc:0-0
Timestamp: 2025-11-25T17:45:49.180Z
Learning: Applies to {apps/deploy-web,apps/provider-console}/**/*.spec.tsx : Use `queryBy` methods instead of `getBy` methods in test expectations
Applied to files:
apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
📚 Learning: 2025-09-04T04:21:26.067Z
Learnt from: stalniy
Repo: akash-network/console PR: 1868
File: apps/api/src/app/services/trial-deployment-lease-created/trial-deployment-lease-created.handler.ts:73-76
Timestamp: 2025-09-04T04:21:26.067Z
Learning: The apps/api codebase consistently uses .toISOString() for startAfter values when scheduling jobs with JobQueueService to ensure UTC timezone consistency. This pattern is used throughout trial-started.handler.ts, trial-deployment-lease-created.handler.ts and other job scheduling code. Do not suggest changing to Date objects as ISO strings provide explicit UTC representation for database storage.
Applied to files:
apps/api/src/core/services/job-queue/job-queue.service.spec.ts
📚 Learning: 2025-09-04T04:21:26.067Z
Learnt from: stalniy
Repo: akash-network/console PR: 1868
File: apps/api/src/app/services/trial-deployment-lease-created/trial-deployment-lease-created.handler.ts:73-76
Timestamp: 2025-09-04T04:21:26.067Z
Learning: In apps/api job scheduling with JobQueueService, use .toISOString() for startAfter values instead of Date objects to ensure UTC timezone consistency when dates are persisted to the database. Date objects can be ambiguous about timezone during serialization, while ISO strings with .toISOString() explicitly represent UTC.
Applied to files:
apps/api/src/core/services/job-queue/job-queue.service.spec.ts
🧬 Code graph analysis (11)
apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts (2)
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (1)
userId(87-143)apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts (3)
userId(60-77)userId(79-107)walletSetting(145-163)
apps/api/src/billing/routes/stripe-payment-methods/stripe-payment-methods.router.ts (7)
apps/api/src/billing/controllers/stripe/stripe.controller.ts (2)
createSetupIntent(36-43)markAsDefault(46-51)apps/api/src/billing/services/stripe/stripe.service.ts (1)
createSetupIntent(51-57)packages/http-sdk/src/stripe/stripe.service.ts (1)
createSetupIntent(23-25)apps/api/src/core/lib/create-route/create-route.ts (1)
createRoute(5-11)apps/api/src/core/services/openapi-docs/openapi-security.ts (1)
SECURITY_BEARER_OR_API_KEY(5-5)apps/api/src/billing/http-schemas/stripe.schema.ts (1)
PaymentMethodMarkAsDefaultInputSchema(10-14)apps/api/src/billing/repositories/payment-method/payment-method.repository.ts (1)
markAsDefault(83-100)
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts (4)
apps/api/src/core/services/job-queue/job-queue.service.ts (2)
JobPayload(199-199)JobMeta(208-208)apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (4)
job(67-85)wallet(226-233)error(171-176)user(145-169)apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts (1)
walletSetting(145-163)apps/api/src/billing/events/wallet-balance-reload-check.ts (1)
WalletBalanceReloadCheck(5-15)
apps/api/src/billing/repositories/user-wallet/user-wallet.repository.ts (1)
apps/api/src/core/providers/postgres.provider.ts (1)
ApiPgTables(80-80)
apps/api/src/billing/model-schemas/wallet-setting/wallet-setting.schema.ts (2)
apps/api/src/user/model-schemas/user/user.schema.ts (1)
Users(8-28)apps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.ts (1)
UserWallets(5-18)
apps/api/src/auth/services/auth.service.ts (1)
apps/api/src/billing/services/paying-user/paying-user.ts (3)
PayingUser(6-6)assertIsPayingUser(8-10)isPayingUser(12-14)
apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts (5)
apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts (1)
walletSetting(145-163)apps/api/test/seeders/user-wallet.seeder.ts (1)
UserWalletSeeder(6-30)apps/api/test/seeders/payment-method.seeder.ts (1)
generatePaymentMethod(11-56)apps/api/src/billing/http-schemas/stripe.schema.ts (1)
PaymentMethod(237-237)apps/api/src/billing/services/stripe/stripe.service.ts (1)
PaymentMethod(33-33)
apps/api/src/core/services/job-queue/job-queue.service.spec.ts (1)
apps/api/src/core/services/job-queue/job-queue.service.ts (2)
setup(175-182)Job(189-197)
apps/api/test/seeders/database-payment-method.seeder.ts (1)
apps/api/src/billing/repositories/payment-method/payment-method.repository.ts (1)
PaymentMethodOutput(11-11)
apps/api/src/billing/repositories/payment-method/payment-method.repository.ts (2)
apps/api/src/core/providers/postgres.provider.ts (1)
ApiPgTables(80-80)apps/api/src/core/services/tx/tx.service.ts (1)
ApiTransaction(11-11)
apps/api/src/core/repositories/base.repository.ts (1)
apps/api/src/core/services/tx/tx.service.ts (1)
ApiTransaction(11-11)
⏰ 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: test-build
- GitHub Check: validate / validate-app
- GitHub Check: validate / validate-app
- GitHub Check: test-build
- GitHub Check: test-build
- GitHub Check: Validate local packages
- GitHub Check: Analyze (javascript-typescript)
apps/api/src/billing/repositories/payment-method/payment-method.repository.ts
Show resolved
Hide resolved
.../api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts
Outdated
Show resolved
Hide resolved
.../api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts
Outdated
Show resolved
Hide resolved
eb9d302 to
492a4ad
Compare
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (2)
21-23: Consider importingRequirefrom core instead of redefining it.The
Requireutility type is already defined atapps/api/src/core/types/require.type.ts(shown in relevant code snippets). Importing it would eliminate duplication and ensure consistency across the codebase.Apply this diff:
+import { Require } from "@src/core/types/require.type"; + import { WalletBalanceReloadCheck } from "@src/billing/events/wallet-balance-reload-check"; import type { GetBalancesResponseOutput } from "@src/billing/http-schemas/balance.schema"; import { UserWalletOutput, WalletSettingOutput, WalletSettingRepository } from "@src/billing/repositories"; import { BalancesService } from "@src/billing/services/balances/balances.service"; import { PaymentMethod, StripeService } from "@src/billing/services/stripe/stripe.service"; import { JobHandler, JobMeta, JobPayload, JobQueueService, LoggerService } from "@src/core"; import { isPayingUser, PayingUser } from "../paying-user/paying-user"; -type Require<T, K extends keyof T> = Omit<T, K> & { - [P in K]-?: NonNullable<T[P]>; -}; - type InitializedWallet = Require<Pick<UserWalletOutput, "address" | "isOldWallet">, "address">;
238-245: Placeholder implementation for next check date calculation.The
#calculateNextCheckDatemethod currently returns a fixed 1-day interval and logs "Implementation pending...". Ensure this logic is implemented before production use, as the current approach doesn't account for wallet activity patterns or dynamic scheduling needs.Do you want me to help design a more sophisticated scheduling algorithm based on historical balance burn rates or other factors?
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
apps/api/src/billing/services/stripe/stripe.service.ts(6 hunks)apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts(2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (.cursor/rules/general.mdc)
**/*.{ts,tsx,js}: Never use typeanyor cast to typeany. 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/wallet-balance-reload-check/wallet-balance-reload-check.handler.tsapps/api/src/billing/services/stripe/stripe.service.ts
🧠 Learnings (3)
📚 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/wallet-balance-reload-check/wallet-balance-reload-check.handler.tsapps/api/src/billing/services/stripe/stripe.service.ts
📚 Learning: 2025-10-31T11:26:42.138Z
Learnt from: stalniy
Repo: akash-network/console PR: 2138
File: apps/api/src/billing/lib/batch-signing-client/batch-signing-client.service.ts:379-382
Timestamp: 2025-10-31T11:26:42.138Z
Learning: In TypeScript/JavaScript, the pattern of checking a cached value and then performing an async operation to fetch it without proper synchronization is race condition unsafe:
```typescript
private async getAddress() {
if (!this.address) {
this.address = await this.wallet.getFirstAddress();
}
return this.address;
}
```
Multiple concurrent calls can all pass the `if (!this.address)` check before any of them sets the value, leading to duplicate async operations. This should be flagged as a race condition. Proper synchronization (mutex, atomic promise caching, or guaranteed single-threaded execution) is required.
Applied to files:
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.tsapps/api/src/billing/services/stripe/stripe.service.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/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts
🧬 Code graph analysis (2)
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (9)
apps/api/src/core/types/require.type.ts (1)
Require(1-3)apps/api/src/billing/repositories/user-wallet/user-wallet.repository.ts (1)
UserWalletOutput(18-22)apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts (1)
WalletSettingOutput(19-22)apps/api/src/billing/services/paying-user/paying-user.ts (2)
PayingUser(6-6)isPayingUser(12-14)apps/api/src/billing/http-schemas/balance.schema.ts (1)
GetBalancesResponseOutput(17-17)apps/api/src/billing/services/stripe/stripe.service.ts (1)
PaymentMethod(33-33)apps/api/src/billing/http-schemas/stripe.schema.ts (1)
PaymentMethod(237-237)apps/api/src/core/services/job-queue/job-queue.service.ts (2)
JobPayload(199-199)JobMeta(208-208)apps/api/src/billing/events/wallet-balance-reload-check.ts (1)
WalletBalanceReloadCheck(5-15)
apps/api/src/billing/services/stripe/stripe.service.ts (5)
apps/api/src/billing/http-schemas/stripe.schema.ts (2)
PaymentMethod(237-237)PaymentIntentResult(240-240)packages/http-sdk/src/stripe/stripe.types.ts (1)
PaymentMethod(30-41)apps/api/src/billing/services/paying-user/paying-user.ts (1)
PayingUser(6-6)apps/api/src/user/repositories/user/user.repository.ts (1)
UserOutput(10-12)apps/api/src/core/services/tx/tx.service.ts (1)
WithTransaction(39-51)
⏰ 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: test-build
- 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 (5)
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (2)
87-143: LGTM!The validation logic is comprehensive and correctly handles all edge cases with appropriate error events. The use of type guards like
isPayingUserensures proper type narrowing for downstream operations.
178-213: LGTM!The reload logic correctly triggers when the balance drops to or below the threshold. The use of job.id in the idempotency key ensures Stripe won't create duplicate payment intents if the job is retried, addressing the transaction consistency concern from past reviews.
apps/api/src/billing/services/stripe/stripe.service.ts (3)
122-138: LGTM!The
getDefaultPaymentMethodimplementation correctly retrieves and validates payment method data from both local database and Stripe, with appropriate authorization checks via CASL abilities. The error messages with source attribution aid debugging.
147-174: Transaction + external API pattern acceptable per past reviews.The method now calls
markRemotePaymentMethodAsDefault(Stripe update) after local DB operations complete (lines 157, 171), addressing the transaction consistency concern from past reviews. The 3-second timeouts help limit DB connection hold time, though this remains a trade-off between consistency and performance as noted in previous discussions.
186-239: LGTM!The
createPaymentIntentmethod correctly handles the optionalidempotencyKeyparameter by conditionally adding it to the Stripe API call's options parameter. The use ofParametersutility type maintains type safety while allowing flexible argument construction.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
apps/api/drizzle.config.ts (1)
5-9: Good addition of pre-flight validation.The runtime check ensures POSTGRES_DB_URI is set before the config is used, preventing downstream errors.
Optionally, you could make the error message more helpful:
if (!POSTGRES_DB_URI) { - throw new Error("POSTGRES_DB_URI must be set"); + throw new Error("POSTGRES_DB_URI environment variable must be set. Please check your .env file or environment configuration."); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
apps/api/drizzle.config.ts(1 hunks)apps/api/drizzle/0023_steady_leech.sql(1 hunks)apps/api/drizzle/meta/0023_snapshot.json(1 hunks)apps/api/drizzle/meta/_journal.json(1 hunks)apps/api/src/billing/model-schemas/payment-method/payment-method.schema.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/api/src/billing/model-schemas/payment-method/payment-method.schema.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (.cursor/rules/general.mdc)
**/*.{ts,tsx,js}: Never use typeanyor cast to typeany. Always define the proper TypeScript types.
Never use deprecated methods from libraries.
Don't add unnecessary comments to the code.
Files:
apps/api/drizzle.config.ts
🧠 Learnings (1)
📚 Learning: 2025-11-28T09:17:28.821Z
Learnt from: ygrishajev
Repo: akash-network/console PR: 2274
File: apps/api/drizzle/0023_sad_adam_warlock.sql:1-1
Timestamp: 2025-11-28T09:17:28.821Z
Learning: In the Akash Network console billing system, the `user_wallets.user_id` column is required (NOT NULL). All user wallets must be associated with a user ID, and there are no anonymous wallets without a user_id in production.
Applied to files:
apps/api/drizzle/0023_steady_leech.sql
⏰ 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: test-build
- 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 (6)
apps/api/drizzle.config.ts (1)
16-16: LGTM!Using the environment variable directly is appropriate for the Drizzle configuration file and simplifies the dependency chain.
apps/api/drizzle/meta/_journal.json (1)
166-172: LGTM!The migration journal entry is properly formatted and correctly tracks the new migration.
apps/api/drizzle/0023_steady_leech.sql (3)
1-1: LGTM!The NOT NULL constraint on
user_wallets.user_idis correct. Based on learnings, all user wallets must be associated with a user ID in the Akash Network console billing system.
2-2: LGTM!Adding the
is_defaultcolumn with a default value offalseand NOT NULL constraint is appropriate for tracking default payment methods per user.
3-3: Excellent use of partial unique index.The partial unique index
payment_methods_user_id_is_default_uniqueon(user_id, is_default) WHERE is_default = trueis the correct pattern for ensuring each user can have at most one default payment method. This prevents data integrity issues while allowing multiple non-default payment methods.apps/api/drizzle/meta/0023_snapshot.json (1)
1-820: LGTM!The schema snapshot is consistent with the migration and correctly reflects the database state after applying migration 0023_steady_leech.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
apps/api/src/billing/routes/stripe-webhook/stripe-webhook.router.ts (1)
44-52: Consider explicit error handling for webhook processing.The webhook handler has no try-catch around
CheckoutController.webhook(). If the webhook processing throws an error, it will result in a 500 response, which may trigger Stripe to retry the webhook. If this is intentional retry behavior, consider documenting it. Otherwise, add error handling to log failures and return appropriate status codes.Additionally, the
as nevercast on line 51 suggests a type mismatch with Hono's response types. Consider investigating the underlying type issue rather than using a type assertion.stripeWebhook.openapi(route, async function routeStripeWebhook(c) { const sig = c.req.header("stripe-signature"); if (!sig) { return c.json({ error: "Stripe signature is required" }, 400); } + try { await container.resolve(CheckoutController).webhook(sig, await c.req.text()); - return c.text("", 200) as never; + return c.text("", 200); + } catch (error) { + // Log error and let Stripe retry + console.error("Webhook processing failed:", error); + throw error; + } });apps/api/src/billing/controllers/stripe/stripe.controller.ts (1)
58-68: Clarify the 404 error message.Line 63 returns a 404 with the message "Default payment method not found" when
currentUserdoesn't exist. However, this message is misleading because:
- The check is for user existence/configuration, not whether a default payment method exists
- A more accurate message would be "Payment account not configured" or "User not found"
The actual "payment method not found" case would occur inside
stripe.getDefaultPaymentMethod()if the user exists but has no default payment method.- assert(currentUser, 404, "Default payment method not found"); + assert(currentUser, 404, "Payment account not configured");
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
apps/api/src/billing/controllers/stripe/stripe.controller.ts(2 hunks)apps/api/src/billing/http-schemas/stripe.schema.ts(3 hunks)apps/api/src/billing/routes/stripe-payment-methods/stripe-payment-methods.router.ts(5 hunks)apps/api/src/billing/routes/stripe-webhook/stripe-webhook.router.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/api/src/billing/http-schemas/stripe.schema.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (.cursor/rules/general.mdc)
**/*.{ts,tsx,js}: Never use typeanyor cast to typeany. 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.tsapps/api/src/billing/routes/stripe-payment-methods/stripe-payment-methods.router.tsapps/api/src/billing/routes/stripe-webhook/stripe-webhook.router.ts
🧠 Learnings (3)
📚 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-10-31T11:26:42.138Z
Learnt from: stalniy
Repo: akash-network/console PR: 2138
File: apps/api/src/billing/lib/batch-signing-client/batch-signing-client.service.ts:379-382
Timestamp: 2025-10-31T11:26:42.138Z
Learning: In TypeScript/JavaScript, the pattern of checking a cached value and then performing an async operation to fetch it without proper synchronization is race condition unsafe:
```typescript
private async getAddress() {
if (!this.address) {
this.address = await this.wallet.getFirstAddress();
}
return this.address;
}
```
Multiple concurrent calls can all pass the `if (!this.address)` check before any of them sets the value, leading to duplicate async operations. This should be flagged as a race condition. Proper synchronization (mutex, atomic promise caching, or guaranteed single-threaded execution) is required.
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/controllers/stripe/stripe.controller.tsapps/api/src/billing/routes/stripe-payment-methods/stripe-payment-methods.router.ts
🧬 Code graph analysis (1)
apps/api/src/billing/controllers/stripe/stripe.controller.ts (3)
apps/api/src/auth/services/auth.service.ts (1)
Protected(62-78)apps/api/src/billing/http-schemas/stripe.schema.ts (5)
PaymentMethodMarkAsDefaultInput(240-240)PaymentMethodResponse(243-243)PaymentMethodsResponse(242-242)ConfirmPaymentRequest(244-244)ConfirmPaymentResponse(247-247)apps/api/src/core/lib/semaphore.decorator.ts (1)
Semaphore(3-28)
⏰ 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). (9)
- GitHub Check: codecov/patch/api
- 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: test-build
- GitHub Check: Validate local packages
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (4)
apps/api/src/billing/routes/stripe-payment-methods/stripe-payment-methods.router.ts (1)
5-6: LGTM! Default payment method routes are well-structured.The new routes for managing default payment methods follow the existing patterns in the codebase:
- Proper OpenAPI documentation with request/response schemas
- Schema validation via
PaymentMethodMarkAsDefaultInputSchemaandPaymentMethodResponseSchema- Appropriate security decorators (
SECURITY_BEARER_OR_API_KEY)- Clean delegation to
StripeControllermethodsAlso applies to: 45-99
apps/api/src/billing/controllers/stripe/stripe.controller.ts (3)
70-80: LGTM! Graceful handling of optional payment user.The method correctly uses
{strict: false}and returns an empty array when no paying user is configured, providing a clean experience for users who haven't set up payments yet.
84-125: Excellent! Critical security bug has been fixed.Line 90 now correctly awaits
this.stripe.hasPaymentMethod()before passing the result toassert(). This fixes the critical security vulnerability flagged in previous reviews where the Promise object (always truthy) was being checked instead of its resolved boolean value.The ownership check now properly validates that the payment method belongs to the user before processing the payment.
✅ Confirmed fix from previous review feedback
50-56: Based on my verification, I can now provide the definitive assessment:The inconsistency is intentional and not an oversight.
Here's what I discovered:
getCurrentPayingUser()behavior:
- Default (no options):
strict: true— enforces PayingUser viaassertIsPayingUser()or throws- With
{ strict: false }: Returnsundefinedgracefully if user isn't a paying userPattern in the controller:
- Read operations (
getDefaultPaymentMethod,getPaymentMethods): Usestrict: falsefor defensive, graceful handling- Create operation (
confirmPayment): Usesstrict: falsebut then asserts afterward- Update operation (
markAsDefault): Uses strict mode (default) — requires a fully configured paying userBusiness logic rationale:
- Marking a payment method as default is a write operation that modifies account configuration
- It's more restrictive than read operations, intentionally requiring a fully configured paying user
- This is a deliberate design choice, not an oversight
The original review comment assumes an inconsistency that doesn't exist. The code is correct as-is.
54f2e22 to
b4ba140
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/api/src/billing/http-schemas/stripe.schema.ts (1)
16-55: Missingidfield in PaymentMethodSchema.The
PaymentMethodResponseSchemawrapsPaymentMethodSchema, butPaymentMethodSchemalacks anidfield. The relevant type definitions inpackages/http-sdk/src/stripe/stripe.types.ts(lines 29-40) show thatPaymentMethodincludes anid: stringfield. The response schema should include this to properly identify the returned payment method.export const PaymentMethodSchema = z.object({ + id: z.string(), type: z.string(), validated: z.boolean().optional(), card: zAlso applies to: 61-63
🧹 Nitpick comments (1)
apps/api/src/billing/routes/stripe-payment-methods/stripe-payment-methods.router.ts (1)
67-70: Consider using 204 No Content for void operations.Returning
c.json(undefined, 200)is semantically unusual. For operations that succeed without returning data, HTTP 204 No Content is the standard practice.-stripePaymentMethodsRouter.openapi(markAsDefaultRoute, async function markAsDefault(c) { - await container.resolve(StripeController).markAsDefault(c.req.valid("json")); - return c.json(undefined, 200); -}); +stripePaymentMethodsRouter.openapi(markAsDefaultRoute, async function markAsDefault(c) { + await container.resolve(StripeController).markAsDefault(c.req.valid("json")); + return c.body(null, 204); +});This would also require updating the route definition to use
204instead of200in the responses.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
apps/api/src/billing/controllers/stripe/stripe.controller.ts(2 hunks)apps/api/src/billing/http-schemas/stripe.schema.ts(3 hunks)apps/api/src/billing/routes/stripe-payment-methods/stripe-payment-methods.router.ts(5 hunks)apps/api/src/billing/routes/stripe-webhook/stripe-webhook.router.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/api/src/billing/routes/stripe-webhook/stripe-webhook.router.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (.cursor/rules/general.mdc)
**/*.{ts,tsx,js}: Never use typeanyor cast to typeany. 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/routes/stripe-payment-methods/stripe-payment-methods.router.tsapps/api/src/billing/controllers/stripe/stripe.controller.tsapps/api/src/billing/http-schemas/stripe.schema.ts
🧠 Learnings (3)
📚 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/routes/stripe-payment-methods/stripe-payment-methods.router.tsapps/api/src/billing/controllers/stripe/stripe.controller.tsapps/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
📚 Learning: 2025-10-31T11:26:42.138Z
Learnt from: stalniy
Repo: akash-network/console PR: 2138
File: apps/api/src/billing/lib/batch-signing-client/batch-signing-client.service.ts:379-382
Timestamp: 2025-10-31T11:26:42.138Z
Learning: In TypeScript/JavaScript, the pattern of checking a cached value and then performing an async operation to fetch it without proper synchronization is race condition unsafe:
```typescript
private async getAddress() {
if (!this.address) {
this.address = await this.wallet.getFirstAddress();
}
return this.address;
}
```
Multiple concurrent calls can all pass the `if (!this.address)` check before any of them sets the value, leading to duplicate async operations. This should be flagged as a race condition. Proper synchronization (mutex, atomic promise caching, or guaranteed single-threaded execution) is required.
Applied to files:
apps/api/src/billing/controllers/stripe/stripe.controller.ts
🧬 Code graph analysis (2)
apps/api/src/billing/controllers/stripe/stripe.controller.ts (3)
apps/api/src/auth/services/auth.service.ts (1)
Protected(62-78)apps/api/src/billing/http-schemas/stripe.schema.ts (4)
PaymentMethodMarkAsDefaultInput(240-240)PaymentMethodResponse(243-243)PaymentMethodsResponse(242-242)ConfirmPaymentRequest(244-244)apps/api/src/core/lib/semaphore.decorator.ts (1)
Semaphore(3-28)
apps/api/src/billing/http-schemas/stripe.schema.ts (2)
apps/api/src/billing/services/stripe/stripe.service.ts (1)
PaymentMethod(33-33)packages/http-sdk/src/stripe/stripe.types.ts (1)
PaymentMethod(30-41)
⏰ 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: test-build
- GitHub Check: validate / validate-app
- 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 (5)
apps/api/src/billing/http-schemas/stripe.schema.ts (1)
10-14: LGTM!The schema follows the established pattern of wrapping data in a
dataobject, consistent with other request schemas in this file.apps/api/src/billing/controllers/stripe/stripe.controller.ts (3)
50-56: LGTM!The method correctly uses the
@Protecteddecorator with appropriate action/subject, retrieves the paying user, and delegates the authorization check to the service layer via theabilityparameter.
67-77: LGTM!Good defensive pattern: using
{ strict: false }and returning an empty array when no paying user exists ensures graceful handling without errors for unauthenticated or non-paying users.
82-87: LGTM! Previous security issue addressed.The ownership check now correctly awaits the
hasPaymentMethodpromise before asserting, fixing the critical security bug from the previous review.apps/api/src/billing/routes/stripe-payment-methods/stripe-payment-methods.router.ts (1)
90-99: 404 handling for missing default payment method is working correctly via global error middleware.The underlying
StripeService.getDefaultPaymentMethod()(lines 134-135 in stripe.service.ts) properly throws 404 errors usingassertwhen no default payment method exists in either the database or Stripe. These exceptions are caught and converted to appropriate HTTP responses by the global error handler registered at line 184 of rest-app.ts (appHono.onError(container.resolve(HonoErrorHandlerService).handle)). The handler's unconditional 200 return is safe because exceptions from the service layer bypass it.While the error handling works correctly, the pattern of implicit error handling via middleware is not obvious from reading the route handler alone. Consider adding explicit error handling or documentation if clarity is desired.
95de779 to
c322b6c
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
apps/api/src/deployment/services/draining-deployment/draining-deployment.service.spec.ts (1)
21-21: Removejest.mock()usage per coding guidelines.The coding guidelines explicitly prohibit using
jest.mock()in test files. The logger mock is already imported on line 1 via@test/mocks/logger-service.mock. If additional mocking is needed, inject mocks as dependencies usingjest-mock-extended.Based on learnings and coding guidelines.
-jest.mock("@akashnetwork/logging");apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.ts (1)
53-63: Add error handling to prevent orphaned records.The
initializemethod lacks error handling that could lead to orphaned database records. IfwalletManager.createWallet(line 55) fails afteruserWalletRepository.create(line 54) succeeds, a user wallet record will exist without an address.This is inconsistent with
initializeAndGrantTrialLimits(lines 27-42), which includes proper rollback logic (line 40).Apply this diff to add error handling:
async initialize(userId: string) { const { id } = await this.userWalletRepository.create({ userId }); - const wallet = await this.walletManager.createWallet({ addressIndex: id, useOldWallet: false }); - return await this.userWalletRepository.updateById( - id, - { - address: wallet.address - }, - { returning: true } - ); + try { + const wallet = await this.walletManager.createWallet({ addressIndex: id, useOldWallet: false }); + return await this.userWalletRepository.updateById( + id, + { + address: wallet.address + }, + { returning: true } + ); + } catch (error) { + await this.userWalletRepository.deleteById(id); + throw error; + } }apps/api/src/billing/services/stripe-webhook/stripe-webhook.service.ts (1)
173-178: Consider removing fingerprint from detach logs for consistency.A past review flagged fingerprint logging as sensitive in the attach handler. For consistency, consider removing or hashing the fingerprint from these detach logs as well, since it's a stable card identifier that could leak payment metadata.
this.logger.info({ event: "PAYMENT_METHOD_DETACHED", paymentMethodId: paymentMethod.id, - customerId, - fingerprint + customerId });Also applies to line 210 where fingerprint is logged again.
♻️ Duplicate comments (3)
apps/api/src/core/services/job-queue/job-queue.service.ts (1)
89-94: Full job payload still logged - PII concern persists.The
JOB_ENQUEUEDlog still includes the fulljobobject. As flagged in a past review, this risks logging sensitive data (e.g., email addresses, message content) for jobs like notifications.apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts (1)
49-49:as anycast persists - consider using proper types.The
as anycasts on mock return values violate the coding guideline "Never use typeanyor cast to typeany". Consider typinggenerateWalletSettingto return the expected output type, or using a type assertion to the concreteWalletSettingOutputtype instead.- walletSettingRepository.updateById.mockResolvedValue(updatedSetting as any); + walletSettingRepository.updateById.mockResolvedValue(updatedSetting as WalletSettingOutput);Also applies to lines 110, 181, 216.
apps/api/src/billing/repositories/payment-method/payment-method.repository.ts (1)
102-116: Potential race condition still exists increateAsDefault.As noted in the previous review, using
Promise.allto runcreateand#unmarkAsDefaultExcludingconcurrently can still lead to multiple default payment methods if twocreateAsDefaultcalls execute in parallel for the same user. The transaction ensures atomicity within a single call, but doesn't prevent interleaving between concurrent calls.Consider:
- Adding a unique partial index on
(userId, isDefault) WHERE isDefault = trueat the database level (if not already present)- Or sequencing the operations: unmark first, then create
async createAsDefault(input: Omit<PaymentMethodInput, "id" | "isDefault">) { return this.ensureTransaction(async tx => { const id = uuidv4(); - const [output] = await Promise.all([ - this.create({ - ...input, - isDefault: true, - id - }), - this.#unmarkAsDefaultExcluding(id, tx) - ]); + await this.#unmarkAsDefaultExcluding(id, tx); + const output = await this.create({ + ...input, + isDefault: true, + id + }); return this.toOutput(output); }); }
🧹 Nitpick comments (9)
apps/api/src/deployment/services/draining-deployment/draining-deployment.service.spec.ts (1)
200-222: Verify the block calculation in the comment.The comment states "~168 hours = ~1100800 blocks" which implies ~6552 blocks/hour. This seems inconsistent with typical Akash block rates (~580 blocks/hour based on ~6.2 second block time). The test uses
toBeCloseTowith tolerance, so it likely passes, but the comment may confuse future readers.Consider updating the comment to reflect actual expected values or simplify by removing the intermediate calculations.
apps/api/test/seeders/balance.seeder.ts (1)
21-34: Helper implementation is sound; consider determinism only if tests require itThe construction of
balance,deployments, and derivedtotalwith override support is logically correct and type-safe. If you ever hit flaky tests around balances, you might switch from randomfakervalues to fixed defaults, but as a generic seeder this is fine as-is.apps/api/src/billing/services/balances/balances.service.ts (1)
112-121: Minor: consider parallelizing and scoping fiat helpersThe fiat helpers are clear and readable; two small polish ideas you can apply later if desired:
- Run the two conversions in parallel to shave a round‑trip when
StatsServicehits external data:const [balance, deployments] = await Promise.all([ this.toFiatAmount(data.balance), this.toFiatAmount(data.deployments) ]); const total = this.ensure2floatingDigits(balance + deployments);
- If
ensure2floatingDigitsis not intended for external callers, marking itprivatewould better express its helper nature.These are non‑blocking cleanups.
Also applies to: 123-134
apps/api/src/billing/services/wallet-balance-reload-check/README.md (1)
53-53: Add fenced‑code languages to satisfy markdownlint (MD040)The three fenced blocks for the sequence diagram and formula examples currently use bare ``` fences, which triggers MD040. Consider tagging them with a language, e.g.:
```text ...sequence diagram...balance < 0.25 * costUntilTargetDateInFiatreloadAmount = max(costUntilTargetDateInFiat - balance, $20)This keeps the existing formatting while making markdownlint happy.
Also applies to: 104-104, 150-150
apps/api/src/billing/routes/stripe-payment-methods/stripe-payment-methods.router.ts (1)
5-12: Default payment‑method routes are well‑structured; consider 204 for mark‑as‑defaultThe new POST/GET
/v1/stripe/payment-methods/defaultroutes are wired cleanly to the controller and schemas, and the 204 behavior on delete matches the description.For
markAsDefault, you currently returnc.json(undefined, 200)while the OpenAPI response has no content schema. Consider returning a204with an empty body (to mirror the delete route) or, alternatively, defining a minimal success payload in the OpenAPI schema so the status/body contract is explicit. Not blocking, but it would tighten the API surface.Also applies to: 45-70, 72-99, 127-148
apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts (1)
48-65: New relation-loading method looks correct.The
findInternalByUserIdWithRelationsmethod correctly fetches wallet settings with related wallet and user data. The implementation properly applies access control viawhereAccessibleByand returns the raw relation-loaded object for the handler to destructure.Consider adding an explicit return type for better type safety and documentation:
- async findInternalByUserIdWithRelations(userId: WalletSettingOutput["userId"]) { + async findInternalByUserIdWithRelations(userId: WalletSettingOutput["userId"]): Promise< + | (DbWalletSettingOutput & { + wallet: { address: string | null; isOldWallet: boolean | null }; + user: ApiPgTables["User"]["$inferSelect"]; + }) + | undefined + > {apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.ts (1)
56-58: UnuseduserIdparameter incancelmethod.The
userIdparameter is never used in the method body. Consider removing it or documenting why it's reserved for future use.- async cancel(userId: string, jobId: string): Promise<void> { + async cancel(jobId: string): Promise<void> { await this.jobQueueService.cancel(WalletBalanceReloadCheck.name, jobId); }Note: If callers depend on the current signature, verify them before changing.
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (1)
23-25: Duplicate type definition.The
Requiretype is already exported from@src/core/types/require.type.ts. Consider importing it instead of redefining.+import { Require } from "@src/core/types/require.type"; import { isPayingUser, PayingUser } from "../paying-user/paying-user"; -type Require<T, K extends keyof T> = Omit<T, K> & { - [P in K]-?: NonNullable<T[P]>; -};apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts (1)
12-16: Consider renaming to avoid type name collision with repository.A
WalletSettingInputtype also exists inwallet-settings.repository.tswith a different structure. While they serve different purposes (service API vs. database operations), the name collision could cause confusion during maintenance or when reviewing type errors.Consider renaming the local interface to clarify its scope:
-export interface WalletSettingInput { +export interface WalletSettingUpdateInput { autoReloadEnabled?: boolean; autoReloadThreshold?: number; autoReloadAmount?: number; }Then update the method signatures accordingly:
async upsertWalletSetting(userId: UserOutput["id"], input: WalletSettingUpdateInput): Promise<...> async #update(userId: UserOutput["id"], settings: WalletSettingUpdateInput): Promise<...> async #create(userId: UserOutput["id"], settings: WalletSettingUpdateInput): Promise<...> async #validate({ prev, next, userId }: { next: WalletSettingUpdateInput; prev?: WalletSettingOutput; userId: string })
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
apps/api/test/functional/__snapshots__/docs.spec.ts.snapis excluded by!**/*.snap
📒 Files selected for processing (44)
apps/api/drizzle.config.ts(1 hunks)apps/api/drizzle/0023_clumsy_vertigo.sql(1 hunks)apps/api/drizzle/meta/0023_snapshot.json(1 hunks)apps/api/drizzle/meta/_journal.json(1 hunks)apps/api/src/auth/services/ability/ability.service.ts(2 hunks)apps/api/src/auth/services/auth.service.ts(2 hunks)apps/api/src/billing/controllers/stripe/stripe.controller.ts(2 hunks)apps/api/src/billing/http-schemas/stripe.schema.ts(3 hunks)apps/api/src/billing/http-schemas/wallet.schema.ts(0 hunks)apps/api/src/billing/model-schemas/payment-method/payment-method.schema.ts(1 hunks)apps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.ts(1 hunks)apps/api/src/billing/model-schemas/wallet-setting/wallet-setting.schema.ts(2 hunks)apps/api/src/billing/repositories/payment-method/payment-method.repository.ts(2 hunks)apps/api/src/billing/repositories/user-wallet/user-wallet.repository.ts(2 hunks)apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts(1 hunks)apps/api/src/billing/routes/stripe-payment-methods/stripe-payment-methods.router.ts(5 hunks)apps/api/src/billing/routes/stripe-webhook/stripe-webhook.router.ts(1 hunks)apps/api/src/billing/services/balances/balances.service.ts(3 hunks)apps/api/src/billing/services/managed-signer/managed-signer.service.spec.ts(6 hunks)apps/api/src/billing/services/managed-signer/managed-signer.service.ts(4 hunks)apps/api/src/billing/services/paying-user/paying-user.ts(1 hunks)apps/api/src/billing/services/refill/refill.service.ts(1 hunks)apps/api/src/billing/services/stripe-webhook/stripe-webhook.service.ts(2 hunks)apps/api/src/billing/services/stripe/stripe.service.ts(6 hunks)apps/api/src/billing/services/wallet-balance-reload-check/README.md(1 hunks)apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts(1 hunks)apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts(2 hunks)apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.ts(2 hunks)apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.ts(1 hunks)apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts(11 hunks)apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts(6 hunks)apps/api/src/core/repositories/base.repository.ts(2 hunks)apps/api/src/core/services/job-queue/job-queue.service.spec.ts(9 hunks)apps/api/src/core/services/job-queue/job-queue.service.ts(4 hunks)apps/api/src/core/services/tx/tx.service.ts(1 hunks)apps/api/src/core/types/require.type.ts(1 hunks)apps/api/src/dashboard/services/stats/stats.service.ts(1 hunks)apps/api/src/deployment/repositories/deployment-setting/deployment-setting.repository.ts(2 hunks)apps/api/src/deployment/services/draining-deployment/draining-deployment.service.spec.ts(7 hunks)apps/api/src/deployment/services/draining-deployment/draining-deployment.service.ts(1 hunks)apps/api/test/seeders/balance.seeder.ts(2 hunks)apps/api/test/seeders/database-payment-method.seeder.ts(1 hunks)apps/api/test/seeders/payment-method.seeder.ts(2 hunks)packages/http-sdk/src/stripe/stripe.service.ts(0 hunks)
💤 Files with no reviewable changes (2)
- packages/http-sdk/src/stripe/stripe.service.ts
- apps/api/src/billing/http-schemas/wallet.schema.ts
🚧 Files skipped from review as they are similar to previous changes (17)
- apps/api/src/billing/repositories/user-wallet/user-wallet.repository.ts
- apps/api/src/core/types/require.type.ts
- apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts
- apps/api/src/core/repositories/base.repository.ts
- apps/api/src/billing/services/refill/refill.service.ts
- apps/api/test/seeders/database-payment-method.seeder.ts
- apps/api/src/core/services/job-queue/job-queue.service.spec.ts
- apps/api/src/deployment/services/draining-deployment/draining-deployment.service.ts
- apps/api/src/dashboard/services/stats/stats.service.ts
- apps/api/test/seeders/payment-method.seeder.ts
- apps/api/src/deployment/repositories/deployment-setting/deployment-setting.repository.ts
- apps/api/src/billing/services/managed-signer/managed-signer.service.spec.ts
- apps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.ts
- apps/api/src/auth/services/auth.service.ts
- apps/api/drizzle/meta/_journal.json
- apps/api/src/billing/routes/stripe-webhook/stripe-webhook.router.ts
- apps/api/drizzle/meta/0023_snapshot.json
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (.cursor/rules/general.mdc)
**/*.{ts,tsx,js}: Never use typeanyor cast to typeany. Always define the proper TypeScript types.
Never use deprecated methods from libraries.
Don't add unnecessary comments to the code.
Files:
apps/api/drizzle.config.tsapps/api/src/billing/services/stripe-webhook/stripe-webhook.service.tsapps/api/test/seeders/balance.seeder.tsapps/api/src/billing/services/paying-user/paying-user.tsapps/api/src/deployment/services/draining-deployment/draining-deployment.service.spec.tsapps/api/src/billing/services/wallet-initializer/wallet-initializer.service.tsapps/api/src/billing/services/balances/balances.service.tsapps/api/src/billing/http-schemas/stripe.schema.tsapps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.tsapps/api/src/core/services/tx/tx.service.tsapps/api/src/billing/routes/stripe-payment-methods/stripe-payment-methods.router.tsapps/api/src/billing/services/managed-signer/managed-signer.service.tsapps/api/src/billing/model-schemas/wallet-setting/wallet-setting.schema.tsapps/api/src/auth/services/ability/ability.service.tsapps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.tsapps/api/src/billing/controllers/stripe/stripe.controller.tsapps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.tsapps/api/src/core/services/job-queue/job-queue.service.tsapps/api/src/billing/services/wallet-settings/wallet-settings.service.tsapps/api/src/billing/repositories/payment-method/payment-method.repository.tsapps/api/src/billing/model-schemas/payment-method/payment-method.schema.tsapps/api/src/billing/services/stripe/stripe.service.tsapps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts
**/*.spec.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/no-jest-mock.mdc)
Don't use
jest.mock()in test files. Instead, usejest-mock-extendedto create mocks and pass mocks as dependencies to the service under testUse
setupfunction instead ofbeforeEachin test files. Thesetupfunction must be at the bottom of the rootdescribeblock, 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>.namein 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/deployment/services/draining-deployment/draining-deployment.service.spec.tsapps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
🧠 Learnings (16)
📚 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-webhook/stripe-webhook.service.tsapps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.tsapps/api/src/billing/controllers/stripe/stripe.controller.tsapps/api/src/billing/services/stripe/stripe.service.ts
📚 Learning: 2025-11-25T17:45:44.790Z
Learnt from: CR
Repo: akash-network/console PR: 0
File: .cursor/rules/no-jest-mock.mdc:0-0
Timestamp: 2025-11-25T17:45:44.790Z
Learning: Applies to **/*.spec.{ts,tsx} : 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
Applied to files:
apps/api/src/deployment/services/draining-deployment/draining-deployment.service.spec.tsapps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
📚 Learning: 2025-11-25T17:45:52.965Z
Learnt from: CR
Repo: akash-network/console PR: 0
File: .cursor/rules/setup-instead-of-before-each.mdc:0-0
Timestamp: 2025-11-25T17:45:52.965Z
Learning: Applies to **/*.spec.{ts,tsx} : 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.
Applied to files:
apps/api/src/deployment/services/draining-deployment/draining-deployment.service.spec.tsapps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
📚 Learning: 2025-11-25T17:45:58.258Z
Learnt from: CR
Repo: akash-network/console PR: 0
File: .cursor/rules/test-descriptions.mdc:0-0
Timestamp: 2025-11-25T17:45:58.258Z
Learning: Applies to **/*.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
Applied to files:
apps/api/src/deployment/services/draining-deployment/draining-deployment.service.spec.ts
📚 Learning: 2025-11-19T15:15:07.283Z
Learnt from: ygrishajev
Repo: akash-network/console PR: 2254
File: apps/api/test/functional/sign-and-broadcast-tx.spec.ts:4-4
Timestamp: 2025-11-19T15:15:07.283Z
Learning: In the Akash Network Console project, when tests use native Node.js fetch (available in Node 18+), fetch-mock should be used for HTTP mocking instead of nock, as nock does not support intercepting native fetch calls. This applies to apps/api/test/functional/sign-and-broadcast-tx.spec.ts and any other tests using native fetch.
Applied to files:
apps/api/src/deployment/services/draining-deployment/draining-deployment.service.spec.tsapps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.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.tsapps/api/src/billing/routes/stripe-payment-methods/stripe-payment-methods.router.tsapps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.tsapps/api/src/billing/controllers/stripe/stripe.controller.ts
📚 Learning: 2025-09-04T04:27:50.638Z
Learnt from: stalniy
Repo: akash-network/console PR: 1868
File: apps/api/src/billing/services/managed-signer/managed-signer.service.ts:1-1
Timestamp: 2025-09-04T04:27:50.638Z
Learning: In the akash-network/console project, importing MsgCreateLease from "akashnetwork/akash-api/v1beta3" instead of v1beta4 is considered non-critical by the maintainers, likely due to backward compatibility or project-specific requirements.
Applied to files:
apps/api/src/billing/services/managed-signer/managed-signer.service.ts
📚 Learning: 2025-09-04T04:27:40.858Z
Learnt from: stalniy
Repo: akash-network/console PR: 1868
File: apps/api/src/billing/services/managed-signer/managed-signer.service.ts:98-107
Timestamp: 2025-09-04T04:27:40.858Z
Learning: In the Akash protocol, when processing MsgCreateLease messages, the bidId.dseq field is always required and guaranteed to be present at runtime, even though TypeScript types may suggest it could be undefined. The non-null assertion operator (!) is appropriate to use in this context.
Applied to files:
apps/api/src/billing/services/managed-signer/managed-signer.service.ts
📚 Learning: 2025-09-29T02:40:22.090Z
Learnt from: stalniy
Repo: akash-network/console PR: 1951
File: apps/api/src/user/services/user/user.service.ts:48-63
Timestamp: 2025-09-29T02:40:22.090Z
Learning: In the Akash Network Console codebase, the users table was originally created by Sequelize and later migrated to Drizzle. Due to this migration history, the unique constraint on the userId field is named "userSetting_userId_unique" rather than following the typical "users_user_id_unique" pattern.
Applied to files:
apps/api/src/billing/model-schemas/wallet-setting/wallet-setting.schema.ts
📚 Learning: 2025-10-31T11:26:42.138Z
Learnt from: stalniy
Repo: akash-network/console PR: 2138
File: apps/api/src/billing/lib/batch-signing-client/batch-signing-client.service.ts:379-382
Timestamp: 2025-10-31T11:26:42.138Z
Learning: In TypeScript/JavaScript, the pattern of checking a cached value and then performing an async operation to fetch it without proper synchronization is race condition unsafe:
```typescript
private async getAddress() {
if (!this.address) {
this.address = await this.wallet.getFirstAddress();
}
return this.address;
}
```
Multiple concurrent calls can all pass the `if (!this.address)` check before any of them sets the value, leading to duplicate async operations. This should be flagged as a race condition. Proper synchronization (mutex, atomic promise caching, or guaranteed single-threaded execution) is required.
Applied to files:
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.tsapps/api/src/billing/controllers/stripe/stripe.controller.tsapps/api/src/billing/repositories/payment-method/payment-method.repository.tsapps/api/src/billing/services/stripe/stripe.service.ts
📚 Learning: 2025-11-25T17:45:39.561Z
Learnt from: CR
Repo: akash-network/console PR: 0
File: .cursor/rules/general.mdc:0-0
Timestamp: 2025-11-25T17:45:39.561Z
Learning: Applies to **/*.{ts,tsx,js} : Never use type `any` or cast to type `any`. Always define the proper TypeScript types.
Applied to files:
apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
📚 Learning: 2025-11-25T17:45:49.180Z
Learnt from: CR
Repo: akash-network/console PR: 0
File: .cursor/rules/query-by-in-tests.mdc:0-0
Timestamp: 2025-11-25T17:45:49.180Z
Learning: Applies to {apps/deploy-web,apps/provider-console}/**/*.spec.tsx : Use `queryBy` methods instead of `getBy` methods in test expectations
Applied to files:
apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
📚 Learning: 2025-10-15T16:39:55.348Z
Learnt from: jzsfkzm
Repo: akash-network/console PR: 2039
File: apps/deploy-web/tests/ui/change-wallets.spec.ts:4-10
Timestamp: 2025-10-15T16:39:55.348Z
Learning: In the Akash Console E2E tests using the context-with-extension fixture, the first wallet is automatically created during fixture setup via `importWalletToLeap` in `apps/deploy-web/tests/ui/fixture/wallet-setup.ts`, so tests that call `frontPage.createWallet()` are creating a second wallet to test wallet switching functionality.
Applied to files:
apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
📚 Learning: 2025-09-04T04:21:26.067Z
Learnt from: stalniy
Repo: akash-network/console PR: 1868
File: apps/api/src/app/services/trial-deployment-lease-created/trial-deployment-lease-created.handler.ts:73-76
Timestamp: 2025-09-04T04:21:26.067Z
Learning: In apps/api job scheduling with JobQueueService, use .toISOString() for startAfter values instead of Date objects to ensure UTC timezone consistency when dates are persisted to the database. Date objects can be ambiguous about timezone during serialization, while ISO strings with .toISOString() explicitly represent UTC.
Applied to files:
apps/api/src/core/services/job-queue/job-queue.service.ts
📚 Learning: 2025-09-04T04:21:26.067Z
Learnt from: stalniy
Repo: akash-network/console PR: 1868
File: apps/api/src/app/services/trial-deployment-lease-created/trial-deployment-lease-created.handler.ts:73-76
Timestamp: 2025-09-04T04:21:26.067Z
Learning: The apps/api codebase consistently uses .toISOString() for startAfter values when scheduling jobs with JobQueueService to ensure UTC timezone consistency. This pattern is used throughout trial-started.handler.ts, trial-deployment-lease-created.handler.ts and other job scheduling code. Do not suggest changing to Date objects as ISO strings provide explicit UTC representation for database storage.
Applied to files:
apps/api/src/core/services/job-queue/job-queue.service.ts
📚 Learning: 2025-11-28T09:17:28.832Z
Learnt from: ygrishajev
Repo: akash-network/console PR: 2274
File: apps/api/drizzle/0023_sad_adam_warlock.sql:1-1
Timestamp: 2025-11-28T09:17:28.832Z
Learning: In the Akash Network console billing system, the `user_wallets.user_id` column is required (NOT NULL). All user wallets must be associated with a user ID, and there are no anonymous wallets without a user_id in production.
Applied to files:
apps/api/drizzle/0023_clumsy_vertigo.sql
🧬 Code graph analysis (13)
apps/api/src/billing/services/stripe-webhook/stripe-webhook.service.ts (2)
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (1)
user(142-166)apps/api/src/billing/services/paying-user/paying-user.ts (1)
assertIsPayingUser(8-10)
apps/api/test/seeders/balance.seeder.ts (2)
apps/api/src/billing/http-schemas/balance.schema.ts (1)
GetBalancesResponseOutput(17-17)apps/deploy-web/src/utils/apiUtils.ts (1)
balance(44-46)
apps/api/src/deployment/services/draining-deployment/draining-deployment.service.spec.ts (4)
apps/api/test/seeders/user-wallet.seeder.ts (1)
UserWalletSeeder(6-30)apps/api/test/seeders/draining-deployment.seeder.ts (1)
DrainingDeploymentSeeder(7-25)apps/api/test/seeders/auto-top-up-deployment.seeder.ts (1)
AutoTopUpDeploymentSeeder(6-21)apps/api/test/mocks/config-service.mock.ts (1)
mockConfigService(16-25)
apps/api/src/billing/http-schemas/stripe.schema.ts (2)
apps/api/src/billing/services/stripe/stripe.service.ts (1)
PaymentMethod(33-33)packages/http-sdk/src/stripe/stripe.types.ts (1)
PaymentMethod(30-41)
apps/api/src/billing/routes/stripe-payment-methods/stripe-payment-methods.router.ts (4)
apps/api/src/billing/controllers/stripe/stripe.controller.ts (3)
createSetupIntent(41-48)markAsDefault(51-56)getDefaultPaymentMethod(59-65)apps/api/src/billing/services/stripe/stripe.service.ts (2)
createSetupIntent(51-57)getDefaultPaymentMethod(122-138)packages/http-sdk/src/stripe/stripe.service.ts (1)
createSetupIntent(23-25)apps/api/src/billing/http-schemas/stripe.schema.ts (2)
PaymentMethodMarkAsDefaultInputSchema(10-14)PaymentMethodResponseSchema(61-63)
apps/api/src/billing/services/managed-signer/managed-signer.service.ts (2)
apps/stats-web/src/components/transactions/akash/MsgCreateDeployment.tsx (1)
MsgCreateDeployment(12-29)apps/stats-web/src/components/transactions/akash/MsgAccountDeposit.tsx (1)
MsgAccountDeposit(10-19)
apps/api/src/billing/model-schemas/wallet-setting/wallet-setting.schema.ts (2)
apps/api/src/user/model-schemas/user/user.schema.ts (1)
Users(8-28)apps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.ts (1)
UserWallets(5-18)
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (8)
apps/api/src/core/types/require.type.ts (1)
Require(1-3)apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts (1)
WalletSettingOutput(19-22)apps/api/src/billing/services/paying-user/paying-user.ts (2)
PayingUser(6-6)isPayingUser(12-14)apps/api/src/billing/http-schemas/balance.schema.ts (1)
GetBalancesResponseOutput(17-17)apps/api/src/billing/http-schemas/stripe.schema.ts (1)
PaymentMethod(241-241)apps/api/src/billing/services/stripe/stripe.service.ts (1)
PaymentMethod(33-33)apps/api/src/core/services/job-queue/job-queue.service.ts (2)
JobPayload(209-209)JobMeta(218-218)apps/api/src/billing/events/wallet-balance-reload-check.ts (1)
WalletBalanceReloadCheck(5-15)
apps/api/src/billing/controllers/stripe/stripe.controller.ts (3)
apps/api/src/auth/services/auth.service.ts (1)
Protected(62-78)apps/api/src/billing/http-schemas/stripe.schema.ts (3)
PaymentMethodMarkAsDefaultInput(240-240)PaymentMethodResponse(243-243)PaymentMethodsResponse(242-242)apps/api/src/core/lib/semaphore.decorator.ts (1)
Semaphore(3-28)
apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts (3)
apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts (2)
WalletSettingInput(12-17)WalletSettingOutput(19-22)apps/api/src/auth/services/auth.service.ts (2)
ability(44-46)ability(48-50)apps/api/src/core/repositories/base.repository.ts (1)
isUniqueViolation(237-239)
apps/api/src/billing/repositories/payment-method/payment-method.repository.ts (2)
apps/api/src/core/providers/postgres.provider.ts (1)
ApiPgTables(80-80)apps/api/src/core/services/tx/tx.service.ts (1)
ApiTransaction(11-11)
apps/api/src/billing/services/stripe/stripe.service.ts (4)
packages/http-sdk/src/stripe/stripe.types.ts (1)
PaymentMethod(30-41)apps/api/src/billing/services/paying-user/paying-user.ts (1)
PayingUser(6-6)apps/api/src/user/repositories/user/user.repository.ts (1)
UserOutput(10-12)apps/api/src/core/services/tx/tx.service.ts (1)
WithTransaction(39-51)
apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts (2)
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (1)
userId(97-140)apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts (2)
userId(58-75)userId(77-105)
🪛 LanguageTool
apps/api/src/billing/services/wallet-balance-reload-check/README.md
[style] ~39-~39: ‘emergency situations’ might be wordy. Consider a shorter alternative.
Context: ... margin before funds run out - Prevents emergency situations ## When Does the Check Run? The handl...
(EN_WORDINESS_PREMIUM_EMERGENCY_SITUATIONS)
🪛 markdownlint-cli2 (0.18.1)
apps/api/src/billing/services/wallet-balance-reload-check/README.md
53-53: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
104-104: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
150-150: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
⏰ 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). (1)
- GitHub Check: validate / validate-app
🔇 Additional comments (50)
apps/api/src/auth/services/ability/ability.service.ts (1)
30-30: LGTM! Payment method permissions properly scoped.The addition of "manage" permissions for PaymentMethod to both REGULAR_USER and REGULAR_PAYING_USER roles is correct. The
userIdcondition ensures users can only manage their own payment methods, following the same secure pattern used for WalletSetting, Alert, and NotificationChannel. Both roles appropriately need this access for payment method setup and management.Also applies to: 42-42
apps/api/src/deployment/services/draining-deployment/draining-deployment.service.spec.ts (5)
32-108: Well-structured test with comprehensive assertions.The test properly validates pagination behavior, marks closed deployments, and filters out closed ones from callback results. The setup pattern and mock usage follow project conventions.
129-141: LGTM!Tests are concise, focused, and follow naming conventions.
143-193: LGTM!Good coverage of success and failure paths. The use of
jest.spyOnto mockcalculateTopUpAmountappropriately isolates the unit under test.
302-343: LGTM!The
setupCalculateCosthelper is well-designed. The"userWallet" in inputcheck correctly distinguishes between a missing key (use default) and an explicitundefinedvalue (test the undefined case).
346-373: LGTM!The
setupfunction follows all coding guidelines: positioned at the bottom of the rootdescribeblock, accepts a single parameter with inline type definition, avoids shared state, and returns all dependencies for test assertions.apps/api/test/seeders/balance.seeder.ts (1)
5-6: Importing response type for the seeder looks goodUsing
GetBalancesResponseOutput["data"]here keeps the test helper in lockstep with the HTTP schema type, avoiding drift if the response shape changes. No issues.apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.ts (3)
5-5: LGTM: Import simplification is appropriate.Removing the
UserWalletInputimport aligns with the method signature change and reduces unnecessary dependencies.
53-53: LGTM: Method signature simplification improves consistency.Changing the parameter type from
UserWalletInput["userId"]tostringimproves consistency withinitializeAndGrantTrialLimits(line 21) and simplifies the API surface.
53-63: Authorization is properly enforced through the call chain.The
initialize()method doesn't require.accessibleBy()because it's an internal service method called exclusively through authorized paths:
- Via
StripeController.applyCoupon()(protected by@Protecteddecorator)- Via
StripeWebhookService(protected by Stripe webhook signature verification)Both paths validate the caller's identity before invoking
topUpWallet(), which is the sole caller ofinitialize(). No security vulnerability exists.Likely an incorrect or invalid review comment.
apps/api/drizzle.config.ts (1)
5-9: Fail‑fast DB URL check looks goodReading
POSTGRES_DB_URIfromprocess.envand throwing early if it’s missing is a sensible way to surface misconfiguration for the drizzle CLI, and wiring it directly intodbCredentials.urlkeeps the config simple and explicit.Also applies to: 16-16
apps/api/src/core/services/tx/tx.service.ts (1)
11-11: ALS‑backed transaction reuse is soundExporting
ApiTransactionclarifies the transaction type, and usingAsyncLocalStorage<Map<TxType, ApiTransaction>>with aPG_TXkey plus the early return intransaction()cleanly avoids nested drizzle transactions while preserving access to the existing tx viagetPgTx(). This design looks correct and cohesive with the rest of the service.Also applies to: 15-15, 20-25
apps/api/src/billing/services/balances/balances.service.ts (1)
9-9: Verify unit assumptions in fiat conversion
getFullBalanceInFiatandtoFiatAmountassume that:
getFullBalance(...).data.balanceand.deploymentsare in micro‑denom units (e.g.uakt/ 1e6), andStatsService.convertToFiatAmount(amount, coin)expects whole‑token amounts, not micro‑denom.Given
retrieveAndCalcFeeLimitandcalculateDeploymentEscrowBalanceboth work with raw on‑chain amounts, theuTokenAmount / 1_000_000scaling intoFiatAmountlooks plausible, but ifconvertToFiatAmountalready expects micro‑denoms you’d end up scaling twice and under‑reporting balances. Please double‑check the expected units onconvertToFiatAmountand the shape ofcalculateDeploymentEscrowBalanceto ensure there’s no mismatch.Also applies to: 96-110, 112-131
apps/api/drizzle/0023_clumsy_vertigo.sql (1)
1-5: Migration aligns DB with current billing model
- Making
user_wallets.user_idNOT NULL matches the Drizzle model and the existing invariant that wallets are always associated with a user (no anonymous wallets). Based on learnings, this should be safe against existing data but is still worth double‑checking in lower envs.- Adding
is_default boolean DEFAULT false NOT NULLplus the partial unique index on(user_id, is_default)whereis_default = truecorrectly enforces “at most one default payment method per user” while allowing multiple non‑default methods.- Dropping
wallet_settings.auto_reload_thresholdandauto_reload_amountis consistent with the refactored wallet‑settings schema where these fields are no longer used.apps/api/src/billing/model-schemas/wallet-setting/wallet-setting.schema.ts (1)
1-3: WalletSetting schema and relations look consistentThe
WalletSettingtable now cleanly models a single setting row per wallet (wallet_settings_wallet_id_unique) and references bothUserWallets.idandUsers.idwithON DELETE CASCADE, whileWalletSettingRelationscorrectly wiresuserandwalletone‑to‑one relations via those FKs. This aligns well with the surrounding user/wallet schema and should make Drizzle relation queries straightforward.Also applies to: 7-29, 31-40
apps/api/src/billing/services/paying-user/paying-user.ts (1)
1-14: PayingUser type and guards are clear and type‑safeUsing
Require<UserOutput, "stripeCustomerId">plus theisPayingUsertype guard andassertIsPayingUserassertion (with 402) gives a clean, reusable way to enforce “payments set up” preconditions while benefiting from TypeScript narrowing in Stripe-related code paths. This looks solid.apps/api/src/billing/model-schemas/payment-method/payment-method.schema.ts (1)
19-27: Well-designed partial unique index for enforcing single default per user.The
isDefaultcolumn with the filtered unique indexuserIdIsDefaultUniqueensures at the database level that only one payment method per user can be marked as default. This is the correct approach to prevent race conditions when multiple payment methods are attached concurrently.apps/api/src/billing/services/managed-signer/managed-signer.service.ts (2)
1-2: Import versions look intentional.Based on learnings, the Akash protocol message imports from different versions (v1, v1beta4, v1beta5) are considered acceptable by maintainers due to backward compatibility requirements.
68-78: Good addition of wallet reload scheduling after spending transactions.The logic correctly schedules an immediate wallet reload when spending-related messages (
MsgCreateDeploymentorMsgAccountDeposit) are detected. The scheduling happens after successful transaction execution, and the original result is properly returned. Theincludes()pattern with$typestatic properties is consistent with how typeUrl matching is done elsewhere in the codebase and works as expected with proto-generated message classes.apps/api/src/billing/services/stripe-webhook/stripe-webhook.service.ts (1)
144-163: Good implementation of default payment method logic.The approach of determining
isDefaultbased on existing count and parallelizing local creation with remote default marking is efficient. TheassertIsPayingUsercheck ensures proper user state before Stripe operations.The race condition concern from past reviews is addressed by the partial unique index in the schema (
userIdIsDefaultUnique), which prevents multiple defaults at the database level.apps/api/src/core/services/job-queue/job-queue.service.ts (1)
108-115: Clean addition of thecompletemethod.The implementation follows the same pattern as the existing
cancelmethod, with proper logging. This enables handlers to manually complete jobs when needed.apps/api/src/billing/http-schemas/stripe.schema.ts (2)
10-14: LGTM!The new schema follows the established pattern for input schemas with nested
dataobjects, consistent with other schemas in this file likeConfirmPaymentRequestSchema.
61-63: LGTM!The
PaymentMethodResponseSchemaappropriately wrapsPaymentMethodSchemain adataenvelope, maintaining consistency with the response schema patterns used throughout this file.apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.ts (2)
16-24: LGTM!The
scheduleImmediatemethod properly handles the case where wallet settings don't exist, preventing unnecessary job scheduling.
26-54: Good transaction handling and idempotency.The method correctly:
- Uses a transaction to ensure atomicity between job state changes and DB updates
- Handles previous job cleanup with configurable action (cancel vs complete)
- Uses
singletonKeyto prevent duplicate jobs per user- Validates job creation success
apps/api/src/billing/controllers/stripe/stripe.controller.ts (4)
50-56: LGTM!The new
markAsDefaultendpoint properly:
- Uses the
Protecteddecorator with appropriate permissions- Retrieves the paying user via
getCurrentPayingUser()- Delegates to the stripe service with proper authorization
58-65: The 404 handling is now in the service layer.The previous review comment about missing 404 handling is no longer applicable. Looking at
stripe.service.tslines 134-135,getDefaultPaymentMethodnow throws 404 assertions when the payment method is not found in either the database or Stripe.
67-77: LGTM!The non-strict user retrieval pattern is appropriate here - returning an empty array for unauthenticated/non-paying users is a reasonable fallback.
82-87: Previous critical issue has been addressed.The
hasPaymentMethodcall is now properly awaited, fixing the security bug where the Promise was being evaluated as truthy instead of its resolved boolean value.apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts (2)
276-319: Good test setup pattern.The
setupfunction follows the coding guidelines:
- Positioned at the bottom of the root
describeblock- Creates and returns the service under test
- Uses
jest-mock-extendedfor mocking- Avoids shared state
- No explicit return type
The new dependencies (
userRepository,stripeService,walletReloadJobService) are properly wired.
98-103: LGTM!The test correctly verifies that
scheduleForWalletSettingis called with the expected wallet setting properties when creating a new setting with auto-reload enabled.apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (4)
66-75: Clear orchestration flow with Result-based error handling.The handler appropriately:
- Collects resources first with validation
- Only attempts reload if resources are valid
- Schedules next check after successful resource collection
- Logs validation errors without rethrowing (allowing graceful termination)
142-166: Good error handling for payment method retrieval.HTTP errors are converted to structured
ValidationErrorresults, while unexpected errors are re-thrown for proper job failure handling. Theerrorfield inValidationErrorpreserves the original error for logging context.
175-219: Solid reload logic with idempotency protection.Key strengths:
- Uses
idempotencyKeywith job ID to prevent duplicate Stripe charges on retry- Enforces minimum reload amount ($20) per retrieved learning
- Logs both success and failure cases with relevant context
- Re-throws errors to trigger job retry mechanism
221-234: Consider the impact of swallowing scheduling errors.If
scheduleForWalletSettingfails, the error is logged but not re-thrown, meaning the current job completes successfully. This could leave the user without a scheduled next check.Is this intentional? If so, consider adding a comment explaining the rationale. If not, you may want to re-throw or implement a fallback mechanism.
apps/api/src/billing/repositories/payment-method/payment-method.repository.ts (2)
70-81: LGTM!New utility methods
countByUserIdandfindDefaultByUserIdare clean implementations using proper access control viawhereAccessibleBy.
83-100: Previous race condition addressed.The
markAsDefaultmethod now usesensureTransactionand performs the unmark operation after successfully marking the target, which addresses the previous review concern about ordering.apps/api/src/billing/services/stripe/stripe.service.ts (5)
32-33: LGTM!The
PaymentMethodtype extension adds thevalidatedflag to the Stripe type, providing a clear contract for methods that merge local validation state with remote Stripe data.
122-138: Good defensive assertions for payment method retrieval.The method properly:
- Checks for deleted customer (402 status)
- Asserts on both local and remote sources with clear source attribution
- Returns merged data with validation status
140-145: LGTM!Simple ownership verification that properly handles both string and object customer references from Stripe.
147-174: Previous transaction/remote-call concern addressed.The Stripe remote update (
markRemotePaymentMethodAsDefault) is now called after local database operations succeed, reducing the risk of inconsistency. The added timeouts (3 seconds) on Stripe calls help prevent connection pool exhaustion as noted in the previous discussion.
186-216: Clean idempotencyKey implementation.The optional
idempotencyKeyparameter is properly handled:
- Only included in request options when provided
- Uses correct Stripe API parameter structure
This enables the wallet reload handler to prevent duplicate charges on job retries.
apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts (8)
6-10: LGTM: New imports support the expanded validation and job scheduling.The imported dependencies are properly used throughout the refactored code - StripeService for payment validation, WalletReloadJobService for scheduling, isUniqueViolation for error handling, and UserRepository for user lookups.
20-27: LGTM: Constructor properly injects new dependencies.The expanded dependencies (UserRepository, StripeService, WalletReloadJobService) support the enhanced validation and job scheduling logic. Proper dependency injection pattern with readonly modifiers.
43-56: LGTM: Clean upsert flow with proper transaction handling.The refactored upsert flow is clear and correct:
- Try update first
- If no existing setting, create new one
- Arrange job scheduling based on state transition
- Return setting without exposing internal autoReloadJobId
The @WithTransaction() decorator ensures atomicity across the update/create and scheduling operations.
58-75: LGTM: Clean update logic with early returns.The update method properly:
- Returns early if no existing setting (avoiding unnecessary work)
- Validates changes before persisting
- Maintains access control via ability
- Returns both previous and next states for scheduling decisions
77-105: LGTM: Robust create logic with race condition handling.The create method properly handles concurrent creation attempts:
- Uses
isUniqueViolationto detect conflicts- Retries with update on unique violation (race condition)
- Asserts success to prevent silent failures
The validation call without
previs correct for new settings. The userWallet lookup and assertion properly guard against invalid state.
143-153: LGTM: Deletion properly integrated with job cancellation.The delete method correctly:
- Maintains access control checks
- Conditionally cancels the job only if it exists
- Uses
Promise.allfor efficient parallel execution of delete and cancel operations
107-131: Verify that the external service methods are available and properly typed.The validation correctly expands to ensure users have Stripe payment setup before enabling auto-reload. Verification confirms:
- ✓
UserRepository.findById()exists and returnsPromise<UserOutput | undefined>- ✓
StripeService.getDefaultPaymentMethod()exists with signature(user: PayingUser, ability: AnyAbility) => Promise<PaymentMethod>However, do not simplify line 126. The spreading
{ ...user, stripeCustomerId }is necessary, not redundant. SincePayingUseris defined asRequire<UserOutput, "stripeCustomerId">(requiringstripeCustomerIdto be non-optional), the spreading explicitly includes the asserted property to help TypeScript properly narrow the type for thegetDefaultPaymentMethod()call. Removing it would cause a type error.Likely an incorrect or invalid review comment.
133-141: Scheduling logic is correct.The method properly handles transitions: schedules when auto-reload changes from disabled to enabled, and cancels any existing job when disabling. Changes to threshold/amount while enabled don't trigger rescheduling, which is appropriate since the job reads current settings on each execution.
a45589e to
2cdd4d8
Compare
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/api/src/deployment/repositories/deployment-setting/deployment-setting.repository.ts (1)
64-74: Address the LEFT JOIN nullability issue flagged in the comment.The comment on line 73 correctly identifies that
walletIdmay be null due to the LEFT JOIN withUserWallets. The type assertion toAutoTopUpDeployment[]on line 74 is unsafe, asAutoTopUpDeploymentexpectswalletId: number(non-null).Consider changing to INNER JOIN to ensure
walletIdis never null:const items = await this.pg .select({ id: this.table.id, dseq: this.table.dseq, walletId: UserWallets.id, address: UserWallets.address, isOldWallet: UserWallets.isOldWallet }) .from(this.table) - .leftJoin(Users, eq(this.table.userId, Users.id)) - .leftJoin(UserWallets, eq(Users.id, UserWallets.userId)) + .innerJoin(Users, eq(this.table.userId, Users.id)) + .innerJoin(UserWallets, eq(Users.id, UserWallets.userId)) .where(and(...clauses)) .limit(options.limit) .orderBy(desc(this.table.id)); lastId = last(items)?.id; if (items.length) { - // BUGALERT: walletId may be null because of LEFT JOIN. should be INNER JOIN? yield items as AutoTopUpDeployment[]; }If deployments without associated wallets are valid, update the
AutoTopUpDeploymenttype to reflect nullability:export type AutoTopUpDeployment = { id: string; - walletId: number; + walletId: number | null; dseq: string; address: string; isOldWallet: boolean | null; };
♻️ Duplicate comments (1)
apps/api/src/core/services/job-queue/job-queue.service.ts (1)
89-94: Avoid logging full job payload to prevent leaking PII.The
JOB_ENQUEUEDevent logs the entirejobobject (line 91), which may contain sensitive data such as email addresses and message content. This was flagged in a previous review but has not been addressed.Apply this diff:
this.logger.info({ event: "JOB_ENQUEUED", - job, + jobName: job.name, jobId, + version: job.version, options });
🧹 Nitpick comments (3)
apps/api/src/core/services/job-queue/job-queue.service.spec.ts (1)
132-136: Consider consistent naming:jobIdvsid.The cancel event uses
idwhile other job events (STARTED, DONE, FAILED) usejobId. Consider aligning tojobIdfor consistency across all job-related log events.apps/api/src/billing/services/wallet-balance-reload-check/README.md (1)
53-53: Consider adding language specifiers to code blocks.Fenced code blocks at lines 53, 104, and 150 would benefit from language specifiers for syntax highlighting.
For example:
-``` +```text balance < 0.25 * costUntilTargetDateInFiatAlso applies to: 104-104, 150-150 </blockquote></details> <details> <summary>apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts (1)</summary><blockquote> `293-300`: **Simplify the nested ternary logic for improved readability.** The nested ternary expressions determining `userWithStripe` are difficult to follow and maintain. Consider using a more explicit conditional structure or helper function. Apply this diff to simplify: ```diff - const userWithStripe = - input?.user && input.user.stripeCustomerId === null - ? user - : input?.user && input.user.stripeCustomerId - ? user - : user.stripeCustomerId - ? user - : { ...user, stripeCustomerId: faker.string.uuid() }; + const userWithStripe = (() => { + if (input?.user?.stripeCustomerId === null) return user; + if (input?.user?.stripeCustomerId) return user; + if (user.stripeCustomerId) return user; + return { ...user, stripeCustomerId: faker.string.uuid() }; + })();
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
apps/api/test/functional/__snapshots__/docs.spec.ts.snapis excluded by!**/*.snap
📒 Files selected for processing (23)
apps/api/drizzle/0023_clumsy_vertigo.sql(1 hunks)apps/api/drizzle/meta/0023_snapshot.json(1 hunks)apps/api/drizzle/meta/_journal.json(1 hunks)apps/api/src/auth/services/ability/ability.service.ts(2 hunks)apps/api/src/billing/controllers/stripe/stripe.controller.ts(2 hunks)apps/api/src/billing/model-schemas/wallet-setting/wallet-setting.schema.ts(2 hunks)apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts(1 hunks)apps/api/src/billing/services/balances/balances.service.ts(3 hunks)apps/api/src/billing/services/managed-signer/managed-signer.service.spec.ts(6 hunks)apps/api/src/billing/services/managed-signer/managed-signer.service.ts(4 hunks)apps/api/src/billing/services/stripe/stripe.service.ts(6 hunks)apps/api/src/billing/services/wallet-balance-reload-check/README.md(1 hunks)apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts(1 hunks)apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts(2 hunks)apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.ts(1 hunks)apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts(11 hunks)apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts(6 hunks)apps/api/src/core/services/job-queue/job-queue.service.spec.ts(9 hunks)apps/api/src/core/services/job-queue/job-queue.service.ts(4 hunks)apps/api/src/dashboard/services/stats/stats.service.ts(1 hunks)apps/api/src/deployment/repositories/deployment-setting/deployment-setting.repository.ts(2 hunks)apps/api/src/deployment/services/draining-deployment/draining-deployment.service.spec.ts(7 hunks)apps/api/src/deployment/services/draining-deployment/draining-deployment.service.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
- apps/api/src/dashboard/services/stats/stats.service.ts
- apps/api/src/auth/services/ability/ability.service.ts
- apps/api/drizzle/meta/_journal.json
- apps/api/src/billing/model-schemas/wallet-setting/wallet-setting.schema.ts
- apps/api/drizzle/0023_clumsy_vertigo.sql
- apps/api/drizzle/meta/0023_snapshot.json
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (.cursor/rules/general.mdc)
**/*.{ts,tsx,js}: Never use typeanyor cast to typeany. 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/wallet-balance-reload-check/wallet-balance-reload-check.handler.tsapps/api/src/billing/services/managed-signer/managed-signer.service.spec.tsapps/api/src/deployment/services/draining-deployment/draining-deployment.service.tsapps/api/src/billing/controllers/stripe/stripe.controller.tsapps/api/src/core/services/job-queue/job-queue.service.tsapps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.tsapps/api/src/billing/services/stripe/stripe.service.tsapps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.tsapps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.tsapps/api/src/billing/services/balances/balances.service.tsapps/api/src/deployment/services/draining-deployment/draining-deployment.service.spec.tsapps/api/src/deployment/repositories/deployment-setting/deployment-setting.repository.tsapps/api/src/billing/services/wallet-settings/wallet-settings.service.tsapps/api/src/billing/services/managed-signer/managed-signer.service.tsapps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.tsapps/api/src/core/services/job-queue/job-queue.service.spec.ts
**/*.spec.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/no-jest-mock.mdc)
Don't use
jest.mock()in test files. Instead, usejest-mock-extendedto create mocks and pass mocks as dependencies to the service under testUse
setupfunction instead ofbeforeEachin test files. Thesetupfunction must be at the bottom of the rootdescribeblock, 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>.namein 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/managed-signer/managed-signer.service.spec.tsapps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.tsapps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.tsapps/api/src/deployment/services/draining-deployment/draining-deployment.service.spec.tsapps/api/src/core/services/job-queue/job-queue.service.spec.ts
🧠 Learnings (15)
📚 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/wallet-balance-reload-check/wallet-balance-reload-check.handler.tsapps/api/src/billing/controllers/stripe/stripe.controller.tsapps/api/src/billing/services/stripe/stripe.service.ts
📚 Learning: 2025-10-31T11:26:42.138Z
Learnt from: stalniy
Repo: akash-network/console PR: 2138
File: apps/api/src/billing/lib/batch-signing-client/batch-signing-client.service.ts:379-382
Timestamp: 2025-10-31T11:26:42.138Z
Learning: In TypeScript/JavaScript, the pattern of checking a cached value and then performing an async operation to fetch it without proper synchronization is race condition unsafe:
```typescript
private async getAddress() {
if (!this.address) {
this.address = await this.wallet.getFirstAddress();
}
return this.address;
}
```
Multiple concurrent calls can all pass the `if (!this.address)` check before any of them sets the value, leading to duplicate async operations. This should be flagged as a race condition. Proper synchronization (mutex, atomic promise caching, or guaranteed single-threaded execution) is required.
Applied to files:
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.tsapps/api/src/billing/controllers/stripe/stripe.controller.tsapps/api/src/billing/services/stripe/stripe.service.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/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.tsapps/api/src/billing/controllers/stripe/stripe.controller.tsapps/api/src/billing/services/stripe/stripe.service.ts
📚 Learning: 2025-09-04T04:27:50.638Z
Learnt from: stalniy
Repo: akash-network/console PR: 1868
File: apps/api/src/billing/services/managed-signer/managed-signer.service.ts:1-1
Timestamp: 2025-09-04T04:27:50.638Z
Learning: In the akash-network/console project, importing MsgCreateLease from "akashnetwork/akash-api/v1beta3" instead of v1beta4 is considered non-critical by the maintainers, likely due to backward compatibility or project-specific requirements.
Applied to files:
apps/api/src/billing/services/managed-signer/managed-signer.service.spec.tsapps/api/src/billing/services/managed-signer/managed-signer.service.tsapps/api/src/core/services/job-queue/job-queue.service.spec.ts
📚 Learning: 2025-09-04T04:27:40.858Z
Learnt from: stalniy
Repo: akash-network/console PR: 1868
File: apps/api/src/billing/services/managed-signer/managed-signer.service.ts:98-107
Timestamp: 2025-09-04T04:27:40.858Z
Learning: In the Akash protocol, when processing MsgCreateLease messages, the bidId.dseq field is always required and guaranteed to be present at runtime, even though TypeScript types may suggest it could be undefined. The non-null assertion operator (!) is appropriate to use in this context.
Applied to files:
apps/api/src/billing/services/managed-signer/managed-signer.service.spec.tsapps/api/src/billing/services/managed-signer/managed-signer.service.ts
📚 Learning: 2025-09-04T04:21:26.067Z
Learnt from: stalniy
Repo: akash-network/console PR: 1868
File: apps/api/src/app/services/trial-deployment-lease-created/trial-deployment-lease-created.handler.ts:73-76
Timestamp: 2025-09-04T04:21:26.067Z
Learning: In apps/api job scheduling with JobQueueService, use .toISOString() for startAfter values instead of Date objects to ensure UTC timezone consistency when dates are persisted to the database. Date objects can be ambiguous about timezone during serialization, while ISO strings with .toISOString() explicitly represent UTC.
Applied to files:
apps/api/src/core/services/job-queue/job-queue.service.tsapps/api/src/core/services/job-queue/job-queue.service.spec.ts
📚 Learning: 2025-10-15T16:39:55.348Z
Learnt from: jzsfkzm
Repo: akash-network/console PR: 2039
File: apps/deploy-web/tests/ui/change-wallets.spec.ts:4-10
Timestamp: 2025-10-15T16:39:55.348Z
Learning: In the Akash Console E2E tests using the context-with-extension fixture, the first wallet is automatically created during fixture setup via `importWalletToLeap` in `apps/deploy-web/tests/ui/fixture/wallet-setup.ts`, so tests that call `frontPage.createWallet()` are creating a second wallet to test wallet switching functionality.
Applied to files:
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.tsapps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
📚 Learning: 2025-11-25T17:45:44.790Z
Learnt from: CR
Repo: akash-network/console PR: 0
File: .cursor/rules/no-jest-mock.mdc:0-0
Timestamp: 2025-11-25T17:45:44.790Z
Learning: Applies to **/*.spec.{ts,tsx} : 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
Applied to files:
apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.tsapps/api/src/deployment/services/draining-deployment/draining-deployment.service.spec.tsapps/api/src/core/services/job-queue/job-queue.service.spec.ts
📚 Learning: 2025-11-25T17:45:39.561Z
Learnt from: CR
Repo: akash-network/console PR: 0
File: .cursor/rules/general.mdc:0-0
Timestamp: 2025-11-25T17:45:39.561Z
Learning: Applies to **/*.{ts,tsx,js} : Never use type `any` or cast to type `any`. Always define the proper TypeScript types.
Applied to files:
apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
📚 Learning: 2025-11-25T17:45:52.965Z
Learnt from: CR
Repo: akash-network/console PR: 0
File: .cursor/rules/setup-instead-of-before-each.mdc:0-0
Timestamp: 2025-11-25T17:45:52.965Z
Learning: Applies to **/*.spec.{ts,tsx} : 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.
Applied to files:
apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.tsapps/api/src/deployment/services/draining-deployment/draining-deployment.service.spec.ts
📚 Learning: 2025-11-19T15:15:07.283Z
Learnt from: ygrishajev
Repo: akash-network/console PR: 2254
File: apps/api/test/functional/sign-and-broadcast-tx.spec.ts:4-4
Timestamp: 2025-11-19T15:15:07.283Z
Learning: In the Akash Network Console project, when tests use native Node.js fetch (available in Node 18+), fetch-mock should be used for HTTP mocking instead of nock, as nock does not support intercepting native fetch calls. This applies to apps/api/test/functional/sign-and-broadcast-tx.spec.ts and any other tests using native fetch.
Applied to files:
apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.tsapps/api/src/deployment/services/draining-deployment/draining-deployment.service.spec.ts
📚 Learning: 2025-11-25T17:45:49.180Z
Learnt from: CR
Repo: akash-network/console PR: 0
File: .cursor/rules/query-by-in-tests.mdc:0-0
Timestamp: 2025-11-25T17:45:49.180Z
Learning: Applies to {apps/deploy-web,apps/provider-console}/**/*.spec.tsx : Use `queryBy` methods instead of `getBy` methods in test expectations
Applied to files:
apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
📚 Learning: 2025-11-25T17:45:58.258Z
Learnt from: CR
Repo: akash-network/console PR: 0
File: .cursor/rules/test-descriptions.mdc:0-0
Timestamp: 2025-11-25T17:45:58.258Z
Learning: Applies to **/*.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
Applied to files:
apps/api/src/deployment/services/draining-deployment/draining-deployment.service.spec.ts
📚 Learning: 2025-09-04T04:21:26.067Z
Learnt from: stalniy
Repo: akash-network/console PR: 1868
File: apps/api/src/app/services/trial-deployment-lease-created/trial-deployment-lease-created.handler.ts:73-76
Timestamp: 2025-09-04T04:21:26.067Z
Learning: The apps/api codebase consistently uses .toISOString() for startAfter values when scheduling jobs with JobQueueService to ensure UTC timezone consistency. This pattern is used throughout trial-started.handler.ts, trial-deployment-lease-created.handler.ts and other job scheduling code. Do not suggest changing to Date objects as ISO strings provide explicit UTC representation for database storage.
Applied to files:
apps/api/src/core/services/job-queue/job-queue.service.spec.ts
📚 Learning: 2025-07-24T17:00:52.361Z
Learnt from: baktun14
Repo: akash-network/console PR: 1725
File: apps/api/src/utils/constants.ts:5-5
Timestamp: 2025-07-24T17:00:52.361Z
Learning: In the Akash Network Console project, when cross-cutting concerns or broader refactoring issues are identified during PR review, the preferred approach is to create a separate GitHub issue to track the work rather than expanding the scope of the current PR. This maintains focus and allows for proper planning of architectural improvements.
Applied to files:
apps/api/src/core/services/job-queue/job-queue.service.spec.ts
🧬 Code graph analysis (12)
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (8)
apps/api/src/core/types/require.type.ts (1)
Require(1-3)apps/api/src/billing/repositories/user-wallet/user-wallet.repository.ts (1)
UserWalletOutput(18-22)apps/api/src/billing/services/paying-user/paying-user.ts (2)
PayingUser(6-6)isPayingUser(12-14)apps/api/src/billing/http-schemas/balance.schema.ts (1)
GetBalancesResponseOutput(17-17)apps/api/src/billing/services/stripe/stripe.service.ts (1)
PaymentMethod(33-33)apps/api/src/billing/http-schemas/stripe.schema.ts (1)
PaymentMethod(241-241)packages/http-sdk/src/stripe/stripe.service.ts (1)
StripeService(18-94)apps/api/src/core/services/job-queue/job-queue.service.ts (2)
JobPayload(209-209)JobMeta(218-218)
apps/api/src/billing/services/managed-signer/managed-signer.service.spec.ts (2)
apps/stats-web/src/components/transactions/akash/MsgCreateDeployment.tsx (1)
MsgCreateDeployment(12-29)apps/stats-web/src/components/transactions/akash/MsgAccountDeposit.tsx (1)
MsgAccountDeposit(10-19)
apps/api/src/deployment/services/draining-deployment/draining-deployment.service.ts (2)
apps/api/src/utils/constants.ts (1)
averageBlockCountInAnHour(8-8)apps/indexer/drizzle/schema.ts (1)
deployment(185-208)
apps/api/src/billing/controllers/stripe/stripe.controller.ts (3)
apps/api/src/auth/services/auth.service.ts (5)
Protected(62-78)currentUser(13-15)currentUser(17-24)ability(44-46)ability(48-50)apps/api/src/billing/http-schemas/stripe.schema.ts (5)
PaymentMethodMarkAsDefaultInput(240-240)PaymentMethodResponse(243-243)PaymentMethodsResponse(242-242)ConfirmPaymentRequest(244-244)ConfirmPaymentResponse(247-247)apps/api/src/core/lib/semaphore.decorator.ts (1)
Semaphore(3-28)
apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.ts (3)
apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts (1)
WalletSettingOutput(19-22)apps/api/src/core/services/job-queue/job-queue.service.ts (1)
EnqueueOptions(227-227)apps/api/src/billing/events/wallet-balance-reload-check.ts (1)
WalletBalanceReloadCheck(5-15)
apps/api/src/billing/services/stripe/stripe.service.ts (5)
apps/api/src/billing/http-schemas/stripe.schema.ts (2)
PaymentMethod(241-241)PaymentIntentResult(245-245)packages/http-sdk/src/stripe/stripe.types.ts (1)
PaymentMethod(30-41)apps/api/src/billing/services/paying-user/paying-user.ts (1)
PayingUser(6-6)apps/api/src/user/repositories/user/user.repository.ts (1)
UserOutput(10-12)apps/api/src/core/services/tx/tx.service.ts (1)
WithTransaction(39-51)
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts (1)
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (3)
job(75-93)error(161-166)user(140-159)
apps/api/src/deployment/repositories/deployment-setting/deployment-setting.repository.ts (1)
apps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.ts (1)
UserWallets(5-18)
apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts (4)
apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts (2)
WalletSettingInput(12-17)WalletSettingOutput(19-22)packages/http-sdk/src/stripe/stripe.service.ts (1)
StripeService(18-94)apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (3)
userId(95-138)error(161-166)user(140-159)apps/api/src/core/repositories/base.repository.ts (1)
isUniqueViolation(237-239)
apps/api/src/billing/services/managed-signer/managed-signer.service.ts (2)
apps/stats-web/src/components/transactions/akash/MsgCreateDeployment.tsx (1)
MsgCreateDeployment(12-29)apps/stats-web/src/components/transactions/akash/MsgAccountDeposit.tsx (1)
MsgAccountDeposit(10-19)
apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts (2)
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (1)
userId(95-138)apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts (2)
userId(58-75)userId(77-105)
apps/api/src/core/services/job-queue/job-queue.service.spec.ts (1)
apps/api/src/core/services/job-queue/job-queue.service.ts (2)
setup(185-192)Job(199-207)
🪛 LanguageTool
apps/api/src/billing/services/wallet-balance-reload-check/README.md
[style] ~39-~39: ‘emergency situations’ might be wordy. Consider a shorter alternative.
Context: ... margin before funds run out - Prevents emergency situations ## When Does the Check Run? The handl...
(EN_WORDINESS_PREMIUM_EMERGENCY_SITUATIONS)
🪛 markdownlint-cli2 (0.18.1)
apps/api/src/billing/services/wallet-balance-reload-check/README.md
53-53: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
104-104: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
150-150: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
⏰ 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). (5)
- GitHub Check: validate / validate-app
- GitHub Check: validate / validate-app
- GitHub Check: test-build
- GitHub Check: test-build
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (19)
apps/api/src/core/services/job-queue/job-queue.service.spec.ts (4)
67-76: Good addition of edge case test for duplicate handler registration.This test ensures the service properly guards against handler conflicts on the same queue, which prevents subtle runtime bugs.
95-101: LGTM!The enhanced logging with
jobIdandoptionsimproves job traceability. The test correctly validates both the log structure and return value.
152-180: LGTM!The test correctly validates the new handler signature with JobMeta context. The job id is properly passed through the worker execution flow and verified in both handler calls and log events.
300-303: LGTM!The
runWithContextmock correctly simulates the context execution pattern by invoking the callback. The type assertion ensures compatibility with the service signature.apps/api/src/deployment/repositories/deployment-setting/deployment-setting.repository.ts (1)
41-53: LGTM! Address filtering implemented correctly.The optional
addressparameter and its filtering logic are correctly implemented. The filter integrates cleanly into the existing pagination pattern.apps/api/src/deployment/services/draining-deployment/draining-deployment.service.ts (1)
96-134: LGTM! Cost calculation logic is correct.The method correctly:
- Returns early if wallet/address is missing
- Calculates target height based on the time difference
- Filters deployments that would close within the target range
- Calculates the cost to keep each deployment running until the target date
The JSDoc provides clear documentation of the method's purpose.
apps/api/src/deployment/services/draining-deployment/draining-deployment.service.spec.ts (1)
195-353: Excellent test coverage for the new method!The tests comprehensively cover:
- Happy path with multiple deployments
- Edge cases (missing wallet, null address, no deployments)
- Boundary conditions (null predictedClosedHeight, out-of-range heights)
The
setupCalculateCosthelper function provides a clean way to configure test scenarios. The test structure follows all coding guidelines, including the use of thesetupfunction pattern andjest-mock-extendedfor mocking.apps/api/src/billing/services/managed-signer/managed-signer.service.ts (1)
74-76: Verify error handling strategy when scheduling fails after successful transaction.Currently, if
scheduleImmediatethrows an error, it propagates to the caller even though the transaction has already succeeded (line 70). This means the caller receives an error despite a successful blockchain transaction.Consider wrapping the scheduling call in try-catch and logging failures instead, or document that this behavior is intentional for alerting purposes.
apps/api/src/billing/services/balances/balances.service.ts (1)
112-134: LGTM! Clean fiat conversion implementation.The fiat conversion flow is well-structured:
- Memoization is consistent with
getFullBalance- Token conversion (÷1,000,000) is correct for uTokens
- Two-decimal formatting is appropriate for currency display
- Coin mapping logic correctly delegates to
StatsServiceapps/api/src/billing/controllers/stripe/stripe.controller.ts (1)
50-79: LGTM! Payment method handling correctly implemented.The new payment method endpoints are well-structured:
- Authorization checks are properly applied via
@Protecteddecorators- 404 handling for missing default payment method (line 64) addresses past review feedback
- Async ownership verification with
await(line 89) addresses past review feedback- Graceful handling when no paying user exists (lines 71-78)
apps/api/src/billing/services/stripe/stripe.service.ts (1)
146-183: LGTM! Payment method default handling correctly sequenced.The refactored flow addresses past review feedback:
- Stripe updates execute after local DB operations (lines 156, 170)
- 3-second timeouts prevent connection pool exhaustion (lines 150, 181)
- Transaction rollback protects consistency if Stripe calls fail
- Idempotency through local record creation prevents duplicate work
apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.ts (1)
26-54: LGTM! Transactional job scheduling correctly implemented.The scheduling logic is well-designed:
- Transaction wrapper ensures atomicity between DB update and job enqueue
- Singleton key prevents duplicate jobs (line 43)
- Pre-existing job cleanup (cancel/complete) prevents orphaned jobs
- Throwing on enqueue failure triggers rollback of wallet setting update
apps/api/src/core/services/job-queue/job-queue.service.ts (1)
218-224: LGTM! JobMeta extension enables better job context.The JobHandler interface extension is well-designed:
- Optional
jobparameter allows gradual handler migrationJobMetatype provides job ID for tracing and idempotency- Handler invocation (line 155) correctly passes the context
apps/api/src/billing/services/managed-signer/managed-signer.service.spec.ts (1)
492-578: LGTM! Comprehensive test coverage for wallet reload scheduling.The new test suite effectively validates the scheduling behavior:
- Verifies spending messages (MsgCreateDeployment, MsgAccountDeposit) trigger immediate reload
- Confirms non-spending messages skip scheduling
- Properly mocks dependencies (registryMock, walletReloadJobService)
- Follows project testing conventions
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (2)
75-159: LGTM! Well-structured resource validation flow.The resource collection and validation logic properly uses the Result pattern to handle errors, performs comprehensive checks (wallet settings, auto-reload enabled, wallet initialized, Stripe customer ID), and returns appropriate validation errors for each failure case.
168-233: LGTM! Solid reload and scheduling implementation.The reload logic correctly:
- Skips reloads when cost is zero or balance is sufficient (line 180)
- Applies minimum reload amount of $20 (line 189)
- Uses idempotent payment intent creation with job-scoped key (line 197)
- Re-throws scheduling errors to enable pgboss retry (line 226)
The implementation properly handles edge cases and ensures reliability.
apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts (2)
107-131: LGTM! Comprehensive validation with proper Stripe integration.The validation logic correctly:
- Ensures threshold and amount are provided when auto-reload is enabled
- Verifies user existence and Stripe customer ID setup
- Confirms default payment method exists before enabling auto-reload
The error messages are clear and actionable for users.
58-105: LGTM! Robust upsert implementation with race condition handling.The update/create flow properly:
- Returns early from update if no existing setting (line 63-65)
- Uses
isUniqueViolationto detect race conditions (line 93)- Retries via update path on duplicate key violations (line 94)
- Validates settings before persistence
This prevents race conditions during concurrent upserts.
apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts (1)
38-46: Critical:toOutputmethod is called but was removed.Line 45 calls
this.toOutput(walletSetting), but according to the AI summary, thetoOutputmethod has been removed from this class. This will cause a runtime error.The method should likely return the walletSetting directly after the findFirst operation:
async findByUserId(userId: WalletSettingOutput["userId"]): Promise<WalletSettingOutput | undefined> { const walletSetting = await this.cursor.query.WalletSetting.findFirst({ where: this.whereAccessibleBy(eq(this.table.userId, userId)) }); if (!walletSetting) return undefined; - return this.toOutput(walletSetting); + return walletSetting; }Likely an incorrect or invalid review comment.
apps/api/src/billing/services/managed-signer/managed-signer.service.ts
Outdated
Show resolved
Hide resolved
.../api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts
Outdated
Show resolved
Hide resolved
apps/api/src/deployment/services/draining-deployment/draining-deployment.service.spec.ts
Outdated
Show resolved
Hide resolved
apps/api/src/deployment/services/draining-deployment/draining-deployment.service.spec.ts
Outdated
Show resolved
Hide resolved
2cdd4d8 to
20f5527
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/api/src/deployment/repositories/deployment-setting/deployment-setting.repository.ts (1)
73-74: Existing type safety issue now more critical with address filtering.The BUGALERT comment correctly identifies that
walletIdcan benulldue to the LEFT JOIN, but the type assertion toAutoTopUpDeployment[]masks this. With the new address filtering feature, this issue becomes more critical because:
- The
AutoTopUpDeploymenttype declareswalletId: number(non-nullable)- Consumers of this method may assume
walletIdis always present- Runtime null reference errors could occur when accessing
walletIdConsider changing the LEFT JOIN to INNER JOIN for UserWallets to ensure all returned deployments have valid wallet data, or update the
AutoTopUpDeploymenttype to reflect thatwalletIdcan be nullable.// Update the type to reflect reality export type AutoTopUpDeployment = { id: string; - walletId: number; + walletId: number | null; dseq: string; - address: string; + address: string | null; isOldWallet: boolean | null; };Or, convert to INNER JOIN if deployments without wallets should be excluded:
.from(this.table) - .leftJoin(Users, eq(this.table.userId, Users.id)) - .leftJoin(UserWallets, eq(Users.id, UserWallets.userId)) + .innerJoin(Users, eq(this.table.userId, Users.id)) + .innerJoin(UserWallets, eq(Users.id, UserWallets.userId)) .where(and(...clauses))
♻️ Duplicate comments (4)
apps/api/src/auth/services/ability/ability.service.ts (1)
30-42: Re-evaluatePaymentMethodmanagerules – appear unused and potentially misleading.These two
"manage"rules for thePaymentMethodsubject still look unreferenced in the current code paths (Stripe controllers are guarded viaStripePayment+ paying-user checks). Keeping unused ability rules around PaymentMethod can be confusing and might accidentally be relied on later in ways that don’t align with the actual authorization flow.If they’re not wired anywhere, consider removing them (or clearly documenting the intended future use) to avoid divergent sources of truth for payment-method permissions.
apps/api/src/core/services/job-queue/job-queue.service.spec.ts (1)
95-100: Align logging expectations with reduced job payload once enqueue logging is trimmed.The
enqueuetest still asserts thatlogger.inforeceives the fulljobobject. If you adopt the earlier change to only log metadata (name/id/version), this expectation should be relaxed to those fields instead of the entire payload to avoid coupling tests to PII-heavy logging.apps/api/src/core/services/job-queue/job-queue.service.ts (1)
89-94: Avoid logging full job payload to reduce PII exposure.
JOB_ENQUEUEDstill logs the entirejobobject, which for many jobs can include user identifiers or message content. Atinfolevel this is a privacy and noise concern; logs generally only need non-sensitive metadata.Consider trimming this to metadata only, e.g.:
- this.logger.info({ - event: "JOB_ENQUEUED", - job, - jobId, - options - }); + this.logger.info({ + event: "JOB_ENQUEUED", + jobName: job.name, + jobId, + version: job.version, + options + });This preserves traceability without leaking job payloads into logs.
apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts (1)
49-49: Replaceas anycasts with proper types.This cast (and similar ones at lines 110, 181, 216) violates the coding guideline "Never use type
any". ImportWalletSettingOutputand use it for proper typing, or adjust thegenerateWalletSettingseeder to return the correct type.- walletSettingRepository.updateById.mockResolvedValue(updatedSetting as any); + walletSettingRepository.updateById.mockResolvedValue(updatedSetting as WalletSettingOutput);
🧹 Nitpick comments (9)
apps/api/src/deployment/services/draining-deployment/draining-deployment.service.ts (2)
99-101: Redundant null check for address.Since the wallet is fetched by
addresson line 97, ifuserWalletexists,userWallet.addressshould not be null or empty. This check might be defensive programming, but it's logically redundant in this context.- if (!userWallet || !userWallet.address) { + if (!userWallet) { return 0; }
126-126: Move constant calculation outside the loop.
blocksNeeded = targetHeight - currentHeightproduces the same value for every iteration since bothtargetHeightandcurrentHeightare constant within the method. Calculate it once before the loop to improve readability and avoid redundant computation.+ const blocksFromNowToTarget = targetHeight - currentHeight; + for await (const deploymentSettings of this.deploymentSettingRepository.paginateAutoTopUpDeployments({ address, limit: 100 })) { // ... for (const { predictedClosedHeight, blockRate } of drainingDeployments) { if (predictedClosedHeight && predictedClosedHeight >= currentHeight && predictedClosedHeight <= targetHeight) { - const blocksNeeded = targetHeight - currentHeight; - const amountNeeded = Math.floor(blockRate * blocksNeeded); + const amountNeeded = Math.floor(blockRate * blocksFromNowToTarget); totalAmount += amountNeeded; } } }Note: This suggestion assumes the current logic is correct. See the previous comment about potential logic issues with the blocks calculation.
apps/api/src/billing/services/wallet-balance-reload-check/README.md (1)
53-98: Add languages to fenced code blocks to satisfy markdownlint.The three fenced blocks (sequence diagram, threshold formula, reload-amount formula) currently use bare ``` fences, which triggers MD040. You can keep them as plain text while satisfying the linter by tagging them, e.g.:
-``` +```text User enables auto-reload ...and similarly for the formula blocks around lines 104 and 150.
Also applies to: 104-107, 150-152
apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.ts (1)
16-58: Wallet reload scheduling flow looks solid; consider cleaning up unuseduserIdincancel.The transactional
scheduleForWalletSettinglogic (complete/cancel previous job, updateautoReloadJobId, enqueue with a per-user singleton key, and fail fast on missingcreatedJobId) is coherent and matches the reload-check design.Minor nit:
cancel(userId: string, jobId: string)ignoresuserId. If there’s no caller need for it, you could drop that parameter; otherwise, consider using it for logging/consistency so the signature reflects actual behavior.apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts (1)
21-279: Comprehensive handler tests; only minor complexity insetuphelper.The test suite gives very good coverage of the reload decision logic (threshold edge cases, minimum amount, next-check scheduling, scheduling failures, and all validation branches) and follows the project’s testing conventions (subject-based
describe,setupfactory, jest-mock-extended).The only minor nit is that the
userWithStripederivation and related ternaries insetupare a bit hard to read. If this helper grows further, consider breaking that into a small, named helper or explicit if/else blocks for the different scenarios (no user, user with stripe ID, user with null stripe ID) to make future maintenance easier.Also applies to: 282-373
apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts (1)
48-65: Add explicit return type annotation for better type safety.The method lacks an explicit return type, which would improve code clarity and catch potential type mismatches at compile time. Consider adding the return type based on the query structure.
- async findInternalByUserIdWithRelations(userId: WalletSettingOutput["userId"]) { + async findInternalByUserIdWithRelations(userId: WalletSettingOutput["userId"]): Promise< + | (DbWalletSettingOutput & { + wallet: Pick<ApiPgTables["UserWallet"]["$inferSelect"], "address" | "isOldWallet">; + user: ApiPgTables["User"]["$inferSelect"]; + }) + | undefined + > {Alternatively, define a dedicated type for this return shape to improve reusability.
apps/api/src/billing/controllers/stripe/stripe.controller.ts (1)
83-89: Consider using 403 instead of 500 for the user assertion.Line 86 uses a 500 status code, but since
getCurrentPayingUser({ strict: false })can returnundefinedfor users without a Stripe customer ID, a 403 (Forbidden) might be more semantically accurate - the user isn't authorized to make payments without proper configuration, rather than it being an internal server error.- assert(currentUser, 500, "Payment account not properly configured. Please contact support."); + assert(currentUser, 403, "Payment account not properly configured. Please contact support.");apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (1)
137-156: Consider extracting the hardcoded ability to a constant or documenting the rationale.The method creates a
MongoAbilityinline with hardcoded permissions. While this is acceptable for a background job handler (no request-scoped auth context), it would benefit from a brief comment explaining why this approach is used, or extracting the ability definition to a class-level constant for clarity.+ // Background jobs don't have request-scoped auth; use minimal required permissions + readonly #paymentMethodReadAbility = createMongoAbility([ + { action: "read", subject: "PaymentMethod" } + ]); + async #getDefaultPaymentMethod(user: PayingUser): Promise<Result<PaymentMethod, ValidationError>> { const paymentMethod = await this.stripeService.getDefaultPaymentMethod( user, - createMongoAbility([ - { - action: "read", - subject: "PaymentMethod" - } - ]) + this.#paymentMethodReadAbility );apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts (1)
58-75: Ambiguous failure handling - empty object masks the reason.When the update returns no result (line 70), the method returns
{}, causingupsertWalletSettingto attempt a create operation (line 48). This could occur due to:
- Permission denied via
accessibleBy- Record deleted between read and update
- Database constraint failure
If the cause is a permission issue or validation failure, the subsequent create will also fail. Consider distinguishing between "not found" (where create makes sense) and "update failed" (where propagating the error is better).
Consider returning a more explicit result or throwing an error if the update unexpectedly returns null:
const next = await this.walletSettingRepository.accessibleBy(ability, "update").updateById(prev.id, settings, { returning: true }); if (!next) { - return {}; + throw new Error("Failed to update wallet setting"); } return { prev, next };This ensures that legitimate failures are surfaced rather than masked.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
apps/api/test/functional/__snapshots__/docs.spec.ts.snapis excluded by!**/*.snap
📒 Files selected for processing (23)
apps/api/drizzle/0023_clumsy_vertigo.sql(1 hunks)apps/api/drizzle/meta/0023_snapshot.json(1 hunks)apps/api/drizzle/meta/_journal.json(1 hunks)apps/api/src/auth/services/ability/ability.service.ts(2 hunks)apps/api/src/billing/controllers/stripe/stripe.controller.ts(2 hunks)apps/api/src/billing/model-schemas/wallet-setting/wallet-setting.schema.ts(2 hunks)apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts(1 hunks)apps/api/src/billing/services/balances/balances.service.ts(3 hunks)apps/api/src/billing/services/managed-signer/managed-signer.service.spec.ts(6 hunks)apps/api/src/billing/services/managed-signer/managed-signer.service.ts(4 hunks)apps/api/src/billing/services/stripe/stripe.service.ts(6 hunks)apps/api/src/billing/services/wallet-balance-reload-check/README.md(1 hunks)apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts(1 hunks)apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts(2 hunks)apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.ts(1 hunks)apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts(11 hunks)apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts(6 hunks)apps/api/src/core/services/job-queue/job-queue.service.spec.ts(9 hunks)apps/api/src/core/services/job-queue/job-queue.service.ts(4 hunks)apps/api/src/dashboard/services/stats/stats.service.ts(1 hunks)apps/api/src/deployment/repositories/deployment-setting/deployment-setting.repository.ts(2 hunks)apps/api/src/deployment/services/draining-deployment/draining-deployment.service.spec.ts(7 hunks)apps/api/src/deployment/services/draining-deployment/draining-deployment.service.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
- apps/api/drizzle/meta/0023_snapshot.json
- apps/api/drizzle/0023_clumsy_vertigo.sql
- apps/api/src/deployment/services/draining-deployment/draining-deployment.service.spec.ts
- apps/api/src/billing/model-schemas/wallet-setting/wallet-setting.schema.ts
- apps/api/drizzle/meta/_journal.json
- apps/api/src/dashboard/services/stats/stats.service.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (.cursor/rules/general.mdc)
**/*.{ts,tsx,js}: Never use typeanyor cast to typeany. 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/managed-signer/managed-signer.service.tsapps/api/src/deployment/repositories/deployment-setting/deployment-setting.repository.tsapps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.tsapps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.tsapps/api/src/billing/controllers/stripe/stripe.controller.tsapps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.tsapps/api/src/deployment/services/draining-deployment/draining-deployment.service.tsapps/api/src/core/services/job-queue/job-queue.service.tsapps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.tsapps/api/src/billing/services/balances/balances.service.tsapps/api/src/auth/services/ability/ability.service.tsapps/api/src/core/services/job-queue/job-queue.service.spec.tsapps/api/src/billing/services/managed-signer/managed-signer.service.spec.tsapps/api/src/billing/services/stripe/stripe.service.tsapps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.tsapps/api/src/billing/services/wallet-settings/wallet-settings.service.ts
**/*.spec.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/no-jest-mock.mdc)
Don't use
jest.mock()in test files. Instead, usejest-mock-extendedto create mocks and pass mocks as dependencies to the service under testUse
setupfunction instead ofbeforeEachin test files. Thesetupfunction must be at the bottom of the rootdescribeblock, 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>.namein 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/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.tsapps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.tsapps/api/src/core/services/job-queue/job-queue.service.spec.tsapps/api/src/billing/services/managed-signer/managed-signer.service.spec.ts
🧠 Learnings (14)
📚 Learning: 2025-09-04T04:27:50.638Z
Learnt from: stalniy
Repo: akash-network/console PR: 1868
File: apps/api/src/billing/services/managed-signer/managed-signer.service.ts:1-1
Timestamp: 2025-09-04T04:27:50.638Z
Learning: In the akash-network/console project, importing MsgCreateLease from "akashnetwork/akash-api/v1beta3" instead of v1beta4 is considered non-critical by the maintainers, likely due to backward compatibility or project-specific requirements.
Applied to files:
apps/api/src/billing/services/managed-signer/managed-signer.service.tsapps/api/src/core/services/job-queue/job-queue.service.spec.tsapps/api/src/billing/services/managed-signer/managed-signer.service.spec.ts
📚 Learning: 2025-09-04T04:27:40.858Z
Learnt from: stalniy
Repo: akash-network/console PR: 1868
File: apps/api/src/billing/services/managed-signer/managed-signer.service.ts:98-107
Timestamp: 2025-09-04T04:27:40.858Z
Learning: In the Akash protocol, when processing MsgCreateLease messages, the bidId.dseq field is always required and guaranteed to be present at runtime, even though TypeScript types may suggest it could be undefined. The non-null assertion operator (!) is appropriate to use in this context.
Applied to files:
apps/api/src/billing/services/managed-signer/managed-signer.service.tsapps/api/src/billing/services/managed-signer/managed-signer.service.spec.ts
📚 Learning: 2025-10-15T16:39:55.348Z
Learnt from: jzsfkzm
Repo: akash-network/console PR: 2039
File: apps/deploy-web/tests/ui/change-wallets.spec.ts:4-10
Timestamp: 2025-10-15T16:39:55.348Z
Learning: In the Akash Console E2E tests using the context-with-extension fixture, the first wallet is automatically created during fixture setup via `importWalletToLeap` in `apps/deploy-web/tests/ui/fixture/wallet-setup.ts`, so tests that call `frontPage.createWallet()` are creating a second wallet to test wallet switching functionality.
Applied to files:
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.tsapps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.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/controllers/stripe/stripe.controller.tsapps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.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.tsapps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.tsapps/api/src/billing/services/stripe/stripe.service.ts
📚 Learning: 2025-10-31T11:26:42.138Z
Learnt from: stalniy
Repo: akash-network/console PR: 2138
File: apps/api/src/billing/lib/batch-signing-client/batch-signing-client.service.ts:379-382
Timestamp: 2025-10-31T11:26:42.138Z
Learning: In TypeScript/JavaScript, the pattern of checking a cached value and then performing an async operation to fetch it without proper synchronization is race condition unsafe:
```typescript
private async getAddress() {
if (!this.address) {
this.address = await this.wallet.getFirstAddress();
}
return this.address;
}
```
Multiple concurrent calls can all pass the `if (!this.address)` check before any of them sets the value, leading to duplicate async operations. This should be flagged as a race condition. Proper synchronization (mutex, atomic promise caching, or guaranteed single-threaded execution) is required.
Applied to files:
apps/api/src/billing/controllers/stripe/stripe.controller.tsapps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.tsapps/api/src/billing/services/stripe/stripe.service.ts
📚 Learning: 2025-09-04T04:21:26.067Z
Learnt from: stalniy
Repo: akash-network/console PR: 1868
File: apps/api/src/app/services/trial-deployment-lease-created/trial-deployment-lease-created.handler.ts:73-76
Timestamp: 2025-09-04T04:21:26.067Z
Learning: In apps/api job scheduling with JobQueueService, use .toISOString() for startAfter values instead of Date objects to ensure UTC timezone consistency when dates are persisted to the database. Date objects can be ambiguous about timezone during serialization, while ISO strings with .toISOString() explicitly represent UTC.
Applied to files:
apps/api/src/core/services/job-queue/job-queue.service.tsapps/api/src/core/services/job-queue/job-queue.service.spec.ts
📚 Learning: 2025-11-25T17:45:44.790Z
Learnt from: CR
Repo: akash-network/console PR: 0
File: .cursor/rules/no-jest-mock.mdc:0-0
Timestamp: 2025-11-25T17:45:44.790Z
Learning: Applies to **/*.spec.{ts,tsx} : 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
Applied to files:
apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.tsapps/api/src/core/services/job-queue/job-queue.service.spec.ts
📚 Learning: 2025-11-25T17:45:39.561Z
Learnt from: CR
Repo: akash-network/console PR: 0
File: .cursor/rules/general.mdc:0-0
Timestamp: 2025-11-25T17:45:39.561Z
Learning: Applies to **/*.{ts,tsx,js} : Never use type `any` or cast to type `any`. Always define the proper TypeScript types.
Applied to files:
apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
📚 Learning: 2025-11-25T17:45:52.965Z
Learnt from: CR
Repo: akash-network/console PR: 0
File: .cursor/rules/setup-instead-of-before-each.mdc:0-0
Timestamp: 2025-11-25T17:45:52.965Z
Learning: Applies to **/*.spec.{ts,tsx} : 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.
Applied to files:
apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
📚 Learning: 2025-11-19T15:15:07.283Z
Learnt from: ygrishajev
Repo: akash-network/console PR: 2254
File: apps/api/test/functional/sign-and-broadcast-tx.spec.ts:4-4
Timestamp: 2025-11-19T15:15:07.283Z
Learning: In the Akash Network Console project, when tests use native Node.js fetch (available in Node 18+), fetch-mock should be used for HTTP mocking instead of nock, as nock does not support intercepting native fetch calls. This applies to apps/api/test/functional/sign-and-broadcast-tx.spec.ts and any other tests using native fetch.
Applied to files:
apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
📚 Learning: 2025-11-25T17:45:49.180Z
Learnt from: CR
Repo: akash-network/console PR: 0
File: .cursor/rules/query-by-in-tests.mdc:0-0
Timestamp: 2025-11-25T17:45:49.180Z
Learning: Applies to {apps/deploy-web,apps/provider-console}/**/*.spec.tsx : Use `queryBy` methods instead of `getBy` methods in test expectations
Applied to files:
apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
📚 Learning: 2025-09-04T04:21:26.067Z
Learnt from: stalniy
Repo: akash-network/console PR: 1868
File: apps/api/src/app/services/trial-deployment-lease-created/trial-deployment-lease-created.handler.ts:73-76
Timestamp: 2025-09-04T04:21:26.067Z
Learning: The apps/api codebase consistently uses .toISOString() for startAfter values when scheduling jobs with JobQueueService to ensure UTC timezone consistency. This pattern is used throughout trial-started.handler.ts, trial-deployment-lease-created.handler.ts and other job scheduling code. Do not suggest changing to Date objects as ISO strings provide explicit UTC representation for database storage.
Applied to files:
apps/api/src/core/services/job-queue/job-queue.service.spec.ts
📚 Learning: 2025-07-24T17:00:52.361Z
Learnt from: baktun14
Repo: akash-network/console PR: 1725
File: apps/api/src/utils/constants.ts:5-5
Timestamp: 2025-07-24T17:00:52.361Z
Learning: In the Akash Network Console project, when cross-cutting concerns or broader refactoring issues are identified during PR review, the preferred approach is to create a separate GitHub issue to track the work rather than expanding the scope of the current PR. This maintains focus and allows for proper planning of architectural improvements.
Applied to files:
apps/api/src/core/services/job-queue/job-queue.service.spec.ts
🧬 Code graph analysis (7)
apps/api/src/deployment/repositories/deployment-setting/deployment-setting.repository.ts (1)
apps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.ts (1)
UserWallets(5-18)
apps/api/src/billing/controllers/stripe/stripe.controller.ts (4)
apps/api/src/auth/services/auth.service.ts (5)
Protected(62-78)currentUser(13-15)currentUser(17-24)ability(44-46)ability(48-50)apps/api/src/billing/http-schemas/stripe.schema.ts (5)
PaymentMethodMarkAsDefaultInput(240-240)PaymentMethodResponse(243-243)PaymentMethodsResponse(242-242)ConfirmPaymentRequest(244-244)ConfirmPaymentResponse(247-247)apps/api/src/core/lib/semaphore.decorator.ts (1)
Semaphore(3-28)packages/http-sdk/src/stripe/stripe.types.ts (1)
ConfirmPaymentResponse(104-109)
apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts (5)
apps/api/src/core/services/job-queue/job-queue.service.ts (1)
setup(185-192)apps/api/test/seeders/user-wallet.seeder.ts (1)
UserWalletSeeder(6-30)apps/api/test/seeders/payment-method.seeder.ts (1)
generatePaymentMethod(11-56)apps/api/src/billing/services/stripe/stripe.service.ts (1)
PaymentMethod(33-33)apps/api/src/billing/http-schemas/stripe.schema.ts (1)
PaymentMethod(241-241)
apps/api/src/billing/services/balances/balances.service.ts (3)
apps/api/src/caching/helpers.ts (1)
Memoize(21-37)apps/api/src/utils/constants.ts (1)
averageBlockTime(4-4)apps/api/src/billing/http-schemas/balance.schema.ts (1)
GetBalancesResponseOutput(17-17)
apps/api/src/core/services/job-queue/job-queue.service.spec.ts (1)
apps/api/src/core/services/job-queue/job-queue.service.ts (2)
setup(185-192)Job(199-207)
apps/api/src/billing/services/managed-signer/managed-signer.service.spec.ts (5)
apps/api/test/seeders/user-wallet.seeder.ts (1)
UserWalletSeeder(6-30)apps/api/test/seeders/user.seeder.ts (1)
UserSeeder(5-45)apps/stats-web/src/components/transactions/akash/MsgCreateDeployment.tsx (1)
MsgCreateDeployment(12-29)apps/stats-web/src/components/transactions/akash/MsgAccountDeposit.tsx (1)
MsgAccountDeposit(10-19)apps/stats-web/src/components/transactions/akash/MsgCreateLease.tsx (1)
MsgCreateLease(10-22)
apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts (2)
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (1)
userId(92-135)apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts (2)
userId(58-75)userId(77-105)
🪛 LanguageTool
apps/api/src/billing/services/wallet-balance-reload-check/README.md
[style] ~39-~39: ‘emergency situations’ might be wordy. Consider a shorter alternative.
Context: ... margin before funds run out - Prevents emergency situations ## When Does the Check Run? The handl...
(EN_WORDINESS_PREMIUM_EMERGENCY_SITUATIONS)
🪛 markdownlint-cli2 (0.18.1)
apps/api/src/billing/services/wallet-balance-reload-check/README.md
53-53: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
104-104: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
150-150: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
⏰ 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: test-build
- GitHub Check: test-build
- GitHub Check: validate / validate-app
- GitHub Check: validate / validate-app
- GitHub Check: test-build
- GitHub Check: Validate local packages
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (34)
apps/api/src/deployment/services/draining-deployment/draining-deployment.service.ts (1)
124-130: The implementation is correct; no logic change needed.The filtering condition on line 125 properly identifies deployments that would close before the target date, and
blocksNeeded = targetHeight - currentHeightcorrectly calculates the cost to extend all such deployments through the target date. Test cases explicitly validate this behavior.apps/api/src/billing/services/balances/balances.service.ts (1)
9-21: Fiat balance helpers andStatsServiceintegration look consistent.The new
getFullBalanceInFiat/toFiatAmount/#convertToFiatAmount/ensure2floatingDigitsflow cleanly layers fiat conversion on top of existing u-denom balances, and the coin selection viaDEPLOYMENT_GRANT_DENOM("uakt"→"akash-network", otherwise"usd-coin") is coherent. Memoization withaverageBlockTimematchesgetFullBalanceand keeps concerns nicely localized inBalancesService.Also applies to: 96-134
apps/api/src/billing/services/managed-signer/managed-signer.service.ts (1)
1-2: Spending detection and immediate reload scheduling are wired correctly.Decoding messages once, detecting spending via
.endsWith(MsgCreateDeployment.$type | MsgAccountDeposit.$type), and then delegating towalletReloadJobService.scheduleImmediate(userId)keeps the side effect tightly scoped to actual spend-related txs and reuses the centralized scheduling logic. Constructor wiring and imports look consistent with existing patterns.Also applies to: 16-16, 40-42, 69-79
apps/api/src/core/services/job-queue/job-queue.service.ts (1)
108-116: Job completion API andJobMetapropagation through workers are consistent.The new
complete(name, id)helper andJOB_COMPLETEDlog mirrorcancelnicely, and passing{ id: job.id }intohandler.handle(job.data, { id: job.id })lines up with theJobMetatype. Making the second parameter optional in theJobHandlersignature keeps backward compatibility while still providing job context where needed. Worker wiring and logging (JOB_STARTED/JOB_DONE/JOB_FAILED) remain coherent after the signature change.Also applies to: 155-176, 218-225
apps/api/src/core/services/job-queue/job-queue.service.spec.ts (1)
19-25: Tests correctly cover new queue behavior and handler signature.The specs now verify:
- Queue options including
retryLimit/retryDelayMax.- Rejection when multiple handlers target the same queue.
enqueuereturning ajobIdand calling the handler with(payload, { id })from workers.- Execution context wiring via
runWithContext.All of that matches the updated
JobQueueServiceimplementation.Also applies to: 51-65, 68-77, 152-181, 189-193, 210-228, 285-303
apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts (1)
38-46: LGTM!The updated
findByUserIdmethod correctly handles the case where no wallet setting is found by returningundefined, and properly transforms the result usingtoOutput.apps/api/src/billing/controllers/stripe/stripe.controller.ts (3)
50-56: LGTM!The
markAsDefaultmethod correctly uses the@Protecteddecorator with appropriate action/subject, retrieves the paying user, and delegates to the service layer.
58-67: LGTM!Good implementation that addresses the previous review feedback - the method now properly asserts with a 404 when the payment method is not found.
69-79: LGTM!The method correctly handles the case where no paying user exists (strict: false) by returning an empty array, providing a safe fallback.
apps/api/src/billing/services/managed-signer/managed-signer.service.spec.ts (3)
1-1: LGTM!Correctly imports
MsgAccountDepositto support the new test cases for deposit message handling.
492-578: LGTM! Good test coverage for the new executeDerivedEncodedTxByUserId method.The tests properly cover:
- Scheduling triggered for
MsgCreateDeployment- Scheduling triggered for
MsgAccountDeposit- No scheduling for non-spending messages (
MsgCreateLease)Test naming follows the coding guidelines (present simple, 3rd person singular).
630-656: LGTM!The setup function correctly wires in the new
WalletReloadJobServicemock andregistryMock, and the constructor invocation matches the expected dependency order.apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts (4)
8-16: LGTM!The new imports correctly bring in the required types and seeders for the updated test dependencies.
67-103: LGTM!The test correctly verifies that
scheduleForWalletSettingis called with the expected wallet setting object containingidanduserIdwhen creating a new setting with auto-reload enabled.
205-232: LGTM!The test properly verifies that
walletReloadJobService.cancelis called with the correctuserIdandexistingJobIdwhen auto-reload is disabled.
276-319: LGTM!The setup function follows all coding guidelines:
- Located at bottom of root describe block
- Creates object under test and returns it
- Accepts single parameter with inline type definition
- Avoids shared state
- Uses
jest-mock-extendedfor mocksThe new dependencies (
userRepository,stripeService,walletReloadJobService) are correctly wired.apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (5)
1-15: LGTM!Clean imports. The
Requireutility type is now correctly imported from@src/core/types/require.typeas suggested in previous review.
17-30: LGTM!The type definitions are well-structured.
ValidationErrornow has a consistent shape (event + message only), and the resource types properly constrain the required fields.
61-70: LGTM!The
handlemethod follows a clean Result-based pattern:
- Collects resources with validation
- On success: attempts reload and schedules next check
- On failure: logs validation error and exits gracefully
165-209: LGTM! Solid reload logic with proper idempotency.The implementation correctly:
- Calculates future costs and determines if reload is needed
- Uses
Math.maxto ensure minimum $20 reload- Applies idempotency key to prevent duplicate charges on retries
- Logs outcomes (skipped/reloaded/failed)
- Re-throws errors to trigger job retry
211-225: LGTM! Scheduling errors are now properly propagated.The method addresses the previous review feedback by re-throwing scheduling errors, allowing pgboss to retry the job when scheduling the next check fails.
apps/api/src/billing/services/stripe/stripe.service.ts (6)
1-1: LGTM - new imports properly support the added functionality.The new imports (
AnyAbility,WithTransaction,PayingUser) are correctly used in the new method signatures and decorators.Also applies to: 13-13, 17-17
33-33: LGTM - clean type definition.The exported
PaymentMethodtype properly extends Stripe's payment method with the validated flag.
146-173: Transaction boundary acknowledged - timeout partially mitigates concern.The external Stripe API calls now include a 3-second timeout (lines 150, 181), which addresses the connection pool concern raised in previous reviews. The Stripe update is sequenced after local DB operations rather than concurrent, reducing (but not eliminating) inconsistency risk.
Based on learnings, organization field updates to Stripe are considered non-critical and can proceed with this pattern. The timeout helps prevent long-running transactions from exhausting the connection pool.
Note: If Stripe is slow or fails after the timeout, the entire transaction will roll back. Consider monitoring for timeout-related rollbacks to assess if compensating transactions or eventual consistency patterns are needed in the future.
175-183: LGTM - timeout appropriately limits blocking.The timeout on the Stripe customer update prevents long-running transactions. Errors correctly propagate to trigger transaction rollback.
185-238: LGTM - idempotency key handling is correct.The conditional inclusion of
idempotencyKeyand the clearer variable naming (amountInCents) improve the code quality. The idempotency pattern correctly prevents duplicate payment intents.
106-106: LGTM - using the exported type improves consistency.The return type now uses the exported
PaymentMethodtype alias defined on line 33.apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts (7)
6-10: LGTM - new imports support enhanced validation and scheduling.The new dependencies enable Stripe payment validation and wallet reload job scheduling functionality.
12-16: LGTM - clean input type definition.The
WalletSettingInputinterface makes optional fields explicit and serves as a clear contract for the upsert operation.
20-27: LGTM - new dependencies properly support validation and scheduling.The constructor additions enable payment method validation via Stripe and job scheduling via the wallet reload service.
77-105: LGTM - good race condition handling with retry.The unique violation catch and retry with
#updateproperly handles concurrent creation attempts. The assertion on line 96 appropriately escalates if the retry also fails.
133-141: LGTM - scheduling logic correctly handles transitions.The method properly:
- Schedules a job when transitioning from disabled to enabled (line 134-136)
- Cancels a job when transitioning to disabled (line 138-140)
Both conditions prevent redundant scheduling/cancellation operations.
43-56: Transaction boundary includes external API calls.The
@WithTransactiondecorator wraps validation logic that callsstripeService.getDefaultPaymentMethod()(invoked via#validateon lines 67, 78). This external Stripe API call can hold database connections while waiting for the remote response, similar to the concern raised instripe.service.ts.Consider the same mitigation strategy used in
stripe.service.ts: add a timeout to the Stripe call to prevent long-running transactions from exhausting the connection pool.If Stripe calls become a bottleneck, consider refactoring to:
- Validate payment method availability outside the transaction
- Run the transaction only for database mutations
- Unschedule/reschedule jobs in a finally block or separate operation
For now, adding a timeout to the
getDefaultPaymentMethodcall in the validation would mitigate the immediate concern.⛔ Skipped due to learnings
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.
107-131: Type narrowing is correct and safe.
PayingUseris defined asRequire<UserOutput, "stripeCustomerId">, which makesstripeCustomerIdrequired. The assertion on lines 121–122 guaranteesstripeCustomerIdexists at runtime. The spread{ ...user, stripeCustomerId }on line 126 properly narrows the type to satisfyPayingUserrequirements without additional properties.
apps/api/src/deployment/repositories/deployment-setting/deployment-setting.repository.ts
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
apps/api/src/billing/services/stripe/stripe.service.ts (1)
122-137: Strengthen the type guard for unexpanded payment methods.The type check on line 134 doesn't guard against
remotebeing a string ID. If Stripe's expand operation fails or is not properly configured,default_payment_methodcould be a string rather than an expanded object, which would cause runtime errors when attempting to spread it on line 135.Apply this diff to add a proper type guard:
- if (typeof remote === "object" && remote && local) { + if (remote && typeof remote === "object" && "type" in remote && local) { return { ...remote, validated: local.isValidated }; }This ensures
remoteis an expandedPaymentMethodobject with expected properties before attempting to use it.
🧹 Nitpick comments (2)
apps/api/src/billing/services/balances/balances.service.ts (2)
96-99: Memoized full-balance wrapper – behaviorally fine, small design consideration
getFullBalanceMemoizedsimply delegates togetFullBalanceand is memoized withttlInSeconds: averageBlockTime. That’s a clean way to opt into caching at call sites while keeping the original method available.If you anticipate that all external callers should always see the memoized behavior, consider (optionally) making
getFullBalanceinternal/private and exposing only the memoized variant to avoid accidental use of the non‑cached path in the future.
116-142: Fiat conversion helpers are consistent; be aware of rounding and decimal assumptionsThe fiat helpers (
getFullBalanceInFiat*,toFiatAmount,#convertToFiatAmount,ensure2floatingDigits) are coherent and type‑safe, but there are a couple of implicit assumptions worth calling out:
toFiatAmountdivides by1_000_000, assuming the underlying token (bothuaktand the non‑uaktcase) uses 6 decimal places. That matches typicalu*/ USDC behavior, but ifDEPLOYMENT_GRANT_DENOMever points to a token with different decimals, this will silently mis‑scale values.- Each component (
balance,deployments) is rounded to 2 decimals, and thentotalis computed asensure2floatingDigits(balance + deployments). This can differ slightly from “round once at the very end” semantics due to double rounding. That’s usually fine for display, but if you need strict cent‑level consistency with some external system, you may want to round only once at the end.Not blockers, but documenting these assumptions (or parameterizing decimals/coin mapping via config) would make the behavior more future‑proof.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
apps/api/src/billing/controllers/wallet/wallet.controller.ts(1 hunks)apps/api/src/billing/services/balances/balances.service.ts(4 hunks)apps/api/src/billing/services/stripe/stripe.service.ts(6 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (.cursor/rules/general.mdc)
**/*.{ts,tsx,js}: Never use typeanyor cast to typeany. 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/balances/balances.service.tsapps/api/src/billing/controllers/wallet/wallet.controller.tsapps/api/src/billing/services/stripe/stripe.service.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/services/stripe/stripe.service.ts
📚 Learning: 2025-10-31T11:26:42.138Z
Learnt from: stalniy
Repo: akash-network/console PR: 2138
File: apps/api/src/billing/lib/batch-signing-client/batch-signing-client.service.ts:379-382
Timestamp: 2025-10-31T11:26:42.138Z
Learning: In TypeScript/JavaScript, the pattern of checking a cached value and then performing an async operation to fetch it without proper synchronization is race condition unsafe:
```typescript
private async getAddress() {
if (!this.address) {
this.address = await this.wallet.getFirstAddress();
}
return this.address;
}
```
Multiple concurrent calls can all pass the `if (!this.address)` check before any of them sets the value, leading to duplicate async operations. This should be flagged as a race condition. Proper synchronization (mutex, atomic promise caching, or guaranteed single-threaded execution) is required.
Applied to files:
apps/api/src/billing/services/stripe/stripe.service.ts
🧬 Code graph analysis (2)
apps/api/src/billing/services/balances/balances.service.ts (3)
apps/api/src/billing/http-schemas/balance.schema.ts (1)
GetBalancesResponseOutput(17-17)apps/api/src/caching/helpers.ts (1)
Memoize(21-37)apps/api/src/utils/constants.ts (1)
averageBlockTime(4-4)
apps/api/src/billing/services/stripe/stripe.service.ts (5)
apps/api/src/billing/http-schemas/stripe.schema.ts (2)
PaymentMethod(241-241)PaymentIntentResult(245-245)packages/http-sdk/src/stripe/stripe.types.ts (1)
PaymentMethod(30-41)apps/api/src/billing/services/paying-user/paying-user.ts (1)
PayingUser(6-6)apps/api/src/user/repositories/user/user.repository.ts (1)
UserOutput(10-12)apps/api/src/core/services/tx/tx.service.ts (1)
WithTransaction(39-51)
⏰ 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: test-build
- 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 (7)
apps/api/src/billing/controllers/wallet/wallet.controller.ts (1)
98-110: Memoized balance retrieval – confirm staleness is acceptable
getBalancesnow usesgetFullBalanceMemoized, introducing a cache layer (TTL =averageBlockTime, currently 6 seconds) keyed byaddressandisOldWallet. Response shape and error behavior stay the same, but balances can now be briefly stale. Please confirm this staleness window is acceptable for callers (especially around recent refills/drains or auto‑reload flows).apps/api/src/billing/services/balances/balances.service.ts (1)
9-21: StatsService injection wiring looks correctImporting
StatsServiceand injecting it as aprivate readonlydependency is consistent with existing patterns in this service and keeps the constructor order stable. No issues from a DI or typing perspective.apps/api/src/billing/services/stripe/stripe.service.ts (5)
1-1: LGTM - Clean type extension and imports.The new imports and
PaymentMethodtype definition are well-structured. The type extends Stripe's base type with avalidatedfield, providing a clean API surface for payment method handling.Also applies to: 13-13, 17-17, 32-33
106-106: LGTM - Improved type consistency.Using the
PaymentMethodtype improves maintainability and consistency across the codebase.
139-152: LGTM - Proper error handling for non-existent payment methods.The try-catch properly handles the case where a payment method doesn't exist, returning
falseinstead of throwing an exception. This aligns with the boolean return type and provides better API ergonomics.
183-191: LGTM - Proper timeout handling for external API call.The 3-second timeout helps prevent long-held database connections during Stripe API calls.
193-223: LGTM - Clean idempotency key implementation.The conditional inclusion of the idempotency key is correctly implemented, and the renaming to
amountInCentsimproves code clarity. This follows Stripe SDK patterns properly.
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
apps/api/src/billing/services/stripe/stripe.service.ts (1)
161-188: Handle non-card payment methods properly.Lines 175-177 assume all payment methods have a
card.fingerprint, but this method may be called with non-card payment methods such as "link" type (enabled increateSetupIntentat line 57). Non-card payment methods don't have thecardproperty, causing the assertion at line 177 to fail inappropriately.Apply this diff to verify the payment method type:
const fingerprint = remote.card?.fingerprint; - assert(fingerprint, 403, "Payment method fingerprint is missing"); + assert(remote.type === "card" && fingerprint, 403, "Only card payment methods can be set as default at this time");Alternatively, if you want to support non-card payment methods, implement appropriate unique identifier extraction for each type:
- const fingerprint = remote.card?.fingerprint; - - assert(fingerprint, 403, "Payment method fingerprint is missing"); + let uniqueIdentifier: string; + + if (remote.type === "card") { + uniqueIdentifier = remote.card?.fingerprint; + assert(uniqueIdentifier, 403, "Card fingerprint is missing"); + } else { + // Handle other payment method types or reject them + assert(false, 403, `Payment method type '${remote.type}' is not supported for default payment method`); + } const newLocal = await this.paymentMethodRepository.accessibleBy(ability, "create").createAsDefault({ userId: user.id, - fingerprint, + fingerprint: uniqueIdentifier, paymentMethodId });Based on past review comments that flagged this same issue.
🧹 Nitpick comments (1)
apps/api/src/billing/services/stripe/stripe.service.ts (1)
124-144: Consider more defensive type checking for the expanded payment method.Line 136 checks
typeof remote === "object"which prevents string IDs but doesn't verify the object has expectedPaymentMethodproperties. If Stripe's expansion fails unexpectedly, this could lead to runtime errors when accessing properties.Apply this diff to add property verification:
- if (typeof remote === "object" && remote && local) { + if (typeof remote === "object" && remote && "type" in remote && local) { return { ...remote, validated: local.isValidated };This ensures
remoteis an expanded PaymentMethod object, not just any object.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
apps/api/src/billing/services/stripe/stripe.service.spec.ts(2 hunks)apps/api/src/billing/services/stripe/stripe.service.ts(7 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (.cursor/rules/general.mdc)
**/*.{ts,tsx,js}: Never use typeanyor cast to typeany. 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.tsapps/api/src/billing/services/stripe/stripe.service.ts
**/*.spec.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/no-jest-mock.mdc)
Don't use
jest.mock()in test files. Instead, usejest-mock-extendedto create mocks and pass mocks as dependencies to the service under testUse
setupfunction instead ofbeforeEachin test files. Thesetupfunction must be at the bottom of the rootdescribeblock, 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>.namein 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/services/stripe/stripe.service.ts
🧬 Code graph analysis (2)
apps/api/src/billing/services/stripe/stripe.service.spec.ts (2)
packages/http-sdk/src/stripe/stripe.service.ts (1)
StripeService(18-94)packages/logging/src/services/logger/logger.service.ts (1)
LoggerService(39-165)
apps/api/src/billing/services/stripe/stripe.service.ts (5)
packages/http-sdk/src/stripe/stripe.types.ts (1)
PaymentMethod(30-41)apps/api/src/billing/services/paying-user/paying-user.ts (1)
PayingUser(6-6)apps/api/src/auth/services/auth.service.ts (2)
ability(44-46)ability(48-50)apps/api/src/user/repositories/user/user.repository.ts (1)
UserOutput(10-12)apps/api/src/core/services/tx/tx.service.ts (1)
WithTransaction(39-51)
⏰ 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: test-build
- 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 (6)
apps/api/src/billing/services/stripe/stripe.service.ts (5)
1-33: LGTM!The new imports and
PaymentMethodtype definition are well-structured. Exporting the type allows consumers to benefit from proper type inference when working with validated payment methods.
39-51: LGTM!The constructor properly injects the new dependencies and sets the logger context. The dependency injection pattern is followed correctly.
146-159: LGTM!The method properly handles non-existent payment methods by catching Stripe's
resource_missingerror and returningfalse, while re-throwing unexpected errors. This addresses the previous review feedback effectively.
190-253: LGTM!The
markRemotePaymentMethodAsDefaultmethod properly updates the Stripe customer with a sensible timeout. ThecreatePaymentIntentrefactoring cleanly handles the optionalidempotencyKeyparameter, and the renamedamountInCentsvariable improves code clarity.
408-410: LGTM!The simplified return statement has no functional impact and slightly reduces verbosity.
apps/api/src/billing/services/stripe/stripe.service.spec.ts (1)
1-1422: LGTM!The test file properly injects the mock
LoggerServiceinto theStripeServiceconstructor, aligning with the service's updated signature. All tests continue to exercise the service correctly with the new dependency.
Implement daily wallet balance checks that automatically reload funds when balance falls below 25% of 7-day deployment costs for auto top-up enabled deployments. Includes immediate triggers on feature enable, deployment creation, and deposits. Uses new WalletReloadJobService to manage job scheduling and break dependency cycles. refs #1779
fd3b6e4 to
de560e9
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/api/src/deployment/repositories/deployment-setting/deployment-setting.repository.ts (1)
51-68: Joins onUserWalletsare not applied – Drizzle query builder must be reassignedDrizzle's query builder is immutable by design; methods like
innerJoin()andleftJoin()return a new builder rather than mutating in place. Calling these methods onquerywithout reassigning means the joins are never actually added to the final query. TheWHEREclauses still referenceUserWallets.*columns, which will cause a runtime SQL error.Reassign the query on each join call:
- const query = this.pg + let query = this.pg .select({ id: this.table.id, dseq: this.table.dseq, walletId: UserWallets.id, address: UserWallets.address, isOldWallet: UserWallets.isOldWallet }) .from(this.table) - .leftJoin(Users, eq(this.table.userId, Users.id)); - - if (options.address) { - query.innerJoin(UserWallets, eq(Users.id, UserWallets.userId)); + .leftJoin(Users, eq(this.table.userId, Users.id)); + + if (options.address) { + query = query.innerJoin(UserWallets, eq(Users.id, UserWallets.userId)); clauses.push(eq(UserWallets.address, options.address)); } else { - query.leftJoin(UserWallets, eq(Users.id, UserWallets.userId)); + query = query.leftJoin(UserWallets, eq(Users.id, UserWallets.userId)); clauses.push(isNotNull(UserWallets.address)); }
♻️ Duplicate comments (4)
apps/api/src/core/services/job-queue/job-queue.service.ts (1)
89-94: Avoid logging full job payload to prevent leaking PII.The
JOB_ENQUEUEDevent currently logs the entirejobobject. For jobs like theNotificationJobexample in the docstring (withto,subject,body), this risks logging email addresses and message content atinfolevel.Recommend logging only non-sensitive metadata (name, id, version) instead of the full payload.
Apply this diff to log only non-sensitive metadata:
this.logger.info({ event: "JOB_ENQUEUED", - job, + jobName: job.name, jobId, - options + version: job.version });apps/api/src/billing/services/stripe-webhook/stripe-webhook.service.ts (1)
144-164: Good improvement on attach handler logging, but detach handler still logs fingerprint.The attach handler correctly removed
fingerprintfrom the logged payload (line 163), addressing the PII concern from the previous review. TheisDefaultflag is appropriate for logging.However, the detach handler (lines 173-178, 207-212) still logs
fingerprintatinfolevel, which is sensitive payment metadata.Consider removing or hashing the fingerprint in detach logs as well:
this.logger.info({ event: "PAYMENT_METHOD_DETACHED", paymentMethodId: paymentMethod.id, - customerId, - fingerprint + customerId });Also applies to: 173-178, 207-212
apps/api/src/billing/repositories/payment-method/payment-method.repository.ts (1)
70-77:createAsDefaultremains concurrently unsafe; avoidPromise.allhereRunning
this.create(...)and#unmarkAsDefaultExcluding(...)in parallel can still yield twoisDefault = truerecords whencreateAsDefaultis called concurrently for the same user: both transactions can execute their unmark step before either insert is visible, and each then inserts a new default.Given this is already known and was previously flagged as non-blocking, consider serializing the operations inside the transaction instead of using
Promise.all, and ensure both use the sametx:async createAsDefault(input: Omit<PaymentMethodInput, "id" | "isDefault">) { - return this.ensureTransaction(async tx => { - const id = uuidv4(); - const [output] = await Promise.all([ - this.create({ - ...input, - isDefault: true, - id - }), - this.#unmarkAsDefaultExcluding(id, tx) - ]); - - return this.toOutput(output); - }); + return this.ensureTransaction(async tx => { + const id = uuidv4(); + + await this.#unmarkAsDefaultExcluding(id, tx); + + const [output] = await tx + .insert(this.table) + .values({ + ...input, + isDefault: true, + id + }) + .returning(); + + return this.toOutput(output); + }); }That way, each transaction atomically “unmarks others then inserts the new default,” reducing the chance of overlapping defaults under load.
Also applies to: 79-118
apps/api/src/billing/services/stripe/stripe.service.ts (1)
124-188: Marking non‑card payment methods as default will fail with a 403In
markPaymentMethodAsDefault, the branch that creates a local record for a previously unseen method assumesremote.card?.fingerprintexists:const fingerprint = remote.card?.fingerprint; assert(fingerprint, 403, "Payment method fingerprint is missing");But
createSetupIntentallows"card"and"link"types. For non‑card methods (e.g.type === "link"),remote.cardisundefined, so this throws a 403 even though the payment method may be perfectly valid to use and set as default.If you only intend to support cards as defaults for now, make that explicit and update the message; otherwise you’ll need a type‑specific identifier strategy for non‑card methods.
For a minimal fix:
- const fingerprint = remote.card?.fingerprint; - - assert(fingerprint, 403, "Payment method fingerprint is missing"); + const isCard = remote.type === "card"; + const fingerprint = isCard ? remote.card?.fingerprint : undefined; + + assert(isCard && fingerprint, 403, "Only card payment methods can be set as default at this time");Optionally, also narrow the setup intent’s
payment_method_typesif you don’t plan to support non‑card defaults in the near term.Please verify behaviour by marking a non‑card (e.g.
"link") payment method as default in a dev environment and ensure the API either works as intended or fails with a clear, documented 4xx error that matches your product requirements.
🧹 Nitpick comments (10)
apps/api/src/billing/model-schemas/payment-method/payment-method.schema.ts (1)
19-27: Default payment-method flag and partial unique index look correct
isDefaultplus the partial unique index ensures at most one default payment method per user and keeps existing non-default rows unaffected. IncludingisDefaultin the indexed columns is slightly redundant given theWHERE is_default = truefilter but not harmful.apps/api/src/billing/services/managed-signer/managed-signer.service.spec.ts (1)
17-18: Wallet reload scheduling tests and setup wiring look solidThe new
executeDerivedEncodedTxByUserIdtests correctly exercise wallet reload scheduling for spending vs non‑spending messages, and the extendedsetupneatly wiresWalletReloadJobServiceand a typedRegistrymock viajest-mock-extended. Constructor arguments forManagedSignerServiceremain coherent, and the test style still aligns with the local testing guidelines.Also applies to: 492-578, 593-656
apps/api/src/deployment/services/draining-deployment/draining-deployment.service.spec.ts (1)
4-6: Comprehensive tests and setup for draining cost logic; be aware of coupling to current formulaThe refactored tests:
- Use typed mocks (
jest-mock-extended+mockConfigService) and an inlinesetupat the bottom, matching the project’s testing conventions.- Correctly update
paginateAutoTopUpDeploymentsmock signatures to{ address?: string; limit: number }.- Add focused scenarios for
calculateAllDeploymentCostUntilDatethat exercise missing wallets, missing addresses, empty deployments, and boundary conditions onpredictedClosedHeight.These tests tightly encode the current formula (
blocksNeeded = targetHeight - currentHeight). If you adjust the service to usepredictedClosedHeightin the cost calculation, remember to update the expectations incalculateAllDeploymentCostUntilDatetests accordingly.Also applies to: 15-16, 32-118, 129-193, 195-352, 355-382
apps/api/src/billing/services/balances/balances.service.ts (1)
9-10: StatsService fiat integration and memoized balance APIs look consistentThe new memoized getters and fiat helpers keep units coherent (uTokens → tokens → fiat) and reuse
averageBlockTimefor TTL, and theStatsServiceinjection cleanly encapsulates conversion logic via#convertToFiatAmount. Overall the additions integrate well with existing balance calculations.If you expect
convertToFiatAmountto be relatively expensive (e.g., external I/O), you might optionally parallelize the two conversions ingetFullBalanceInFiat:const [balance, deployments] = await Promise.all([ this.toFiatAmount(data.balance), this.toFiatAmount(data.deployments) ]); const total = this.ensure2floatingDigits(balance + deployments);Also applies to: 19-21, 96-143
apps/api/src/billing/services/wallet-balance-reload-check/README.md (1)
53-99: Add languages to fenced code blocks (markdownlint MD040) and optionally tighten wordingmarkdownlint is right that the three fenced blocks would benefit from explicit languages and will otherwise keep flagging MD040. For example:
-``` +```text User enables auto-reload ...-``` +```text balance < 0.25 * costUntilTargetDateInFiat-``` +```text reloadAmount = max(costUntilTargetDateInFiat - balance, $20)You might also optionally shorten “emergency situations” (e.g., to “emergencies”) if you want to follow the style suggestion, but that’s purely cosmetic.
Also applies to: 104-107, 150-155
apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts (1)
48-65: Consider adding explicit return type annotation.The
findInternalByUserIdWithRelationsmethod relies on type inference for the complex return type with relations. Adding an explicit return type would improve readability and catch schema mismatches at compile time.- async findInternalByUserIdWithRelations(userId: WalletSettingOutput["userId"]) { + async findInternalByUserIdWithRelations(userId: WalletSettingOutput["userId"]): Promise< + | (DbWalletSettingOutput & { + wallet: { address: string | null; isOldWallet: boolean }; + user: ApiPgTables["User"]["$inferSelect"]; + }) + | undefined + > {Alternatively, define a dedicated type for this composite return shape for reuse.
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts (1)
292-300: Simplify the user stripeCustomerId logic.The nested ternary condition is hard to follow. Consider simplifying:
- const userWithStripe = - input?.user && input.user.stripeCustomerId === null - ? user - : input?.user && input.user.stripeCustomerId - ? user - : user.stripeCustomerId - ? user - : { ...user, stripeCustomerId: faker.string.uuid() }; + const userWithStripe = user.stripeCustomerId + ? user + : { ...user, stripeCustomerId: faker.string.uuid() };The original logic appears to preserve
nullonly when explicitly passed viainput?.user, but the nested conditions are redundant sinceinput?.useris already assigned touseron line 292.apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts (1)
49-49: Replaceas anycast with proper typing.Per coding guidelines, avoid using
any. ThegenerateWalletSettingreturn type should matchWalletSettingOutputor the mock should be typed appropriately.- walletSettingRepository.updateById.mockResolvedValue(updatedSetting as any); + walletSettingRepository.updateById.mockResolvedValue(updatedSetting);If there's a type mismatch, consider updating
generateWalletSettingto return the correct type or use a type assertion toWalletSettingOutputinstead ofany. As per coding guidelines, never use typeany.apps/api/src/billing/routes/stripe-payment-methods/stripe-payment-methods.router.ts (1)
5-7: Default-payment-method routes and OpenAPI wiring look consistentImports, route definitions, security, and schemas line up with
StripeController.markAsDefault/getDefaultPaymentMethodand the zod schemas. One minor nit:markAsDefaultreturnsc.json(undefined, 200)while the OpenAPI response has no content schema; you may want to switch this to204withc.body(null, 204)for tighter alignment with the documented empty body, similar toremovePaymentMethod.Also applies to: 45-70, 72-99
apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts (1)
43-56: Upsert flow and scheduling are well structured; consider moving Stripe validation out of the transactionThe refactor to
upsertWalletSettingwith#update,#create,#validate, and#arrangeSchedulenicely separates concerns:
- Validation enforces that threshold and amount are present when enabling auto‑reload and that the user has a Stripe customer and a default payment method.
#arrangeSchedulecleanly encapsulates scheduling/cancellation viaWalletReloadJobService, anddeleteWalletSettingnow consistently cancels any existing job.One thing to watch:
#validatecallsstripeService.getDefaultPaymentMethodfrom within a@WithTransaction()method. That means a network call to Stripe runs while a DB transaction is open, which can hold a connection longer than necessary if Stripe is slow.If feasible, consider performing the “has default payment method” check before opening the transaction (or add a short timeout to
getDefaultPaymentMethod) so the critical DB section only covers the actual create/update + scheduling work.Please confirm, based on your TxService implementation and DB connection limits, that running the Stripe lookup inside
upsertWalletSetting’s transaction won’t risk exhausting connections under load; if it does, it’s worth refactoring the validation step to occur before the transaction starts.Also applies to: 58-80, 92-131, 133-152
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
apps/api/test/functional/__snapshots__/docs.spec.ts.snapis excluded by!**/*.snap
📒 Files selected for processing (46)
apps/api/drizzle.config.ts(1 hunks)apps/api/drizzle/0023_clumsy_vertigo.sql(1 hunks)apps/api/drizzle/meta/0023_snapshot.json(1 hunks)apps/api/drizzle/meta/_journal.json(1 hunks)apps/api/src/auth/services/ability/ability.service.ts(2 hunks)apps/api/src/auth/services/auth.service.ts(2 hunks)apps/api/src/billing/controllers/stripe/stripe.controller.ts(2 hunks)apps/api/src/billing/controllers/wallet/wallet.controller.ts(1 hunks)apps/api/src/billing/http-schemas/stripe.schema.ts(3 hunks)apps/api/src/billing/http-schemas/wallet.schema.ts(0 hunks)apps/api/src/billing/model-schemas/payment-method/payment-method.schema.ts(1 hunks)apps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.ts(1 hunks)apps/api/src/billing/model-schemas/wallet-setting/wallet-setting.schema.ts(2 hunks)apps/api/src/billing/repositories/payment-method/payment-method.repository.ts(2 hunks)apps/api/src/billing/repositories/user-wallet/user-wallet.repository.ts(2 hunks)apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts(1 hunks)apps/api/src/billing/routes/stripe-payment-methods/stripe-payment-methods.router.ts(5 hunks)apps/api/src/billing/routes/stripe-webhook/stripe-webhook.router.ts(1 hunks)apps/api/src/billing/services/balances/balances.service.ts(4 hunks)apps/api/src/billing/services/managed-signer/managed-signer.service.spec.ts(6 hunks)apps/api/src/billing/services/managed-signer/managed-signer.service.ts(4 hunks)apps/api/src/billing/services/paying-user/paying-user.ts(1 hunks)apps/api/src/billing/services/refill/refill.service.ts(1 hunks)apps/api/src/billing/services/stripe-webhook/stripe-webhook.service.ts(2 hunks)apps/api/src/billing/services/stripe/stripe.service.spec.ts(2 hunks)apps/api/src/billing/services/stripe/stripe.service.ts(7 hunks)apps/api/src/billing/services/wallet-balance-reload-check/README.md(1 hunks)apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts(1 hunks)apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts(2 hunks)apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.ts(2 hunks)apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.ts(1 hunks)apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts(11 hunks)apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts(6 hunks)apps/api/src/core/repositories/base.repository.ts(2 hunks)apps/api/src/core/services/job-queue/job-queue.service.spec.ts(9 hunks)apps/api/src/core/services/job-queue/job-queue.service.ts(4 hunks)apps/api/src/core/services/tx/tx.service.ts(1 hunks)apps/api/src/core/types/require.type.ts(1 hunks)apps/api/src/dashboard/services/stats/stats.service.ts(1 hunks)apps/api/src/deployment/repositories/deployment-setting/deployment-setting.repository.ts(4 hunks)apps/api/src/deployment/services/draining-deployment/draining-deployment.service.spec.ts(7 hunks)apps/api/src/deployment/services/draining-deployment/draining-deployment.service.ts(1 hunks)apps/api/test/seeders/balance.seeder.ts(2 hunks)apps/api/test/seeders/database-payment-method.seeder.ts(1 hunks)apps/api/test/seeders/payment-method.seeder.ts(2 hunks)packages/http-sdk/src/stripe/stripe.service.ts(0 hunks)
💤 Files with no reviewable changes (2)
- packages/http-sdk/src/stripe/stripe.service.ts
- apps/api/src/billing/http-schemas/wallet.schema.ts
🚧 Files skipped from review as they are similar to previous changes (14)
- apps/api/test/seeders/balance.seeder.ts
- apps/api/drizzle/meta/_journal.json
- apps/api/src/dashboard/services/stats/stats.service.ts
- apps/api/src/core/types/require.type.ts
- apps/api/src/billing/services/managed-signer/managed-signer.service.ts
- apps/api/src/auth/services/auth.service.ts
- apps/api/src/auth/services/ability/ability.service.ts
- apps/api/src/core/repositories/base.repository.ts
- apps/api/src/billing/services/refill/refill.service.ts
- apps/api/drizzle/meta/0023_snapshot.json
- apps/api/src/billing/http-schemas/stripe.schema.ts
- apps/api/src/core/services/tx/tx.service.ts
- apps/api/drizzle/0023_clumsy_vertigo.sql
- apps/api/src/billing/controllers/wallet/wallet.controller.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (.cursor/rules/general.mdc)
**/*.{ts,tsx,js}: Never use typeanyor cast to typeany. 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-webhook/stripe-webhook.service.tsapps/api/src/billing/services/managed-signer/managed-signer.service.spec.tsapps/api/drizzle.config.tsapps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.tsapps/api/src/billing/services/stripe/stripe.service.spec.tsapps/api/src/deployment/services/draining-deployment/draining-deployment.service.spec.tsapps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.tsapps/api/src/deployment/services/draining-deployment/draining-deployment.service.tsapps/api/src/billing/model-schemas/wallet-setting/wallet-setting.schema.tsapps/api/src/billing/controllers/stripe/stripe.controller.tsapps/api/src/billing/repositories/user-wallet/user-wallet.repository.tsapps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.tsapps/api/test/seeders/payment-method.seeder.tsapps/api/src/billing/repositories/payment-method/payment-method.repository.tsapps/api/src/deployment/repositories/deployment-setting/deployment-setting.repository.tsapps/api/src/billing/services/balances/balances.service.tsapps/api/src/billing/services/wallet-settings/wallet-settings.service.tsapps/api/test/seeders/database-payment-method.seeder.tsapps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.tsapps/api/src/core/services/job-queue/job-queue.service.spec.tsapps/api/src/billing/model-schemas/payment-method/payment-method.schema.tsapps/api/src/billing/routes/stripe-webhook/stripe-webhook.router.tsapps/api/src/billing/services/paying-user/paying-user.tsapps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.tsapps/api/src/billing/services/wallet-initializer/wallet-initializer.service.tsapps/api/src/billing/routes/stripe-payment-methods/stripe-payment-methods.router.tsapps/api/src/core/services/job-queue/job-queue.service.tsapps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.tsapps/api/src/billing/services/stripe/stripe.service.ts
**/*.spec.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/no-jest-mock.mdc)
Don't use
jest.mock()in test files. Instead, usejest-mock-extendedto create mocks and pass mocks as dependencies to the service under testUse
setupfunction instead ofbeforeEachin test files. Thesetupfunction must be at the bottom of the rootdescribeblock, 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>.namein 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/managed-signer/managed-signer.service.spec.tsapps/api/src/billing/services/stripe/stripe.service.spec.tsapps/api/src/deployment/services/draining-deployment/draining-deployment.service.spec.tsapps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.tsapps/api/src/core/services/job-queue/job-queue.service.spec.tsapps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
🧠 Learnings (18)
📚 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-webhook/stripe-webhook.service.tsapps/api/src/billing/controllers/stripe/stripe.controller.tsapps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.tsapps/api/src/billing/services/stripe/stripe.service.ts
📚 Learning: 2025-09-04T04:27:50.638Z
Learnt from: stalniy
Repo: akash-network/console PR: 1868
File: apps/api/src/billing/services/managed-signer/managed-signer.service.ts:1-1
Timestamp: 2025-09-04T04:27:50.638Z
Learning: In the akash-network/console project, importing MsgCreateLease from "akashnetwork/akash-api/v1beta3" instead of v1beta4 is considered non-critical by the maintainers, likely due to backward compatibility or project-specific requirements.
Applied to files:
apps/api/src/billing/services/managed-signer/managed-signer.service.spec.tsapps/api/src/core/services/job-queue/job-queue.service.spec.ts
📚 Learning: 2025-09-04T04:27:40.858Z
Learnt from: stalniy
Repo: akash-network/console PR: 1868
File: apps/api/src/billing/services/managed-signer/managed-signer.service.ts:98-107
Timestamp: 2025-09-04T04:27:40.858Z
Learning: In the Akash protocol, when processing MsgCreateLease messages, the bidId.dseq field is always required and guaranteed to be present at runtime, even though TypeScript types may suggest it could be undefined. The non-null assertion operator (!) is appropriate to use in this context.
Applied to files:
apps/api/src/billing/services/managed-signer/managed-signer.service.spec.ts
📚 Learning: 2025-11-28T09:17:28.832Z
Learnt from: ygrishajev
Repo: akash-network/console PR: 2274
File: apps/api/drizzle/0023_sad_adam_warlock.sql:1-1
Timestamp: 2025-11-28T09:17:28.832Z
Learning: In the Akash Network console billing system, the `user_wallets.user_id` column is required (NOT NULL). All user wallets must be associated with a user ID, and there are no anonymous wallets without a user_id in production.
Applied to files:
apps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.tsapps/api/src/deployment/repositories/deployment-setting/deployment-setting.repository.ts
📚 Learning: 2025-11-28T09:21:45.610Z
Learnt from: ygrishajev
Repo: akash-network/console PR: 2274
File: apps/api/drizzle/0023_sad_adam_warlock.sql:1-1
Timestamp: 2025-11-28T09:21:45.610Z
Learning: In the Akash Network console, `userSettings.userId` can be NULL for anonymous users, but `user_wallets.user_id` is always required (NOT NULL). Anonymous users are identified by userId=null in the userSettings table, not in the user_wallets table.
Applied to files:
apps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.ts
📚 Learning: 2025-09-29T02:40:22.090Z
Learnt from: stalniy
Repo: akash-network/console PR: 1951
File: apps/api/src/user/services/user/user.service.ts:48-63
Timestamp: 2025-09-29T02:40:22.090Z
Learning: In the Akash Network Console codebase, the users table was originally created by Sequelize and later migrated to Drizzle. Due to this migration history, the unique constraint on the userId field is named "userSetting_userId_unique" rather than following the typical "users_user_id_unique" pattern.
Applied to files:
apps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.tsapps/api/src/billing/model-schemas/wallet-setting/wallet-setting.schema.ts
📚 Learning: 2025-11-25T17:45:44.790Z
Learnt from: CR
Repo: akash-network/console PR: 0
File: .cursor/rules/no-jest-mock.mdc:0-0
Timestamp: 2025-11-25T17:45:44.790Z
Learning: Applies to **/*.spec.{ts,tsx} : 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
Applied to files:
apps/api/src/deployment/services/draining-deployment/draining-deployment.service.spec.tsapps/api/src/core/services/job-queue/job-queue.service.spec.tsapps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
📚 Learning: 2025-11-25T17:45:52.965Z
Learnt from: CR
Repo: akash-network/console PR: 0
File: .cursor/rules/setup-instead-of-before-each.mdc:0-0
Timestamp: 2025-11-25T17:45:52.965Z
Learning: Applies to **/*.spec.{ts,tsx} : 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.
Applied to files:
apps/api/src/deployment/services/draining-deployment/draining-deployment.service.spec.tsapps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
📚 Learning: 2025-11-25T17:45:58.258Z
Learnt from: CR
Repo: akash-network/console PR: 0
File: .cursor/rules/test-descriptions.mdc:0-0
Timestamp: 2025-11-25T17:45:58.258Z
Learning: Applies to **/*.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
Applied to files:
apps/api/src/deployment/services/draining-deployment/draining-deployment.service.spec.ts
📚 Learning: 2025-11-25T17:45:49.180Z
Learnt from: CR
Repo: akash-network/console PR: 0
File: .cursor/rules/query-by-in-tests.mdc:0-0
Timestamp: 2025-11-25T17:45:49.180Z
Learning: Applies to {apps/deploy-web,apps/provider-console}/**/*.spec.tsx : Use `queryBy` methods instead of `getBy` methods in test expectations
Applied to files:
apps/api/src/deployment/services/draining-deployment/draining-deployment.service.spec.tsapps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
📚 Learning: 2025-11-19T15:15:07.283Z
Learnt from: ygrishajev
Repo: akash-network/console PR: 2254
File: apps/api/test/functional/sign-and-broadcast-tx.spec.ts:4-4
Timestamp: 2025-11-19T15:15:07.283Z
Learning: In the Akash Network Console project, when tests use native Node.js fetch (available in Node 18+), fetch-mock should be used for HTTP mocking instead of nock, as nock does not support intercepting native fetch calls. This applies to apps/api/test/functional/sign-and-broadcast-tx.spec.ts and any other tests using native fetch.
Applied to files:
apps/api/src/deployment/services/draining-deployment/draining-deployment.service.spec.tsapps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
📚 Learning: 2025-10-15T16:39:55.348Z
Learnt from: jzsfkzm
Repo: akash-network/console PR: 2039
File: apps/deploy-web/tests/ui/change-wallets.spec.ts:4-10
Timestamp: 2025-10-15T16:39:55.348Z
Learning: In the Akash Console E2E tests using the context-with-extension fixture, the first wallet is automatically created during fixture setup via `importWalletToLeap` in `apps/deploy-web/tests/ui/fixture/wallet-setup.ts`, so tests that call `frontPage.createWallet()` are creating a second wallet to test wallet switching functionality.
Applied to files:
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.tsapps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
📚 Learning: 2025-10-31T11:26:42.138Z
Learnt from: stalniy
Repo: akash-network/console PR: 2138
File: apps/api/src/billing/lib/batch-signing-client/batch-signing-client.service.ts:379-382
Timestamp: 2025-10-31T11:26:42.138Z
Learning: In TypeScript/JavaScript, the pattern of checking a cached value and then performing an async operation to fetch it without proper synchronization is race condition unsafe:
```typescript
private async getAddress() {
if (!this.address) {
this.address = await this.wallet.getFirstAddress();
}
return this.address;
}
```
Multiple concurrent calls can all pass the `if (!this.address)` check before any of them sets the value, leading to duplicate async operations. This should be flagged as a race condition. Proper synchronization (mutex, atomic promise caching, or guaranteed single-threaded execution) is required.
Applied to files:
apps/api/src/billing/controllers/stripe/stripe.controller.tsapps/api/src/billing/repositories/payment-method/payment-method.repository.tsapps/api/src/deployment/repositories/deployment-setting/deployment-setting.repository.tsapps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.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/controllers/stripe/stripe.controller.tsapps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.tsapps/api/src/billing/routes/stripe-payment-methods/stripe-payment-methods.router.ts
📚 Learning: 2025-09-04T04:21:26.067Z
Learnt from: stalniy
Repo: akash-network/console PR: 1868
File: apps/api/src/app/services/trial-deployment-lease-created/trial-deployment-lease-created.handler.ts:73-76
Timestamp: 2025-09-04T04:21:26.067Z
Learning: The apps/api codebase consistently uses .toISOString() for startAfter values when scheduling jobs with JobQueueService to ensure UTC timezone consistency. This pattern is used throughout trial-started.handler.ts, trial-deployment-lease-created.handler.ts and other job scheduling code. Do not suggest changing to Date objects as ISO strings provide explicit UTC representation for database storage.
Applied to files:
apps/api/src/core/services/job-queue/job-queue.service.spec.ts
📚 Learning: 2025-07-24T17:00:52.361Z
Learnt from: baktun14
Repo: akash-network/console PR: 1725
File: apps/api/src/utils/constants.ts:5-5
Timestamp: 2025-07-24T17:00:52.361Z
Learning: In the Akash Network Console project, when cross-cutting concerns or broader refactoring issues are identified during PR review, the preferred approach is to create a separate GitHub issue to track the work rather than expanding the scope of the current PR. This maintains focus and allows for proper planning of architectural improvements.
Applied to files:
apps/api/src/core/services/job-queue/job-queue.service.spec.ts
📚 Learning: 2025-09-04T04:21:26.067Z
Learnt from: stalniy
Repo: akash-network/console PR: 1868
File: apps/api/src/app/services/trial-deployment-lease-created/trial-deployment-lease-created.handler.ts:73-76
Timestamp: 2025-09-04T04:21:26.067Z
Learning: In apps/api job scheduling with JobQueueService, use .toISOString() for startAfter values instead of Date objects to ensure UTC timezone consistency when dates are persisted to the database. Date objects can be ambiguous about timezone during serialization, while ISO strings with .toISOString() explicitly represent UTC.
Applied to files:
apps/api/src/core/services/job-queue/job-queue.service.spec.tsapps/api/src/core/services/job-queue/job-queue.service.ts
📚 Learning: 2025-11-25T17:45:39.561Z
Learnt from: CR
Repo: akash-network/console PR: 0
File: .cursor/rules/general.mdc:0-0
Timestamp: 2025-11-25T17:45:39.561Z
Learning: Applies to **/*.{ts,tsx,js} : Never use type `any` or cast to type `any`. Always define the proper TypeScript types.
Applied to files:
apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
🧬 Code graph analysis (17)
apps/api/src/billing/services/stripe-webhook/stripe-webhook.service.ts (2)
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (1)
user(137-156)apps/api/src/billing/services/paying-user/paying-user.ts (1)
assertIsPayingUser(8-10)
apps/api/src/billing/services/stripe/stripe.service.spec.ts (1)
packages/logging/src/services/logger/logger.service.ts (1)
LoggerService(39-165)
apps/api/src/billing/controllers/stripe/stripe.controller.ts (3)
apps/api/src/auth/services/auth.service.ts (1)
Protected(62-78)apps/api/src/billing/http-schemas/stripe.schema.ts (5)
PaymentMethodMarkAsDefaultInput(240-240)PaymentMethodResponse(243-243)PaymentMethodsResponse(242-242)ConfirmPaymentRequest(244-244)ConfirmPaymentResponse(247-247)apps/api/src/core/lib/semaphore.decorator.ts (1)
Semaphore(3-28)
apps/api/src/billing/repositories/user-wallet/user-wallet.repository.ts (1)
apps/api/src/core/providers/postgres.provider.ts (1)
ApiPgTables(80-80)
apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.ts (3)
apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts (2)
singleton(24-66)WalletSettingOutput(19-22)apps/api/src/core/services/job-queue/job-queue.service.ts (2)
singleton(11-197)EnqueueOptions(227-227)apps/api/src/billing/events/wallet-balance-reload-check.ts (1)
WalletBalanceReloadCheck(5-15)
apps/api/test/seeders/payment-method.seeder.ts (3)
apps/api/src/billing/services/stripe/stripe.service.ts (1)
PaymentMethod(33-33)apps/api/src/billing/http-schemas/stripe.schema.ts (1)
PaymentMethod(241-241)packages/http-sdk/src/stripe/stripe.types.ts (1)
PaymentMethod(30-41)
apps/api/src/billing/repositories/payment-method/payment-method.repository.ts (2)
apps/api/src/core/providers/postgres.provider.ts (1)
ApiPgTables(80-80)apps/api/src/core/services/tx/tx.service.ts (1)
ApiTransaction(11-11)
apps/api/src/deployment/repositories/deployment-setting/deployment-setting.repository.ts (2)
apps/api/src/user/model-schemas/user/user.schema.ts (1)
Users(8-28)apps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.ts (1)
UserWallets(5-18)
apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts (4)
apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts (2)
WalletSettingInput(12-17)WalletSettingOutput(19-22)apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (2)
error(158-163)user(137-156)apps/api/src/auth/services/auth.service.ts (2)
ability(44-46)ability(48-50)apps/api/src/core/repositories/base.repository.ts (1)
isUniqueViolation(237-239)
apps/api/test/seeders/database-payment-method.seeder.ts (1)
apps/api/src/billing/repositories/payment-method/payment-method.repository.ts (1)
PaymentMethodOutput(11-11)
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (9)
apps/api/src/core/types/require.type.ts (1)
Require(1-3)apps/api/src/billing/repositories/user-wallet/user-wallet.repository.ts (1)
UserWalletOutput(18-22)apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts (1)
WalletSettingOutput(19-22)apps/api/src/billing/services/paying-user/paying-user.ts (2)
PayingUser(6-6)isPayingUser(12-14)apps/api/src/billing/http-schemas/balance.schema.ts (1)
GetBalancesResponseOutput(17-17)apps/api/src/billing/services/stripe/stripe.service.ts (1)
PaymentMethod(33-33)apps/api/src/billing/http-schemas/stripe.schema.ts (1)
PaymentMethod(241-241)apps/api/src/core/services/job-queue/job-queue.service.ts (2)
JobPayload(209-209)JobMeta(218-218)apps/api/src/billing/events/wallet-balance-reload-check.ts (1)
WalletBalanceReloadCheck(5-15)
apps/api/src/core/services/job-queue/job-queue.service.spec.ts (1)
apps/api/src/core/services/job-queue/job-queue.service.ts (2)
setup(185-192)Job(199-207)
apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts (4)
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (1)
user(137-156)apps/api/test/seeders/payment-method.seeder.ts (1)
generatePaymentMethod(11-56)apps/api/src/billing/services/stripe/stripe.service.ts (1)
PaymentMethod(33-33)apps/api/src/billing/http-schemas/stripe.schema.ts (1)
PaymentMethod(241-241)
apps/api/src/billing/routes/stripe-payment-methods/stripe-payment-methods.router.ts (5)
apps/api/src/billing/services/stripe/stripe.service.ts (2)
createSetupIntent(53-59)getDefaultPaymentMethod(124-144)apps/api/src/billing/controllers/stripe/stripe.controller.ts (3)
createSetupIntent(41-48)markAsDefault(51-56)getDefaultPaymentMethod(59-67)packages/http-sdk/src/stripe/stripe.service.ts (1)
createSetupIntent(23-25)apps/api/src/core/services/openapi-docs/openapi-security.ts (1)
SECURITY_BEARER_OR_API_KEY(5-5)apps/api/src/billing/http-schemas/stripe.schema.ts (2)
PaymentMethodMarkAsDefaultInputSchema(10-14)PaymentMethodResponseSchema(61-63)
apps/api/src/core/services/job-queue/job-queue.service.ts (1)
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (1)
job(72-90)
apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts (2)
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (1)
userId(92-135)apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts (2)
userId(58-75)userId(77-105)
apps/api/src/billing/services/stripe/stripe.service.ts (5)
packages/http-sdk/src/stripe/stripe.types.ts (1)
PaymentMethod(30-41)apps/api/src/billing/services/paying-user/paying-user.ts (1)
PayingUser(6-6)apps/api/src/auth/services/auth.service.ts (2)
ability(44-46)ability(48-50)apps/api/src/user/repositories/user/user.repository.ts (1)
UserOutput(10-12)apps/api/src/core/services/tx/tx.service.ts (1)
WithTransaction(39-51)
🪛 LanguageTool
apps/api/src/billing/services/wallet-balance-reload-check/README.md
[style] ~39-~39: ‘emergency situations’ might be wordy. Consider a shorter alternative.
Context: ... margin before funds run out - Prevents emergency situations ## When Does the Check Run? The handl...
(EN_WORDINESS_PREMIUM_EMERGENCY_SITUATIONS)
🪛 markdownlint-cli2 (0.18.1)
apps/api/src/billing/services/wallet-balance-reload-check/README.md
53-53: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
104-104: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
150-150: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
⏰ 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). (4)
- GitHub Check: validate / validate-app
- GitHub Check: test-build
- GitHub Check: validate / validate-app
- GitHub Check: test-build
🔇 Additional comments (38)
apps/api/drizzle.config.ts (1)
5-9: Env‑driven DB URL with explicit guard looks goodReading
POSTGRES_DB_URIfromprocess.envand failing fast when it’s missing is a solid, simple setup for drizzle‑kit, and the value is correctly wired intodbCredentials.url. No issues from a correctness or typing perspective here.Also applies to: 16-16
apps/api/src/billing/model-schemas/user-wallet/user-wallet.schema.ts (2)
9-10: Technical implementation is correct; defer to existing review for breaking change confirmation.The syntax and schema structure are correctly implemented. The past review comment comprehensively addresses the breaking change implications of adding
.notNull()touserId. No new concerns identified beyond what was already flagged.
1-27: Schema structure is well-defined and follows best practices.The schema implementation is clean and follows Drizzle ORM conventions properly:
- Proper TypeScript types throughout (no
anyusage)- Appropriate constraints and defaults
- Good use of the helper function for allowance fields to maintain DRY principle
- Follows coding guidelines
apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.ts (2)
5-5: LGTM! Correct import cleanup.Removing
UserWalletInputfrom the imports is appropriate since it's no longer used in the file after the method signature simplification.
53-53: Type signature simplification is correct and improves type safety.The
initializemethod parameter type ofuserId: stringis safe and compatible with its single caller inrefill.service.ts, which passesuserId: UserWalletOutput["userId"](a non-optional string). No type compatibility issues found.apps/api/src/billing/repositories/user-wallet/user-wallet.repository.ts (2)
9-10: Good type refinement for insert vs. update distinction.Introducing
DbCreateUserWalletInputas the concrete insert type and derivingDbUserWalletInputas its partial makes the type system more explicit about required vs. optional fields. This aligns well with the broader typing improvements across the billing repositories.
72-83: Method signature correctly aligns with database operations.The
createmethod signature usingPick<DbCreateUserWalletInput, "userId" | "address">accurately reflects the database schema, whereuserIdis required andaddressis optional. The implementation is sound, and all call sites—including the newly discovered one inwallet-initializer.service.ts:54—are type-safe and compatible with the signature.apps/api/src/billing/routes/stripe-webhook/stripe-webhook.router.ts (1)
11-51: Stripe webhook handler wiring matches expected contractThe route definition and handler correctly require the
stripe-signatureheader, pass the rawtext/plainrequest body toCheckoutController.webhook, and return appropriate 400/200 responses. This aligns with Stripe’s signature verification needs and keeps the OpenAPI description in sync with runtime behavior.apps/api/src/billing/model-schemas/wallet-setting/wallet-setting.schema.ts (1)
31-40: LGTM!The relations are correctly defined following Drizzle ORM conventions, with proper references to the foreign keys defined in the WalletSetting table schema.
apps/api/test/seeders/payment-method.seeder.ts (1)
58-61: LGTM!The new seeder function correctly merges the
validatedfield with the Stripe payment method data while maintaining proper type safety.apps/api/src/billing/services/paying-user/paying-user.ts (1)
6-14: LGTM!The PayingUser utilities are well-implemented:
- Type predicate and assertion function follow TypeScript best practices
- HTTP 402 (Payment Required) is semantically appropriate
- Clean, focused implementation
apps/api/src/core/services/job-queue/job-queue.service.ts (2)
108-115: LGTM!The new
complete()method correctly delegates to pg-boss and logs the completion event with appropriate metadata.
218-224: LGTM!The
JobMetatype and updatedJobHandlersignature are well-designed:
JobMetaappropriately narrows to just theidfield- Making the
jobparameter optional maintains backward compatibilityNote: Per past review comments, the breaking change to handler signatures was addressed in commit a45589e.
apps/api/src/billing/services/stripe-webhook/stripe-webhook.service.ts (1)
147-147: LGTM!The
assertIsPayingUserprecondition correctly narrows the user type and ensures payment setup before proceeding with Stripe operations.apps/api/test/seeders/database-payment-method.seeder.ts (1)
3-20: LGTM!The seeder correctly adopts the
PaymentMethodOutputtype from the repository and includes the newisDefaultfield, improving type consistency across the codebase.apps/api/src/billing/services/stripe/stripe.service.spec.ts (1)
7-7: LGTM!The test correctly reflects the updated
StripeServiceconstructor signature by adding the logger dependency, maintaining consistency with the service implementation.Also applies to: 1422-1422
apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.ts (2)
30-53: Transaction and job lifecycle management looks good.The method correctly:
- Wraps operations in a transaction for atomicity
- Handles previous job cleanup (cancel or complete) before scheduling new job
- Updates wallet setting with new job ID before enqueueing
- Validates job creation and throws on failure
56-58: LGTM!The
cancelmethod correctly delegates to the job queue service with the appropriate job name.apps/api/src/core/services/job-queue/job-queue.service.spec.ts (5)
22-25: LGTM!Queue configuration expectations are correctly updated to match the new defaults (retryDelayMax: 300s, retryLimit: 5, policy: undefined).
68-76: LGTM!Good addition of a test case verifying that duplicate handler registration for the same queue throws an appropriate error. This prevents silent conflicts when multiple handlers accidentally target the same queue.
95-101: LGTM!Updated log expectations correctly capture the new
jobIdandoptionsfields in theJOB_ENQUEUEDevent, providing better traceability for debugging and monitoring.
152-176: LGTM!The test correctly reflects the updated job processing pattern with single-item job arrays and context object containing
{ id: job.id }. The mock implementation aligns with the PgBoss.WorkHandler signature.
300-303: Type cast is acceptable for mock compatibility.The cast to
ExecutionContextService["runWithContext"]is necessary to satisfy the mock's type requirements while maintaining the correct callback signature (() => Promise<unknown>).apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts (1)
38-46: LGTM!The explicit
Promise<WalletSettingOutput | undefined>return type improves type safety and aligns with the consistent pattern of returningundefinedwhen no record is found.apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts (2)
21-72: LGTM!Comprehensive test for the primary reload trigger scenario. The test validates:
- Cost calculation with 7-day lookahead
- Next check scheduling for 1 day
- Payment intent creation with correct amount and idempotency key
- Proper event logging
The assertions use
toBeCloseToappropriately for date comparisons.
282-373: LGTM on setup function structure.The setup function correctly:
- Is placed at the bottom of the root describe block
- Accepts a single parameter with inline type definition
- Returns all necessary test doubles and data
- Avoids shared state
- Does not have a specified return type
As per coding guidelines, this follows the expected pattern.
apps/api/src/billing/controllers/stripe/stripe.controller.ts (4)
50-56: LGTM!The
markAsDefaultmethod correctly:
- Uses the
@Protecteddecorator for authorization- Retrieves the paying user for customer context
- Passes ability for access control in the service layer
58-67: LGTM!The 404 assertion is now correctly implemented, addressing the past review comment. The method properly validates that a default payment method exists before returning.
69-79: LGTM!Good defensive handling for non-paying users. Using
strict: falseand returning an empty array when no paying user exists provides a graceful fallback.
84-89: LGTM!The ownership check is now properly awaited, addressing the critical security issue from past review comments. The
assert(await this.stripe.hasPaymentMethod(...))pattern correctly evaluates the resolved boolean value.apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts (5)
8-10: LGTM!New imports correctly reflect the updated service dependencies for Stripe and wallet reload job integration.
98-103: LGTM!The test correctly verifies that
walletReloadJobService.scheduleForWalletSettingis called with the expected wallet setting data when creating a new setting with auto-reload enabled.
197-202: LGTM!Good test coverage for the scheduling flow when updating an existing setting to enable auto-reload. The expectation correctly validates the setting ID and user ID are passed to the scheduler.
231-231: LGTM!Correct verification that the job is cancelled when auto-reload is disabled, using the user ID and existing job ID.
276-319: LGTM on setup function structure.The setup function correctly follows guidelines:
- Positioned at the bottom of the describe block
- Single parameter with inline type definition
- Creates and returns all necessary test doubles
- Uses
jest-mock-extendedmock() function- No shared state between tests
The addition of
stripeService,walletReloadJobService, anduserRepositoryproperly reflects the updated service dependencies.apps/api/src/billing/routes/stripe-payment-methods/stripe-payment-methods.router.ts (1)
127-148: Delete route now correctly returns 204 with empty bodyThe delete handler’s
c.body(null, 204)matches the documented 204 response and is consistent with typical REST semantics. No issues here.apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (1)
17-31: End-to-end auto‑reload flow and error handling look solidThe resource-collection, validation, reload decision (
balancevs threshold), and follow-up scheduling logic are coherent and defensive:
- Validation errors short-circuit with structured logging via
ValidationError, avoiding noisy retries for misconfigured wallets.- Only paying users with initialized wallets, enabled auto‑reload, and a default payment method proceed.
- Reload amount respects a minimum of $20 and uses a job-scoped idempotency key, which should prevent double-charging on job retries.
- Balance-reload and next-check scheduling both log failures and rethrow so the job framework can retry.
I don’t see correctness or concurrency issues worth blocking on here.
Also applies to: 61-90, 92-156, 165-209, 211-229
apps/api/src/billing/services/stripe/stripe.service.ts (1)
108-123: New payment-method helpers and idempotentcreatePaymentIntentlook good
getPaymentMethodsnow cleanly merges remote methods with local validation status and sorts bycreateddescending.hasPaymentMethodcorrectly handles “resource missing” asfalsewhile rethrowing other Stripe errors.markRemotePaymentMethodAsDefaultcentralizes the customer update with a short timeout.createPaymentIntentnow takes an amount in major units, converts to cents once, and threads an optionalidempotencyKeythrough to Stripe, which fits the way you’re using it from the auto‑reload handler.Assuming existing tests around Stripe interactions pass, this looks correct and aligns with typical Stripe usage.
Please re-run the existing
StripeServicetest suite to confirm the updatedcreatePaymentIntentsignature and options still match the Stripe SDK expectations.Also applies to: 146-160, 190-231
apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.ts
Show resolved
Hide resolved
apps/api/src/deployment/services/draining-deployment/draining-deployment.service.ts
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (5)
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts (3)
41-47: Consider tightening date assertions or controllingnowmore explicitly.Using
new Date()in both the SUT and the spec withtoBeCloseTo(..., -3)gives a very generous tolerance window; it’s unlikely to be flaky but the negative precision is unusual and hard to read. You might consider:
- Freezing time with fake timers and asserting exact equality, or
- Using a small positive precision and comparing timestamps in seconds instead of milliseconds.
This is non-blocking; current assertions are acceptable but a bit surprising.
Please double-check Jest’s
toBeCloseToprecision semantics in your version to ensure the current usage matches your intended tolerance.
290-322: SimplifyuserWithStripederivation for readability.The nested ternary logic for
userWithStripeis correct but hard to scan. Consider a more direct formulation such as:const baseUser = input?.user ?? UserSeeder.create(); const userWithStripe = input?.user && input.user.stripeCustomerId === null ? baseUser // explicitly test non-paying user case : baseUser.stripeCustomerId ? baseUser : { ...baseUser, stripeCustomerId: faker.string.uuid() };or an explicit
if/elsechain. This keeps the important distinction (“explicit null means non-paying; otherwise ensure it’s a paying user”) but is easier to reason about.
323-381: Minor cleanup: unify core imports for consistency.You import
JobMetafrom@src/coreandJobPayloadfrom"../../../core". Both should resolve to the same module; it would be clearer to import both from the same path (preferably the alias form) to avoid confusion.apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (2)
42-49: Hard-coded timing and amount constants are reasonable, but consider centralizing if reused.The daily check interval, 7‑day coverage horizon, 25% threshold, and
$20minimum reload amount are encoded as private fields, which is fine here. If these values are shared with HTTP validation (e.g., the$20floor instripe.schema.ts) or are likely to change, it might be worth centralizing them in a config/constants module to avoid drift.
137-156: Default payment method retrieval is simple and matches the “not found” behavior.
#getDefaultPaymentMethodscopes the call viacreateMongoAbility, returnsOkon a found method, and a clearDEFAULT_PAYMENT_METHOD_NOT_FOUNDerror otherwise. This dovetails with the spec that logs this specific validation error.If
StripeService.getDefaultPaymentMethodcan throw for transport-layer issues rather than returningundefined, you may want to add a narrow try/catch around it and convert known “expected” failures intoValidationErrorresults, leaving unexpected errors to bubble up. Not required for this PR if that service already normalizes its errors.Please confirm how
StripeService.getDefaultPaymentMethodbehaves on network/Stripe API errors in your environment; if it throws, consider whether you want those to fail the job or be logged as validation errors.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
-
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts(1 hunks) -
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts(2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (.cursor/rules/general.mdc)
**/*.{ts,tsx,js}: Never use typeanyor cast to typeany. 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/wallet-balance-reload-check/wallet-balance-reload-check.handler.tsapps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts
**/*.spec.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/no-jest-mock.mdc)
Don't use
jest.mock()in test files. Instead, usejest-mock-extendedto create mocks and pass mocks as dependencies to the service under testUse
setupfunction instead ofbeforeEachin test files. Thesetupfunction must be at the bottom of the rootdescribeblock, 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>.namein 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/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts
🧠 Learnings (4)
📚 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/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts
📚 Learning: 2025-10-31T11:26:42.138Z
Learnt from: stalniy
Repo: akash-network/console PR: 2138
File: apps/api/src/billing/lib/batch-signing-client/batch-signing-client.service.ts:379-382
Timestamp: 2025-10-31T11:26:42.138Z
Learning: In TypeScript/JavaScript, the pattern of checking a cached value and then performing an async operation to fetch it without proper synchronization is race condition unsafe:
```typescript
private async getAddress() {
if (!this.address) {
this.address = await this.wallet.getFirstAddress();
}
return this.address;
}
```
Multiple concurrent calls can all pass the `if (!this.address)` check before any of them sets the value, leading to duplicate async operations. This should be flagged as a race condition. Proper synchronization (mutex, atomic promise caching, or guaranteed single-threaded execution) is required.
Applied to files:
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.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/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts
📚 Learning: 2025-10-15T16:39:55.348Z
Learnt from: jzsfkzm
Repo: akash-network/console PR: 2039
File: apps/deploy-web/tests/ui/change-wallets.spec.ts:4-10
Timestamp: 2025-10-15T16:39:55.348Z
Learning: In the Akash Console E2E tests using the context-with-extension fixture, the first wallet is automatically created during fixture setup via `importWalletToLeap` in `apps/deploy-web/tests/ui/fixture/wallet-setup.ts`, so tests that call `frontPage.createWallet()` are creating a second wallet to test wallet switching functionality.
Applied to files:
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts
🧬 Code graph analysis (2)
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (9)
apps/api/src/core/types/require.type.ts (1)
Require(1-3)apps/api/src/billing/repositories/user-wallet/user-wallet.repository.ts (1)
UserWalletOutput(18-22)apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts (1)
WalletSettingOutput(19-22)apps/api/src/billing/services/paying-user/paying-user.ts (2)
PayingUser(6-6)isPayingUser(12-14)apps/api/src/billing/http-schemas/balance.schema.ts (1)
GetBalancesResponseOutput(17-17)apps/api/src/billing/services/stripe/stripe.service.ts (1)
PaymentMethod(33-33)apps/api/src/billing/http-schemas/stripe.schema.ts (1)
PaymentMethod(241-241)apps/api/src/core/services/job-queue/job-queue.service.ts (2)
JobPayload(209-209)JobMeta(218-218)apps/api/src/billing/events/wallet-balance-reload-check.ts (1)
WalletBalanceReloadCheck(5-15)
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts (3)
apps/api/src/core/services/job-queue/job-queue.service.ts (2)
JobPayload(209-209)JobMeta(218-218)apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (2)
job(72-90)user(137-156)apps/api/src/billing/events/wallet-balance-reload-check.ts (1)
WalletBalanceReloadCheck(5-15)
⏰ 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: test-build
- 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 (14)
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts (5)
21-80: Strong, behavior-driven coverage for the reload path.The happy-path test around
"triggers reload when balance is below 25% of cost"nicely exercises cost horizon, thresholding, Stripe intent fields (including idempotency key), and scheduling of the next check; it aligns well with the handler’s logic and the business rules.
82-107: Minimum-amount reload case is covered correctly.The
"triggers reload with minimum amount when needed amount is below minimum"scenario cleanly validates the$20floor behavior and the Stripe call payload; this complements the threshold test above and matches the handler’sMath.max(..., MIN_RELOAD_AMOUNT)logic.
109-157: Skip-path tests are accurate and readable.The two tests for
"does not trigger reload when balance equals/above 25% of cost"correctly assert that no payment intent is created and theWALLET_BALANCE_RELOAD_SKIPPEDevent is logged with the expected payload, matching thebalance >= thresholdbranch.
186-208: Good coverage of scheduling failure behavior.The
"logs error and throws when scheduling next check fails"test matches the implementation’s behavior of loggingERROR_SCHEDULING_NEXT_CHECKand rethrowing, ensuring PgBoss can retry the job. This regression-guards the design choice discussed in earlier review comments.
210-287: Validation-error scenarios are thoroughly exercised.The tests for missing wallet setting, disabled auto-reload, uninitialized wallet, missing Stripe customer ID, and missing default payment method align with
ValidationErrorevents and the logger payloads in the handler. This gives good confidence that all early-exit paths are observable and won’t silently fail.apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (9)
1-32: Imports, types, and supporting aliases are well structured.The use of
Result<…, ValidationError>,InitializedWallet,ActionableWalletSetting, andAllResourcesgives a clear, typed pipeline for the handler; bringing inRequirefrom the shared type utility avoids duplication noted in earlier reviews.
17-30:ValidationErrorand resource typing correctly match the current usage.
ValidationErrorcontaining justeventandmessage, combined withResources/AllResources, aligns with how errors are propagated and logged (#finishWithValidationError) and with what the specs assert on. This resolves the earlier mismatch around an unusedsourcefield.
61-70: Mainhandleorchestration is straightforward and matches tests.
handlecleanly separates:
- Resource collection and validation,
- Conditional reload with logging, and
- Scheduling the next check,
and uses
Resultfor control flow. The early‑return on validation errors via#finishWithValidationErroris simple and aligns with the spec behaviors.
72-90: Resource collection is cohesive and correctly ordered.
#collectResourcesfirst validates wallet and user state, then retrieves the default payment method, then fiat balance; this order minimizes external calls when basic invariants fail. Returning a fully-populatedAllResourcesobject makes the downstream reload logic easy to reason about.
92-135: Wallet and user validation logic is solid and mirrors the spec expectations.The
#getValidWalletResourceschecks:
- Existence of wallet setting,
autoReloadEnabled,- Non-empty wallet address, and
isPayingUser(user)(Stripe customer ID),and returns specific
event/messagepairs for each failure. These match the test suite’s validation paths and prevent unnecessary downstream calls when the wallet/user is not reloadable.
158-163: Validation error logging is consistent and structured.
#finishWithValidationErrorsimply forwards theeventandmessageand attachesuserId, matching the tests and avoiding partial state changes. This is straightforward and easy to monitor.
165-209: Reload decision logic, amount calculation, and idempotent Stripe call look correct.Key aspects look solid:
- Horizon and cost: computes a 7‑day
reloadTargetDateand usescalculateAllDeploymentCostUntilDate+toFiatAmountto derivecostUntilTargetDateInFiat.- Thresholding: reload only when
costUntilTargetDateInFiat > 0andbalance < 0.25 * cost, which matches the specs.- Amount:
Math.max(costUntilTargetDateInFiat - balance, MIN_RELOAD_AMOUNT_IN_USD)implements the “top up to coverage, but at least $20” rule.- Idempotency:
idempotencyKey: \${WalletBalanceReloadCheck.name}.${resources.job.id}`` ensures PgBoss retries won’t double‑charge Stripe as long as the job ID is stable across retries.- Logging: logs a clear
WALLET_BALANCE_RELOADEDon success andWALLET_BALANCE_RELOAD_FAILEDwith the error on failure, and rethrows to signal a job failure.Overall this matches the PR’s intent and the associated specs.
If you haven’t already, please verify PgBoss’s behavior around job IDs on retries—idempotency hinges on the job ID being stable so that the same idempotency key is reused for the same logical reload.
211-225: Scheduling behavior and error handling are appropriate for reliability.
#scheduleNextCheck:
- Schedules via
walletReloadJobService.scheduleForWalletSettingwith an ISOstartAfterbased on#calculateNextCheckDate(), and- Logs
ERROR_SCHEDULING_NEXT_CHECKand rethrows on failure.This ensures that scheduling failures don’t silently disable auto‑reload and allows PgBoss to retry the job. The corresponding spec confirms this behavior, which is a good reliability stance.
227-229: Next-check date calculation is clear and consistent with tests.
#calculateNextCheckDateuses the samemillisecondsInDaybasis as the tests, keeping the daily cadence logic easy to understand.
...src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts
Outdated
Show resolved
Hide resolved
21068b5 to
bc659a9
Compare
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (1)
46-49: Consider centralizing the$20minimum reload amount with Stripe payment validation.
#MIN_RELOAD_AMOUNT_IN_USD = 20duplicates the business rule enforced in the Stripe schema atapps/api/src/billing/http-schemas/stripe.schema.tsline 69 (amount: z.number().gte(20, ...)). Extract this constant to a single source of truth in the billing module and import it both here and in the schema to prevent divergence if this threshold changes in the future.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts(1 hunks)apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts(2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (.cursor/rules/general.mdc)
**/*.{ts,tsx,js}: Never use typeanyor cast to typeany. 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/wallet-balance-reload-check/wallet-balance-reload-check.handler.tsapps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts
**/*.spec.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/no-jest-mock.mdc)
Don't use
jest.mock()in test files. Instead, usejest-mock-extendedto create mocks and pass mocks as dependencies to the service under testUse
setupfunction instead ofbeforeEachin test files. Thesetupfunction must be at the bottom of the rootdescribeblock, 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>.namein 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/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts
🧠 Learnings (4)
📚 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/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts
📚 Learning: 2025-10-31T11:26:42.138Z
Learnt from: stalniy
Repo: akash-network/console PR: 2138
File: apps/api/src/billing/lib/batch-signing-client/batch-signing-client.service.ts:379-382
Timestamp: 2025-10-31T11:26:42.138Z
Learning: In TypeScript/JavaScript, the pattern of checking a cached value and then performing an async operation to fetch it without proper synchronization is race condition unsafe:
```typescript
private async getAddress() {
if (!this.address) {
this.address = await this.wallet.getFirstAddress();
}
return this.address;
}
```
Multiple concurrent calls can all pass the `if (!this.address)` check before any of them sets the value, leading to duplicate async operations. This should be flagged as a race condition. Proper synchronization (mutex, atomic promise caching, or guaranteed single-threaded execution) is required.
Applied to files:
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.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/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts
📚 Learning: 2025-10-15T16:39:55.348Z
Learnt from: jzsfkzm
Repo: akash-network/console PR: 2039
File: apps/deploy-web/tests/ui/change-wallets.spec.ts:4-10
Timestamp: 2025-10-15T16:39:55.348Z
Learning: In the Akash Console E2E tests using the context-with-extension fixture, the first wallet is automatically created during fixture setup via `importWalletToLeap` in `apps/deploy-web/tests/ui/fixture/wallet-setup.ts`, so tests that call `frontPage.createWallet()` are creating a second wallet to test wallet switching functionality.
Applied to files:
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts
🧬 Code graph analysis (1)
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts (8)
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (3)
job(72-90)error(158-163)user(137-156)apps/api/src/billing/events/wallet-balance-reload-check.ts (1)
WalletBalanceReloadCheck(5-15)apps/api/test/seeders/user-wallet.seeder.ts (1)
UserWalletSeeder(6-30)apps/api/test/seeders/user.seeder.ts (1)
UserSeeder(5-45)apps/api/test/seeders/wallet-setting.seeder.ts (1)
generateWalletSetting(5-18)packages/http-sdk/src/stripe/stripe.service.ts (1)
StripeService(18-94)apps/api/test/seeders/balance.seeder.ts (1)
generateBalance(21-34)apps/api/test/seeders/payment-method.seeder.ts (1)
generatePaymentMethod(11-56)
⏰ 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: test-build
- 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 (2)
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (1)
61-90: Core handler flow, validation, and error handling look solid.
- Resource collection validates wallet setting existence, auto‑reload flag, wallet initialization, and paying user status, returning structured
ValidationErrors.- Default payment method lookup is gated by CASL ability and correctly short‑circuits with a validation error if none is found.
- Reload logic uses a 7‑day cost horizon, 25% coverage threshold, a minimum reload of
$20, and an idempotency key per job; success and failure are both logged with useful context.- Next‑check scheduling now logs and rethrows errors, ensuring pgboss can retry when scheduling fails while keeping the payment step idempotent via Stripe.
No issues from a correctness or robustness standpoint on these paths.
Also applies to: 165-209, 211-229
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts (1)
41-64: Replace undocumented negativenumDigitsintoBeCloseTowith an explicit, documented tolerance check.The current assertions use
toBeCloseTowith negativenumDigits(-3), which is not documented in Jest's official API. While the formulaMath.abs(expected - received) < 10**(-numDigits) / 2does work mathematically (producing a ~500ms tolerance), relying on undocumented behavior makes tests fragile.However, the suggested refactor in the original comment (using
toBeLessThan(millisecondsInHour)) is not equivalent and would weaken the test by allowing a 1-hour tolerance instead of ~500ms.Instead, use an explicit, documented approach:
- expect(reloadTargetDate.getTime()).toBeCloseTo(expectedReloadDate.getTime(), -3); + const reloadDeltaMs = Math.abs(reloadTargetDate.getTime() - expectedReloadDate.getTime()); + expect(reloadDeltaMs).toBeLessThan(1000); // Within 1 secondOr use
toBeCloseTowith a documented positivenumDigitsthat achieves the desired ~500ms tolerance (e.g.,toBeCloseTo(..., 2)with scaled values). This maintains the original test strictness while relying only on documented Jest behavior.
refs #1779
Summary by CodeRabbit
New Features
Improvements
Bug Fixes / DB
Tests & Docs
✏️ Tip: You can customize this high-level summary in your review settings.