Skip to content

verify payment transactions integrity#1128

Merged
BilalG1 merged 18 commits intodevfrom
verify-payment-transactions
Jan 27, 2026
Merged

verify payment transactions integrity#1128
BilalG1 merged 18 commits intodevfrom
verify-payment-transactions

Conversation

@BilalG1
Copy link
Contributor

@BilalG1 BilalG1 commented Jan 22, 2026

Summary by CodeRabbit

  • New Features
    • Adds a payments data-integrity verifier, Stripe payout reconciliation, API response validation helpers, and a throttled progress utility for long-running checks.
  • Bug Fixes
    • Subscription/product filtering during verification now respects customer type.
  • Chores
    • Reorganized verification scripts, updated verification entrypoint, and added mock-Stripe detection to skip real Stripe checks in mock mode.
  • Tests
    • Enhanced test fixtures to include full product data for subscriptions.

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

@vercel
Copy link

vercel bot commented Jan 22, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
stack-backend Ready Ready Preview, Comment Jan 27, 2026 8:49pm
stack-dashboard Ready Ready Preview, Comment Jan 27, 2026 8:49pm
stack-demo Ready Ready Preview, Comment Jan 27, 2026 8:49pm
stack-docs Ready Ready Preview, Comment Jan 27, 2026 8:49pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 22, 2026

📝 Walkthrough

Walkthrough

Adds tenancy-aware verify-data-integrity tooling: new API helpers, a payments verifier that reconstructs ledgers from transactions, Stripe payout integrity checks, a recursive progress helper, tighter product filtering in payments code, a script entrypoint update, and richer test fixtures for subscriptions.

Changes

Cohort / File(s) Summary
Payments filtering & tests
apps/backend/src/lib/payments.tsx, apps/backend/src/lib/payments.test.tsx
getSubscriptions now reads subscription.product directly (removing fallback) and filters products by product.customerType === options.customerType; tests updated to include product objects on mocked subscriptions.
Package script
apps/backend/package.json
verify-data-integrity script path changed from scripts/verify-data-integrity.ts to scripts/verify-data-integrity/index.ts.
API verification helpers
apps/backend/scripts/verify-data-integrity/api.ts
New createApiHelpers() providing appendOutputData and expectStatusCode; adds types EndpointOutput, OutputData, ExpectStatusCode and detailed StackAssertionError reporting.
Top-level verify script
apps/backend/scripts/verify-data-integrity/index.ts
Refactored to tenancy-aware flow: uses createApiHelpers() and createRecurse(), resolves tenancy per project/branch, detects mock-Stripe mode, conditionally runs Stripe payout checks, and invokes payments verifier per customer.
Payments verification engine
apps/backend/scripts/verify-data-integrity/payments-verifier.ts
New createPaymentsVerifier() that reconstructs ledgers from transactions, computes expected item quantities and owned products, loads related DB records, and exposes verifyCustomerPayments() plus customCustomerIds; throws detailed assertion errors on mismatches.
Recursive progress helper
apps/backend/scripts/verify-data-integrity/recurse.ts
New createRecurse() factory and RecurseFunction type for throttled progress reporting and nested recursion support.
Stripe payout verification
apps/backend/scripts/verify-data-integrity/stripe-payout-integrity.ts
New fetchAllTransactionsForProject() and verifyStripePayoutIntegrity() to paginate/sum internal USD transfers, fetch Stripe balance transactions, compare totals, and throw on mismatch.

Sequence Diagram(s)

