Skip to content

feat(billing): add database schema and core types for payment method validation#1897

Merged
baktun14 merged 6 commits intomainfrom
features/billing-database-schema
Sep 15, 2025
Merged

feat(billing): add database schema and core types for payment method validation#1897
baktun14 merged 6 commits intomainfrom
features/billing-database-schema

Conversation

@baktun14
Copy link
Contributor

@baktun14 baktun14 commented Sep 15, 2025

#1886

  • Add database migration for payment method changes
  • Add new payment method types and schemas
  • Update payment method repository with validation support
  • Update payment method seeder for testing

Summary by CodeRabbit

  • New Features

    • Payment methods gain a validation status; methods can be marked validated and listed per user.
    • CSV exports now include Card Brand and Card Last 4 columns.
  • Bug Fixes

    • Improved handling of duplicate trial-account detection across payment methods (now considers multiple candidates).
  • Tests

    • Test data and seeders updated to use richer Stripe-like payment method shapes and cover validation, duplicate checks, and CSV exports.

…validation

- Add database migration for payment method changes
- Add new payment method types and schemas
- Update payment method repository with validation support
- Update payment method seeder for testing
@baktun14 baktun14 requested a review from a team as a code owner September 15, 2025 08:14
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 15, 2025

Walkthrough

Adds an is_validated boolean column and composite index to payment_methods; updates Drizzle metadata and journal; extends the PaymentMethods schema and repository with validation-related methods; adapts seeders and tests to nested Stripe-style card data and to the renamed repository method used by StripeService.

Changes

Cohort / File(s) Change Summary
Database migration & metadata
apps/api/drizzle/0019_dusty_polaris.sql, apps/api/drizzle/meta/0019_snapshot.json, apps/api/drizzle/meta/_journal.json
- Add is_validated boolean column (NOT NULL DEFAULT false) to payment_methods
- Add composite BTREE index payment_methods_user_id_is_validated_idx on (user_id,is_validated)
- Append snapshot/journal entries for migration
ORM schema
apps/api/src/billing/model-schemas/payment-method/payment-method.schema.ts
- Add is_validated boolean column with default(false) and notNull()
- Define composite index payment_methods_user_id_is_validated_idx (userId, is_validated)
Repository
apps/api/src/billing/repositories/payment-method/payment-method.repository.ts
- Rename findOtherByFingerprintfindOthersTrialingByFingerprint and change to return multiple results
- Add findValidatedByUserId(userId)
- Add markAsValidated(paymentMethodId, userId)
- Load related user.userWallets (isTrialing) in duplicate lookup
Service usage
apps/api/src/billing/services/stripe/stripe.service.ts
- Replace call to findOtherByFingerprint with findOthersTrialingByFingerprint in duplicate-check logic (no other flow changes)
Tests — seeders & specs
apps/api/test/seeders/payment-method.seeder.ts, apps/api/test/seeders/database-payment-method.seeder.ts, apps/api/src/billing/services/stripe/stripe.service.spec.ts
- Rework generatePaymentMethod to return full Stripe-shaped object and accept overrides
- Add generateDatabasePaymentMethod and DatabasePaymentMethod interface with isValidated field
- Update tests to use nested card and card.fingerprint structures, adapt duplicate-detection tests to array results and multi-user mocking, and update CSV/export expectations

Sequence Diagram(s)

sequenceDiagram
    participant StripeService
    participant PaymentMethodRepo
    participant DB

    Note over StripeService,PaymentMethodRepo #DDEEFF: Duplicate trial-account check (new)
    StripeService->>PaymentMethodRepo: findOthersTrialingByFingerprint(fingerprints[], currentUserId)
    PaymentMethodRepo->>DB: SELECT ... WHERE fingerprint IN (...) AND user_id != currentUserId
    DB-->>PaymentMethodRepo: rows[]
    PaymentMethodRepo-->>StripeService: PaymentMethod[] | undefined
    alt any userWallets.isTrialing == true
        StripeService->>StripeService: treat as duplicate
    else
        StripeService->>StripeService: no duplicate
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • ygrishajev

Poem

🐰 A hop, a schema, new boolean light,
is_validated tucked in rows so tight.
Seeders bloom, tests dance in kind,
fingerprints checked, duplicates find.
CodeRabbit cheers — validation takes flight! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly and accurately captures the primary intent of the changeset: introducing database schema support and related types for payment method validation. It matches the migration and schema updates (is_validated column, index, Drizzle metadata), repository and seeder changes shown in the diff, and uses clear conventional-commit formatting. The title is concise and specific enough for a teammate scanning history to understand the main purpose without listing every detail.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch features/billing-database-schema

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
apps/api/src/billing/model-schemas/payment-method/payment-method.schema.ts (2)