sequenceDiagram
    participant Index as verify-data-integrity/index.ts
    participant Recurse as Recurse Helper
    participant Verifier as Payments Verifier
    participant DB as Prisma DB
    participant API as Stack API
    participant Stripe as Stripe API

    Index->>Recurse: createRecurse()
    Index->>API: createApiHelpers(currentOutputData, targetOutputData?)
    Index->>Verifier: createPaymentsVerifier(projectId, tenancy, paymentsConfig, prisma, expectStatusCode)
    Index->>Verifier: for each customer -> verifyCustomerPayments(customer)
    Verifier->>DB: fetch transactions, subscriptions, purchases, item changes
    Verifier->>Verifier: build expected ledger & owned products
    Verifier->>API: expectStatusCode(customer item-quantities endpoint)
    API-->>Verifier: return current item quantities
    Verifier->>Verifier: compare expected vs actual
    alt mismatch
        Verifier->>DB: fetch detailed records for diagnostics
        DB-->>Verifier: return records
        Verifier-->>Index: throw StackAssertionError
    else match
        Verifier->>API: expectStatusCode(owned-products endpoint)
        API-->>Verifier: return owned products
        Verifier->>Verifier: compare expected owned products
    end
    Index->>API: fetch project transaction totals
    Index->>Stripe: fetch Stripe balance transactions (account)
    Stripe-->>Index: return balance totals
    Index->>Index: compare totals, throw on mismatch
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested reviewers

  • N2D4

Poem

🐰 Hopping through ledgers, I tally each line,
Tenancy branches and Stripe cents align,
I fetch, I compare with a twitch of my nose,
Alert if a penny or product misgoes,
Tiny paws, keen eyes — the verifier knows.

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is empty except for the contribution template header and provides no meaningful context about the changes, implementation details, or rationale. Add a detailed description explaining the purpose of the payment transaction integrity verification, what was changed, why these changes were made, and any relevant context for reviewers.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main objective of the changeset: adding payment transaction integrity verification functionality.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@BilalG1 BilalG1 marked this pull request as ready for review January 26, 2026 17:38
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 26, 2026

Greptile Overview

Greptile Summary

This PR adds comprehensive payment transaction verification to the data integrity script and fixes a critical customerType filtering bug in the payments library.

Key Changes:

  • Added Stripe balance transaction reconciliation that compares internal money transfers with Stripe's balance transactions to detect discrepancies
  • Implemented customer payment verification for users, teams, and custom customers, validating item quantities and owned products against transaction ledger
  • Added verification that skips projects using mock Stripe API to avoid false positives in testing environments
  • Fixed bug in getSubscriptions where default products weren't filtered by customerType, potentially returning products meant for different customer types
  • Added handling for include-by-default conflicts by skipping payment verification when multiple default products exist in the same product line

Bug Fix:
The fix in payments.tsx ensures that when retrieving default products for product lines, only products matching the requested customerType are included. Without this filter, a user could receive team-only products and vice versa.

Confidence Score: 4/5

  • Safe to merge with minor non-critical improvements suggested
  • The PR adds comprehensive payment verification infrastructure to detect data integrity issues. The bug fix in payments.tsx is straightforward and correct. The verification logic is extensive but follows existing patterns in the codebase. Main concern is the hardcoded project ID check which is a minor style issue rather than a functional problem.
  • No files require special attention

Important Files Changed

Filename Overview
apps/backend/scripts/verify-data-integrity.ts Added comprehensive payment transaction verification logic including Stripe balance reconciliation, customer payment validation, and product/item quantity checks
apps/backend/src/lib/payments.tsx Fixed customerType filtering bug in getSubscriptions function when retrieving default products

Sequence Diagram

sequenceDiagram
    participant Script as verify-data-integrity.ts
    participant DB as Database
    participant API as Stack API
    participant Stripe as Stripe API
    
    Script->>DB: Fetch projects with stripeAccountId
    loop For each project
        Script->>API: Get tenancy config
        
        alt Has payments config
            Script->>Script: createPaymentsVerifier()
            Script->>API: fetchAllTransactionsForProject()
            Script->>DB: Fetch subscriptions, purchases, item changes
            
            alt Stripe account exists & not mock
                Script->>Script: verifyStripePayoutIntegrity()
                Script->>API: Get all money_transfer transactions
                Script->>Stripe: Fetch balance transactions
                Script->>Script: Compare totals (must match)
            end
            
            loop For each user
                Script->>Script: verifyCustomerPayments()
                Script->>Script: buildExpectedItemQuantitiesForCustomer()
                Script->>API: Get actual item quantities
                Script->>Script: Compare expected vs actual
                Script->>API: Get owned products
                Script->>Script: Compare expected vs actual products
            end
            
            loop For each team
                Script->>Script: verifyCustomerPayments()
                Note over Script: Same verification as users
            end
            
            loop For each custom customer
                Script->>Script: verifyCustomerPayments()
                Note over Script: Same verification as users
            end
        end
    end