18-18: Use camelCase field name and map to snake_case column.

Keeps TS API consistent with existing fields (userId, paymentMethodId, createdAt).

Apply this diff:

-    is_validated: boolean("is_validated").default(false).notNull(),
+    isValidated: boolean("is_validated").default(false).notNull(),

And update downstream references to table.isValidated.


26-26: Update index definition to the camelCase property if you rename the field.

Apply this diff after renaming:

-    userIdIsValidatedIdx: index("payment_methods_user_id_is_validated_idx").on(table.userId, table.is_validated),
+    userIdIsValidatedIdx: index("payment_methods_user_id_is_validated_idx").on(table.userId, table.isValidated),
apps/api/src/billing/types/payment-method.type.ts (1)

1-30: Prefer Stripe’s official types (or compose from them) to avoid drift.

Reduces maintenance and mismatches with upstream API.

Apply this diff if Stripe is available:

+import type Stripe from "stripe";
-export interface PaymentMethod {
-  type: string;
-  id: string;
-  card?: {
-    brand: string | null;
-    last4: string | null;
-    exp_month: number;
-    exp_year: number;
-    funding?: string | null;
-    country?: string | null;
-    network?: string | null;
-    fingerprint?: string | null;
-    three_d_secure_usage?: {
-      supported?: boolean | null;
-    } | null;
-  } | null;
-  billing_details?: {
-    address?: {
-      city: string | null;
-      country: string | null;
-      line1: string | null;
-      line2: string | null;
-      postal_code: string | null;
-      state: string | null;
-    } | null;
-    email?: string | null;
-    name?: string | null;
-    phone?: string | null;
-  };
-}
+export type PaymentMethod = Pick<
+  Stripe.PaymentMethod,
+  "id" | "type" | "card" | "billing_details"
+>;

If Stripe types aren’t desired, consider narrowing unions (brands/networks/funding) to known literals for stronger type safety.

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

11-24: Default type may not align with Stripe payment method types.

faker.finance.transactionType() produces values that likely don’t match Stripe (e.g., “invoice”). Default to "card" or accept an explicit union.

Apply this diff:

-    type: input.type || faker.finance.transactionType(),
+    type: input.type ?? "card",

Optionally tighten the type: type?: "card" | "us_bank_account" | "sepa_debit" | string;

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 436e41a and 3ac2e23.

📒 Files selected for processing (7)
  • apps/api/drizzle/0019_dusty_polaris.sql (1 hunks)
  • apps/api/drizzle/meta/0019_snapshot.json (1 hunks)
  • apps/api/drizzle/meta/_journal.json (1 hunks)
  • apps/api/src/billing/model-schemas/payment-method/payment-method.schema.ts (2 hunks)
  • apps/api/src/billing/repositories/payment-method/payment-method.repository.ts (2 hunks)
  • apps/api/src/billing/types/payment-method.type.ts (1 hunks)
  • apps/api/test/seeders/payment-method.seeder.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

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

Never use type any or cast to type any. Always define the proper TypeScript types.

Files:

  • apps/api/src/billing/types/payment-method.type.ts
  • apps/api/src/billing/model-schemas/payment-method/payment-method.schema.ts
  • apps/api/test/seeders/payment-method.seeder.ts
  • apps/api/src/billing/repositories/payment-method/payment-method.repository.ts
**/*.{js,ts,tsx}

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

**/*.{js,ts,tsx}: Never use deprecated methods from libraries.
Don't add unnecessary comments to the code

Files:

  • apps/api/src/billing/types/payment-method.type.ts
  • apps/api/src/billing/model-schemas/payment-method/payment-method.schema.ts
  • apps/api/test/seeders/payment-method.seeder.ts
  • apps/api/src/billing/repositories/payment-method/payment-method.repository.ts
⏰ 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: test-build
🔇 Additional comments (9)
apps/api/drizzle/0019_dusty_polaris.sql (2)

1-1: Column addition looks good.

NOT NULL with DEFAULT false is safe for backfilling existing rows.


2-2: Create the index CONCURRENTLY and run it outside a transaction.

On large tables plain CREATE INDEX blocks writes; use CONCURRENTLY and ensure the statement is executed outside a transactional migration (add a drizzle stmt-breakpoint before it).

File: apps/api/drizzle/0019_dusty_polaris.sql

Apply this diff:

-ALTER TABLE "payment_methods" ADD COLUMN "is_validated" boolean DEFAULT false NOT NULL;--> statement-breakpoint
-CREATE INDEX IF NOT EXISTS "payment_methods_user_id_is_validated_idx" ON "payment_methods" USING btree ("user_id","is_validated");
+ALTER TABLE "payment_methods" ADD COLUMN "is_validated" boolean DEFAULT false NOT NULL;--> statement-breakpoint
+-- drizzle:stmt-breakpoint
+CREATE INDEX CONCURRENTLY IF NOT EXISTS "payment_methods_user_id_is_validated_idx" ON "payment_methods" USING btree ("user_id","is_validated");

Also consider dropping the single-column user_id index later if it becomes redundant.

apps/api/drizzle/meta/_journal.json (1)

139-144: Journal entry matches migration tag and ordering.

No issues.

apps/api/drizzle/meta/0019_snapshot.json (3)

193-199: Snapshot reflects the new is_validated column as boolean with default false.

Matches the SQL migration.


267-287: Composite index captured correctly.

Good alignment with the intended validated-by-user lookups.


43-50: Note for repository code: user_wallets column is named trial.

Ensure repository checks use trial (boolean) and treat userWallets as an array, not an object.

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

3-3: Import path change looks fine.

No issues.

apps/api/src/billing/repositories/payment-method/payment-method.repository.ts (2)

57-63: Validated lookup aligns with the new composite index.

Good use of the is_validated filter.