Loading

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

1 file reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

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

🤖 Fix all issues with AI agents
In `@apps/backend/scripts/verify-data-integrity.ts`:
- Around line 1127-1139: The query that builds extraItemQuantityChanges omits
customerType, causing cross-type matches; update the itemQuantityChange.findMany
where clause to include customerType: customer.customerType (or the appropriate
property from the customer object) alongside tenancyId and customerId so it
matches how getCustomerKey uniquely identifies customers; ensure the filter uses
the same customerType enum/string shape as other queries.
🧹 Nitpick comments (4)
apps/backend/scripts/verify-data-integrity.ts (4)

19-20: Clarify that this is a sentinel value, not a real key.

The static analysis tool flagged this as a potential Stripe access token. While this is a false positive (it's a sentinel value for mock mode detection), adding a comment will prevent future confusion and suppress tooling warnings.

📝 Suggested clarification
 const STRIPE_SECRET_KEY = getEnvVariable("STACK_STRIPE_SECRET_KEY", "");
+// Sentinel value used to detect mock mode - not a real API key
 const USE_MOCK_STRIPE_API = STRIPE_SECRET_KEY === "sk_test_mockstripekey";

493-557: Significant code duplication with payments.tsx.

computeLedgerBalanceAtNow (lines 493-533) and addWhenRepeatedItemWindowTransactions (lines 535-557) are nearly identical to their counterparts in apps/backend/src/lib/payments.tsx (lines 119-183). This creates a maintenance burden where changes must be synchronized across both files.

Consider extracting these functions to a shared utility module that both files can import.


957-960: Avoid hardcoding project IDs.

This hardcoded UUID is fragile and difficult to maintain. If the dummy project ID changes or additional projects need to be skipped, this requires a code change.

Consider using project metadata or a configuration flag to mark projects that should skip Stripe verification.

📝 Suggested approach
-  if (options.projectId === '6fbbf22e-f4b2-4c6e-95a1-beab6fa41063') {
-    // Dummy project doesn't have a real stripe account, so we skip the verification.
-    return;
-  }
+  // Skip projects without valid Stripe accounts (e.g., dummy/test projects)
+  // Consider adding a flag to project metadata or configuration instead of hardcoding
+  const SKIP_STRIPE_VERIFICATION_PROJECT_IDS = new Set([
+    '6fbbf22e-f4b2-4c6e-95a1-beab6fa41063', // Dummy project
+  ]);
+  if (SKIP_STRIPE_VERIFICATION_PROJECT_IDS.has(options.projectId)) {
+    return;
+  }

Better long-term: Check for a project flag like project.skipStripeVerification or verify that the Stripe account is properly configured before running verification.


29-29: Consider importing or aliasing the Prisma CustomerType if applicable.

This local CustomerType definition includes "custom" which may differ from the Prisma-generated enum. If there's a specific reason for the local definition (e.g., the verification script needs to handle additional customer types), a brief comment explaining the divergence would improve clarity.

@BilalG1 BilalG1 requested a review from N2D4 January 26, 2026 20:25
@BilalG1 BilalG1 assigned N2D4 and unassigned BilalG1 Jan 26, 2026
Copy link
Contributor

@N2D4 N2D4 left a comment

Choose a reason for hiding this comment

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

Can we split this up into multiple files? It's getting a bit much

(asking AI to do it should prolly be enough)

@github-actions github-actions bot assigned BilalG1 and unassigned N2D4 Jan 27, 2026
Copy link

@vercel vercel bot left a comment

Choose a reason for hiding this comment

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

Additional Suggestion:

Missing null check before calling .map() on a potentially undefined object property will cause a TypeError if the output file is missing the /api/v1/internal/projects/current endpoint.

View Details
📝 Patch Details
diff --git a/apps/backend/scripts/verify-data-integrity/index.ts b/apps/backend/scripts/verify-data-integrity/index.ts
index d9da13e5..97154d8f 100644
--- a/apps/backend/scripts/verify-data-integrity/index.ts
+++ b/apps/backend/scripts/verify-data-integrity/index.ts
@@ -97,17 +97,19 @@ async function main() {
 
       // TODO next-release these are hacks for the migration, delete them
       if (targetOutputData) {
-        targetOutputData["/api/v1/internal/projects/current"] = targetOutputData["/api/v1/internal/projects/current"].map(output => {
-          if ("config" in output.responseJson) {
-            delete output.responseJson.config.id;
-            output.responseJson.config.oauth_providers = output.responseJson.config.oauth_providers
-              // `any` because this is historical output JSON from disk.
-              // We intentionally keep this "migration hack" untyped.
-              .filter((provider: any) => provider.enabled)
-              .map((provider: any) => omit(provider, ["enabled"]));
-          }
-          return output;
-        });
+        if (targetOutputData["/api/v1/internal/projects/current"]) {
+          targetOutputData["/api/v1/internal/projects/current"] = targetOutputData["/api/v1/internal/projects/current"].map(output => {
+            if ("config" in output.responseJson) {
+              delete output.responseJson.config.id;
+              output.responseJson.config.oauth_providers = output.responseJson.config.oauth_providers
+                // `any` because this is historical output JSON from disk.
+                // We intentionally keep this "migration hack" untyped.
+                .filter((provider: any) => provider.enabled)
+                .map((provider: any) => omit(provider, ["enabled"]));
+            }
+            return output;
+          });
+        }
       }
 
       console.log(`Loaded previous output data for verification`);

Analysis

Missing null check on endpoint property before calling .map() in verify-data-integrity script

What fails: Line 100 in apps/backend/scripts/verify-data-integrity/index.ts calls .map() on targetOutputData["/api/v1/internal/projects/current"] without checking if this property exists. If the output file (loaded from OUTPUT_FILE_PATH) is missing the /api/v1/internal/projects/current endpoint key, accessing it returns undefined, and calling .map() on undefined throws: TypeError: Cannot read properties of undefined (reading 'map')

How to reproduce:

  1. Generate an output file with --save-output that doesn't include the /api/v1/internal/projects/current endpoint (e.g., a partial or legacy output file)
  2. Run the script with --verify-output flag
  3. The script throws TypeError: Cannot read properties of undefined (reading 'map')

Result: Script terminates with unhandled TypeError when verifying incomplete output files

Expected: Script should gracefully handle output files that don't contain all endpoints, skipping migration transformations for missing endpoints

Fix implemented: Added guard condition if (targetOutputData["/api/v1/internal/projects/current"]) before the .map() call to check if the property exists before processing it

Fix on Vercel

@BilalG1 BilalG1 requested a review from N2D4 January 27, 2026 18:31
@BilalG1 BilalG1 assigned N2D4 and unassigned BilalG1 Jan 27, 2026
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

🤖 Fix all issues with AI agents
In `@apps/backend/src/lib/payments.tsx`:
- Around line 334-336: The code currently treats s.product as possibly undefined
(const product = s.product as yup.InferType<typeof productSchema> | undefined)
and simply skips pushing that subscription, which can silently drop
entitlements; instead update the logic in the loop that builds subscriptions to
(a) attempt to reconstruct the product from your canonical config using the
subscription's productId when product is undefined, and only if reconstruction
fails throw an explicit error (or log and throw) rather than continue; adjust
the block that references product, productSchema, s.product, productId and the
subscriptions.push call so missing product data is either rebuilt from config or
causes a loud failure.

Copy link
Contributor

@N2D4 N2D4 left a comment

Choose a reason for hiding this comment

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

looks great! let's wait a few hours with merging tho until after the Swift SDK if that's okay? anything after 4pm today should be good

@github-actions github-actions bot assigned BilalG1 and unassigned N2D4 Jan 27, 2026
@BilalG1 BilalG1 enabled auto-merge (squash) January 27, 2026 20:01
@BilalG1 BilalG1 merged commit e439bd0 into dev Jan 27, 2026
30 of 31 checks passed
@BilalG1 BilalG1 deleted the verify-payment-transactions branch January 27, 2026 21:17
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