65-67: ```shell
#!/bin/bash

Verify payment-method repository and related artifacts

set -euo pipefail

echo "CWD: $(pwd)"
echo "----- locate target file -----"
rg -n --hidden "apps/api/src/billing/repositories/payment-method/payment-method.repository.ts" || true

echo "----- print file (if present) -----"
if [ -f "apps/api/src/billing/repositories/payment-method/payment-method.repository.ts" ]; then
sed -n '1,240p' apps/api/src/billing/repositories/payment-method/payment-method.repository.ts || true
else
echo "File not found at apps/api/src/billing/repositories/payment-method/payment-method.repository.ts"
fi

echo "----- search for updateBy implementations/usages -----"
rg -n --hidden -C3 "updateBy\s*(" || true
rg -n --hidden -C3 "updateBy\s*[:=]\s*async" || true

echo "----- search for repository base classes -----"
rg -n --hidden -C3 "class\s+\w*Repository\b" || true
rg -n --hidden -C3 "BaseRepository|AbstractRepository|RepositoryBase" || true

echo "----- search for is_validated occurrences -----"
rg -n --hidden -C3 "is_validated" || true

echo "----- search for payment_methods/migrations/schema -----"
rg -n --hidden -C3 "payment_methods" || true
rg -n --hidden -C3 "payment-method|payment_method|PaymentMethod" || true

echo "----- search for updatedAt / updated_at conventions -----"
rg -n --hidden -C3 "updatedAt|updated_at" || true

echo "----- check for ORM or db tool indicators -----"
rg -n --hidden -C3 "prisma|TypeORM|knex|sequelize|objection|typeorm|pg" || true

echo "----- show package.json (root and apps/api) -----"
[ -f package.json ] && echo "---- root package.json ----" && sed -n '1,200p' package.json || echo "no root package.json"
[ -f apps/api/package.json ] && echo "---- apps/api/package.json ----" && sed -n '1,200p' apps/api/package.json || echo "no apps/api/package.json"


</blockquote></details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

578-607: Avoid type name collision; use Stripe types or a clear alias.

This local PaymentMethod overlaps conceptually with DB/domain payment method types introduced in this PR. Prefer Stripe.PaymentMethod in this service and remove the local interface to prevent confusion.

Apply this diff:

-  async getPaymentMethods(customerId: string): Promise<PaymentMethod[]> {
+  async getPaymentMethods(customerId: string): Promise<Stripe.PaymentMethod[]> {
     const paymentMethods = await this.paymentMethods.list({
       customer: customerId
     });
     return paymentMethods.data;
   }
-  async hasDuplicateTrialAccount(paymentMethods: PaymentMethod[], currentUserId: string): Promise<boolean> {
+  async hasDuplicateTrialAccount(paymentMethods: Stripe.PaymentMethod[], currentUserId: string): Promise<boolean> {
-export interface PaymentMethod {
-  type: string;
-  id: string;
-  card?: {
-    brand: string | null;
-    last4: string | null;
-    exp_month: number;
-    exp_year: number;
-    funding?: string | null;
-    country?: string | null;
-    network?: string | null;
-    fingerprint?: string | null;
-    three_d_secure_usage?: {
-      supported?: boolean | null;
-    } | null;
-  } | null;
-  billing_details?: {
-    address?: {
-      city: string | null;
-      country: string | null;
-      line1: string | null;
-      line2: string | null;
-      postal_code: string | null;
-      state: string | null;
-    } | null;
-    email?: string | null;
-    name?: string | null;
-    phone?: string | null;
-  };
-}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e8bd7ce and b029cd5.

📒 Files selected for processing (2)
  • apps/api/src/billing/services/stripe/stripe.service.spec.ts (3 hunks)
  • apps/api/src/billing/services/stripe/stripe.service.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

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

Never use type any or cast to type any. Always define the proper TypeScript types.

Files:

  • apps/api/src/billing/services/stripe/stripe.service.ts
  • apps/api/src/billing/services/stripe/stripe.service.spec.ts
**/*.{js,ts,tsx}

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

**/*.{js,ts,tsx}: Never use deprecated methods from libraries.
Don't add unnecessary comments to the code

Files:

  • apps/api/src/billing/services/stripe/stripe.service.ts
  • apps/api/src/billing/services/stripe/stripe.service.spec.ts
**/*.spec.{ts,tsx}

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

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

**/*.spec.{ts,tsx}: Use setup function instead of beforeEach in test files
setup function must be at the bottom of the root describe block in test files
setup function creates an object under test and returns it
setup function should accept a single parameter with inline type definition
Don't use shared state in setup function
Don't specify return type of setup function

Files:

  • apps/api/src/billing/services/stripe/stripe.service.spec.ts
🧬 Code graph analysis (1)
apps/api/src/billing/services/stripe/stripe.service.spec.ts (1)
apps/api/src/billing/services/stripe/stripe.service.ts (1)
  • PaymentMethod (578-607)
⏰ 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: test-build
🔇 Additional comments (4)
apps/api/src/billing/services/stripe/stripe.service.spec.ts (3)

899-909: LGTM! Test correctly updated for plural repository method.

The test properly mocks findOthersByFingerprint to return an array of PaymentMethod objects with the new isValidated field, which aligns with the repository method's updated signature.


914-914: LGTM! Method call updated correctly.

The test correctly calls the updated plural method findOthersByFingerprint.


928-928: LGTM! All test expectations updated consistently.

All test cases have been correctly updated to use the plural method name findOthersByFingerprint and handle the array return type appropriately.

Also applies to: 933-933, 957-957, 962-962, 970-970, 975-975

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

572-572: Ensure duplicates are checked only against validated methods.

Confirm that findOthersByFingerprint filters to is_validated = true. If not, either switch to the validated-only variant or filter in-memory to avoid false positives from unvalidated/seed data.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
apps/api/src/billing/repositories/payment-method/payment-method.repository.ts (1)

26-47: Fix wallet relation shape/field; filter the result; add proper typing.

  • userWallets is a collection; use .some(...).
  • Field is trial, not isTrialing.
  • Return only the trialing matches, not the whole set.
  • Strongly type params and return type for consistency with other methods.
-  async findOthersTrialingByFingerprint(fingerprints: string[], userId: string) {
-    const item = await this.cursor.query.PaymentMethods.findMany({
+  async findOthersTrialingByFingerprint(
+    fingerprints: PaymentMethodOutput["fingerprint"][],
+    userId: PaymentMethodOutput["userId"]
+  ): Promise<PaymentMethodOutput[] | undefined> {
+    const items = await this.cursor.query.PaymentMethods.findMany({
       where: this.whereAccessibleBy(and(inArray(this.table.fingerprint, fingerprints), ne(this.table.userId, userId))),
       with: {
         user: {
           with: {
             userWallets: {
-              columns: {
-                isTrialing: true
-              }
+              columns: { trial: true }
             }
           }
         }
       }
     });
 
-    if (item && item.some(item => item.user?.userWallets?.isTrialing)) {
-      return this.toOutputList(item);
+    const filtered = items?.filter(pm => pm.user?.userWallets?.some(w => w.trial)) ?? [];
+    if (filtered.length) {
+      return this.toOutputList(filtered);
     }
 
     return undefined;
   }
🧹 Nitpick comments (2)
apps/api/src/billing/repositories/payment-method/payment-method.repository.ts (2)

27-39: Push the “trial” predicate into the DB to cut payload.

If supported by your Drizzle relation query, add a nested where (and limit 1) on userWallets so you only hydrate trial wallets.

Example (adjust imports/types as needed):

with: {
  user: {
    with: {
      userWallets: {
        columns: { trial: true },
        // where: eq(UserWallets.trial, true),
        // limit: 1,
      }
    }
  }
}

65-67: Strongly type params for consistency.

Align with other methods’ use of PaymentMethodOutput-derived types.

-  async markAsValidated(paymentMethodId: string, userId: string) {
+  async markAsValidated(
+    paymentMethodId: PaymentMethodOutput["paymentMethodId"],
+    userId: PaymentMethodOutput["userId"]
+  ) {
     return await this.updateBy({ paymentMethodId, userId }, { isValidated: true });
   }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b029cd5 and 426acb4.

📒 Files selected for processing (3)
  • apps/api/src/billing/repositories/payment-method/payment-method.repository.ts (2 hunks)
  • apps/api/src/billing/services/stripe/stripe.service.spec.ts (3 hunks)
  • apps/api/src/billing/services/stripe/stripe.service.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/api/src/billing/services/stripe/stripe.service.ts
  • apps/api/src/billing/services/stripe/stripe.service.spec.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

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

Never use type any or cast to type any. Always define the proper TypeScript types.

Files:

  • apps/api/src/billing/repositories/payment-method/payment-method.repository.ts
**/*.{js,ts,tsx}

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

**/*.{js,ts,tsx}: Never use deprecated methods from libraries.
Don't add unnecessary comments to the code

Files:

  • apps/api/src/billing/repositories/payment-method/payment-method.repository.ts
⏰ 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: test-build
🔇 Additional comments (1)
apps/api/src/billing/repositories/payment-method/payment-method.repository.ts (1)

57-63: Validated query: LGTM.

Matches the new (userId, is_validated) index intent and scopes via accessibleBy.

@codecov
Copy link

codecov bot commented Sep 15, 2025

Codecov Report

❌ Patch coverage is 18.18182% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 44.19%. Comparing base (436e41a) to head (9fe1aed).
⚠️ Report is 2 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...tories/payment-method/payment-method.repository.ts 0.00% 9 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1897      +/-   ##
==========================================
- Coverage   44.59%   44.19%   -0.40%     
==========================================
  Files         979      969      -10     
  Lines       27439    27096     -343     
  Branches     7087     7034      -53     
==========================================
- Hits        12236    11975     -261     
+ Misses      14107    13949     -158     
- Partials     1096     1172      +76     
Flag Coverage Δ *Carryforward flag
api 81.39% <18.18%> (-0.03%) ⬇️ Carriedforward from 1db71e4
deploy-web 22.68% <ø> (-0.01%) ⬇️
log-collector ?
notifications 87.94% <ø> (ø) Carriedforward from 1db71e4
provider-console 81.48% <ø> (ø) Carriedforward from 1db71e4
provider-proxy 84.47% <ø> (ø) Carriedforward from 1db71e4

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

Files with missing lines Coverage Δ
...el-schemas/payment-method/payment-method.schema.ts 85.71% <100.00%> (ø)
.../api/src/billing/services/stripe/stripe.service.ts 92.23% <100.00%> (ø)
...tories/payment-method/payment-method.repository.ts 40.00% <0.00%> (-12.64%) ⬇️

... and 40 files with indirect coverage changes

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
apps/api/src/billing/services/stripe/stripe.service.spec.ts (1)

899-907: Good test data generation using the updated seeder.

The test correctly uses the generatePaymentMethod seeder with card overrides to create test payment methods with fingerprints.

🧹 Nitpick comments (2)
apps/api/test/seeders/payment-method.seeder.ts (2)

5-7: Consider making the PaymentMethodOverrides type more flexible for nested properties.

The current type definition requires consumers to override the entire card object even when they only want to change a single property. This could lead to unintended loss of default values.

Consider using a deep partial type utility for better ergonomics:

-type PaymentMethodOverrides = Omit<Partial<Stripe.PaymentMethod>, "card"> & {
-  card?: Partial<Stripe.PaymentMethod.Card> | null;
-};
+import type { DeepPartial } from "type-fest"; // or implement your own
+
+type PaymentMethodOverrides = DeepPartial<Stripe.PaymentMethod>;

Alternatively, if you don't want to add a dependency, you could at least document that the merge behavior preserves nested defaults.


19-20: Potential issue with hardcoded card brand case mismatch.

The hardcoded brand: "Visa" uses title case, but test expectations use lowercase "visa". This inconsistency could cause test failures if the override doesn't specify the brand.

     card: {
-      brand: "Visa",
+      brand: "visa",
       last4: faker.finance.creditCardNumber().slice(-4),
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 426acb4 and 1db71e4.

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

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

Never use type any or cast to type any. Always define the proper TypeScript types.

Files:

  • apps/api/test/seeders/payment-method.seeder.ts
  • apps/api/src/billing/services/stripe/stripe.service.spec.ts
**/*.{js,ts,tsx}

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

**/*.{js,ts,tsx}: Never use deprecated methods from libraries.
Don't add unnecessary comments to the code

Files:

  • apps/api/test/seeders/payment-method.seeder.ts
  • apps/api/src/billing/services/stripe/stripe.service.spec.ts
**/*.spec.{ts,tsx}

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

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

**/*.spec.{ts,tsx}: Use setup function instead of beforeEach in test files
setup function must be at the bottom of the root describe block in test files
setup function creates an object under test and returns it
setup function should accept a single parameter with inline type definition
Don't use shared state in setup function
Don't specify return type of setup function

Files:

  • apps/api/src/billing/services/stripe/stripe.service.spec.ts
🧬 Code graph analysis (2)
apps/api/test/seeders/payment-method.seeder.ts (3)
apps/api/src/billing/services/stripe/stripe.service.ts (1)
  • PaymentMethod (578-607)
apps/api/src/billing/http-schemas/stripe.schema.ts (1)
  • PaymentMethod (202-202)
packages/http-sdk/src/stripe/stripe.types.ts (1)
  • PaymentMethod (34-44)
apps/api/src/billing/services/stripe/stripe.service.spec.ts (3)
apps/api/test/seeders/payment-method.seeder.ts (1)
  • generatePaymentMethod (9-52)
apps/api/src/billing/services/stripe/stripe.service.ts (1)
  • PaymentMethod (578-607)
packages/http-sdk/src/stripe/stripe.types.ts (1)
  • PaymentMethod (34-44)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: validate / validate-app
  • GitHub Check: test-build
  • GitHub Check: validate / validate-app
🔇 Additional comments (4)
apps/api/test/seeders/payment-method.seeder.ts (1)

51-51: Good use of deep merge for applying overrides.

The use of lodash.merge correctly handles nested object overrides while preserving unspecified nested properties from the base object.

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

799-806: LGTM! Test correctly uses the updated seeder with nested card structure.

The test properly leverages the new generatePaymentMethod seeder to create payment methods with nested card data, aligning with the Stripe API structure.


909-919: LGTM! Test properly handles the new repository method and return type.

The test correctly mocks findOthersTrialingByFingerprint returning an array of PaymentMethod objects with the new isValidated field, aligning with the schema changes in this PR.


949-961: Excellent edge case coverage for payment methods without fingerprints.

The test thoroughly covers scenarios where payment methods have null card or undefined fingerprint, ensuring the filtering logic works correctly.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
apps/api/src/billing/services/stripe/stripe.service.spec.ts (5)

28-42: Fix flakiness: don’t call StripeSeederCreate() inside expectations.

Each call returns new random data, so these assertions will intermittently fail. Reuse the same stripeData from setup.

Apply this diff:

-      const { service, userRepository } = setup();
+      const { service, userRepository, stripeData } = setup();
@@
-      expect(userRepository.updateBy).toHaveBeenCalledWith(
+      expect(userRepository.updateBy).toHaveBeenCalledWith(
         { id: user.id, stripeCustomerId: null },
-        { stripeCustomerId: StripeSeederCreate().customer.id },
+        { stripeCustomerId: stripeData.customer.id },
         { returning: true }
       );
-      expect(result).toEqual(StripeSeederCreate().customer.id);
+      expect(result).toEqual(stripeData.customer.id);

And update setup() to return stripeData (see separate diff).


55-72: Same flakiness here: reuse seeded paymentIntent from setup.

Apply this diff:

-      const { service } = setup();
+      const { service, stripeData } = setup();
@@
-      expect(result).toEqual({
-        success: true,
-        paymentIntentId: StripeSeederCreate().paymentIntent.id
-      });
+      expect(result).toEqual({ success: true, paymentIntentId: stripeData.paymentIntent.id });

1035-1041: Expose stripeData from setup for deterministic assertions.

Apply this diff:

   return {
     service,
     userRepository,
     refillService,
     billingConfig,
-    paymentMethodRepository
+    paymentMethodRepository,
+    stripeData
   };

564-600: Remove as any casts; use proper Stripe types or plain nulls.

Violates repo guideline: never use any in TS files.

Apply these diffs:

-          percent_off: null as any,
+          percent_off: null,
@@
-          amount_off: null as any,
+          amount_off: null,
@@
-      jest.spyOn(service, "findPromotionCodeByCode").mockResolvedValue(mockPromotionCode as any);
+      jest.spyOn(service, "findPromotionCodeByCode").mockResolvedValue(mockPromotionCode as Stripe.PromotionCode);
@@
-          amount_off: null as any,
+          amount_off: null,
@@
-      jest.spyOn(service, "findPromotionCodeByCode").mockResolvedValue(mockPromotionCode as any);
+      jest.spyOn(service, "findPromotionCodeByCode").mockResolvedValue(mockPromotionCode as Stripe.PromotionCode);
@@
-      jest.spyOn(service, "findPromotionCodeByCode").mockResolvedValue(null as any);
+      jest.spyOn(service, "findPromotionCodeByCode").mockResolvedValue(null);
@@
-      jest.spyOn(service, "listCoupons").mockResolvedValue({ coupons: [mockCoupon] } as any);
+      jest.spyOn(service, "listCoupons").mockResolvedValue({ coupons: [mockCoupon] } as { coupons: Stripe.Coupon[] });

Also applies to: 616-621, 635-637, 654-655


985-1043: Fix remaining as any casts in spec files

rg output shows many lingering as any usages across tests — remove or narrow these casts (use proper types/test helpers) and re-run the same check. Examples:

  • apps/notifications/src/modules/alert/services/chain-alert/chain-alert.service.spec.ts (multiple options.callback(alerts as any)),
  • apps/notifications/src/modules/alert/services/deployment-balance-alerts/deployment-balance-alerts.service.spec.ts (await options.callback(alerts as any)),
  • apps/deploy-web/src/lib/nextjs/defineServerSideProps/defineServerSideProps.spec.ts ((result.props as any), config: {} as any),
  • apps/deploy-web/src/services/decodeInjectedConfig/decodeInjectedConfig.spec.ts ((window as any), webcrypto as any),
  • apps/api/src/billing/services/stripe/stripe.service.spec.ts (several mocks cast as any),
  • numerous functional tests under apps/api/test/functional/*.spec.ts (JSON responses cast as any).
♻️ Duplicate comments (1)
apps/api/src/billing/services/stripe/stripe.service.spec.ts (1)

796-808: Nice: switched to seeders for PMs and added DB seeder usage.

This addresses earlier review feedback to centralize fixtures.

Also applies to: 900-918

🧹 Nitpick comments (7)
apps/api/test/seeders/database-payment-method.seeder.ts (2)

13-22: Ensure updatedAt is >= createdAt for temporal consistency.

Generate updatedAt from createdAt to avoid flaky asserts that compare timestamps.

Apply this diff:

 export function generateDatabasePaymentMethod(overrides: Partial<DatabasePaymentMethod> = {}): DatabasePaymentMethod {
-  const basePaymentMethod: DatabasePaymentMethod = {
+  const createdAt = faker.date.recent();
+  const basePaymentMethod: DatabasePaymentMethod = {
     id: faker.string.uuid(),
     userId: faker.string.uuid(),
     fingerprint: faker.string.uuid(),
     paymentMethodId: faker.string.uuid(),
     isValidated: false,
-    createdAt: faker.date.recent(),
-    updatedAt: faker.date.recent()
+    createdAt,
+    updatedAt: new Date(createdAt.getTime() + 1000)
   };

15-19: Consider realistic prefixes for IDs/fingerprint for readability.

Using pm_/fp_ (still random) makes test intent clearer without changing behavior.

Example:

-    fingerprint: faker.string.uuid(),
-    paymentMethodId: faker.string.uuid(),
+    fingerprint: `fp_${faker.string.uuid()}`,
+    paymentMethodId: `pm_${faker.string.uuid()}`,
apps/api/src/billing/services/stripe/stripe.service.spec.ts (5)

401-405: Avoid Array.fromAsync for Node compatibility in CI.

Use a for-await loop; safer across Node versions.

Apply this diff in all three places:

-      const chunks = await Array.fromAsync(csvStream);
-
-      const fullCsv = chunks.join("");
+      const chunks: string[] = [];
+      for await (const chunk of csvStream) chunks.push(chunk);
+      const fullCsv = chunks.join("");

Also applies to: 470-474, 510-513


985-1043: Test setup guideline gaps: location and signature.

  • Setup is outside the root describe; guideline asks for it at the bottom of the root describe block.
  • Signature should accept a single parameter with inline type (even if unused for now).

Proposed minimal change (can be applied later):

-function setup() {
+function setup(_opts: { }: { } = {}) {
  // ...
}

And in a follow-up, move setup inside the outer describe as the last declaration.


186-193: Over-asserting created: undefined couples tests to implementation detail.

Safer to assert subset of params without undefined fields.

Example:

-      expect(service.charges.list).toHaveBeenCalledWith({
+      expect(service.charges.list).toHaveBeenCalledWith({
         customer: "cus_123",
         limit: 50,
-        created: undefined,
-        starting_after: undefined,
+        // created not set
+        // starting_after not set
         ending_before: "ch_before_id",
         expand: ["data.payment_intent"]
       });

Also applies to: 225-236, 356-366


333-341: Optional: add Stripe types to local mocks for stronger compile-time checks.

Annotate mock collections with Stripe.ApiList<Stripe.Charge> or use satisfies to avoid accidental shape drift.

Example:

-      const mockCharges = {
+      const mockCharges = {
         data: [
           { id: "ch_123", amount: 1000, currency: "usd", status: "succeeded", created: 1234567890, payment_method_details: { type: "card" }, receipt_url: "https://receipt.url", description: "Test charge", metadata: {} }
         ],
         has_more: false
-      };
+      } satisfies Stripe.ApiList<Stripe.Charge>;

Also applies to: 780-787, 120-136, 160-178, 196-214, 240-267, 305-323


565-566: You can drop as any on nulls entirely.

null satisfies Stripe’s nullable fields; the extra cast is unnecessary.

No diff added beyond the major fix above.

Also applies to: 597-598, 651-652

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1db71e4 and 9fe1aed.

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

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

Never use type any or cast to type any. Always define the proper TypeScript types.

Files:

  • apps/api/test/seeders/database-payment-method.seeder.ts
  • apps/api/src/billing/services/stripe/stripe.service.spec.ts
**/*.{js,ts,tsx}

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

**/*.{js,ts,tsx}: Never use deprecated methods from libraries.
Don't add unnecessary comments to the code

Files:

  • apps/api/test/seeders/database-payment-method.seeder.ts
  • apps/api/src/billing/services/stripe/stripe.service.spec.ts
**/*.spec.{ts,tsx}

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

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

**/*.spec.{ts,tsx}: Use setup function instead of beforeEach in test files
setup function must be at the bottom of the root describe block in test files
setup function creates an object under test and returns it
setup function should accept a single parameter with inline type definition
Don't use shared state in setup function
Don't specify return type of setup function

Files:

  • apps/api/src/billing/services/stripe/stripe.service.spec.ts
🧬 Code graph analysis (1)
apps/api/src/billing/services/stripe/stripe.service.spec.ts (4)
apps/api/test/seeders/payment-method.seeder.ts (1)
  • generatePaymentMethod (9-52)
apps/api/test/seeders/database-payment-method.seeder.ts (1)
  • generateDatabasePaymentMethod (13-28)
apps/api/src/billing/services/stripe/stripe.service.ts (1)
  • PaymentMethod (578-607)
packages/http-sdk/src/stripe/stripe.types.ts (1)
  • PaymentMethod (34-44)
⏰ 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 (4)
apps/api/test/seeders/database-payment-method.seeder.ts (1)

3-11: Seeder type aligns with migration; good coverage.

Interface shape is clear and matches the new isValidated column.

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

895-924: Duplicate detection tests read well and cover edge-cases.

Good coverage for multiple fingerprints, missing fingerprints, and empty inputs.

Also applies to: 926-943, 944-968, 970-981


1-16: Spec-only note: keep imports strictly typed.

Good use of jest-mock-extended; no global jest.mock() usage.


555-585: Good rollback test scenario.

Verifies compensation when wallet top-up fails.

@baktun14 baktun14 merged commit 1cffbf6 into main Sep 15, 2025
62 of 77 checks passed
@baktun14 baktun14 deleted the features/billing-database-schema branch September 15, 2025 12:55
stalniy pushed a commit that referenced this pull request Nov 20, 2025
…validation (#1897)

* feat(billing): add database schema and core types for payment method validation

- Add database migration for payment method changes
- Add new payment method types and schemas
- Update payment method repository with validation support
- Update payment method seeder for testing

* refactor(billing): rename is_validated to isValidated

* fix(billing): find others by fingerprint

* fix(billing): rename function

* fix(billing): fix types for seeder payments

* fix(billing): payment method seeder
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments