Skip to content

[codex] add Stripe customer export scripts#2206

Merged
riderx merged 5 commits into
mainfrom
codex/stripe-customer-exports
May 11, 2026
Merged

[codex] add Stripe customer export scripts#2206
riderx merged 5 commits into
mainfrom
codex/stripe-customer-exports

Conversation

@riderx
Copy link
Copy Markdown
Member

@riderx riderx commented May 11, 2026

Summary (AI generated)

  • Adds a Stripe CSV export for org member emails from customers with at least six months of paid invoice coverage.
  • Adds a Stripe CSV export for customers that are not attached to any Capgo org, with filters for paid, active, canceled, never-paid, or all customers.
  • Shares Stripe paid-invoice coverage helpers between both scripts and wires package commands.

Motivation (AI generated)

  • The team needs operational CSV exports to identify long-running paid customers and Stripe customers that are not mapped to orgs.

Business Impact (AI generated)

  • Helps customer and revenue workflows by making it easier to identify users in paid orgs and billing records that may need cleanup or outreach.
  • Reduces manual Stripe and database reconciliation work.

Test Plan (AI generated)

  • bun run stripe:export-paid-customers-without-org --help
  • bun run stripe:export-six-month-org-emails --help
  • bun --bun tsc --noEmit --ignoreConfig --skipLibCheck --allowImportingTsExtensions --moduleResolution bundler --module ESNext --target ESNext scripts/export_stripe_six_month_org_emails.ts scripts/export_stripe_paid_customers_without_org.ts scripts/stripe_paid_invoice_export_utils.ts
  • bunx eslint --no-ignore scripts/export_stripe_six_month_org_emails.ts scripts/export_stripe_paid_customers_without_org.ts scripts/stripe_paid_invoice_export_utils.ts
  • git diff --check
  • bun typecheck (via pre-commit hook)

Live exports were not run because they call production Stripe and Supabase.

Summary by CodeRabbit

  • Style

    • Updated sidebar navigation button styling for consistent sizing.
  • Improvements

    • Replication status now requires a Supabase session token (uses authenticated requests only).
  • Chores

    • Added new admin export tools and npm scripts to generate CSVs of Stripe-paid customer data and org member emails, including paid-status analysis and CSV export helpers.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 11, 2026

Warning

Rate limit exceeded

@riderx has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 1 minute and 38 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c06fc103-a112-4295-a91d-335b3ad28cc6

📥 Commits

Reviewing files that changed from the base of the PR and between bc23fbb and 649f9c4.

📒 Files selected for processing (2)
  • .github/workflows/tests.yml
  • package.json
📝 Walkthrough

Walkthrough

Adds a paid-invoice coverage utility and two Bun TypeScript CLI exports (six-month org member emails and paid customers without org), registers npm scripts, and updates sidebar height and replication fetching to require a Supabase session token.

Changes

Stripe Data Export Scripts

Layer / File(s) Summary
Paid Coverage Utility
scripts/stripe_paid_invoice_export_utils.ts
Defines PaidInterval, CustomerPaidCoverage, CustomerPaidSummary. Extracts paid intervals from invoices, accumulates per-customer coverage with Stripe pagination, merges intervals, computes durations (ms → months), and adds CSV escaping/writing helpers.
Six-Month Org Member Emails Export
scripts/export_stripe_six_month_org_emails.ts
CLI that pages actionable orgs from Supabase, groups orgs by Stripe customer, derives qualifying org ids from paid coverage summaries, fetches org user rows and active role bindings in DB chunks, normalizes user emails, aggregates unique emails per org, and writes a CSV of emails with paid-duration metadata.
Paid Customers Without Org Export
scripts/export_stripe_paid_customers_without_org.ts
CLI that collects org-associated Stripe customer IDs, computes paid coverage, optionally enumerates Stripe customers without orgs, builds synthetic never-paid summaries when needed, concurrently fetches customer profiles, sorts rows by billing status/active/duration, and writes CSV output.
NPM Script Registration
package.json
Adds stripe:export-six-month-org-emails and stripe:export-paid-customers-without-org scripts invoking the Bun TypeScript entrypoints.

UI Styling and Replication Auth

Layer / File(s) Summary
Sidebar Navigation Button
src/components/Sidebar.vue
Updates navigation button minimum-height Tailwind class from min-h-[44px] to min-h-11.
Replication Dashboard
src/pages/admin/dashboard/replication.vue
Removes fallback to internal API secret; replication fetch now requires Supabase session and sends only Authorization: Bearer <access_token> header.

Sequence Diagram(s)

sequenceDiagram
  participant CLI as Export CLI
  participant Supabase as Supabase DB
  participant Stripe as Stripe API
  participant Util as PaidUtil
  participant FS as FileSystem
  CLI->>Supabase: fetch actionable orgs / org user rows / role_bindings
  CLI->>Stripe: list customers / invoices (paginated)
  Stripe->>Util: invoice records
  Util->>CLI: CustomerPaidSummary (active, duration)
  CLI->>Stripe: fetch customer profiles (concurrent)
  CLI->>CLI: build CSV rows (merge coverage + emails/profiles)
  CLI->>FS: writeCsv (output file)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Cap-go/capgo#2202: Modifies the same frontend files (src/components/Sidebar.vue, src/pages/admin/dashboard/replication.vue) with similar styling and replication-auth changes.

"🐰 Two scripts hop into the warren,
Counting invoices by moonlit code,
Emails gathered, customers freed,
CSV carrots in a tidy row,
Hooray — the rabbit hops back home!"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ 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%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: adding Stripe customer export scripts, which aligns with the primary focus of the changeset.
Description check ✅ Passed The description includes a comprehensive Summary section, detailed Test Plan with checked items, and optional sections like Motivation and Business Impact, though it omits the required Checklist section.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/stripe-customer-exports

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

@riderx riderx marked this pull request as ready for review May 11, 2026 13:16
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Copy link
Copy Markdown
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: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/export_stripe_paid_customers_without_org.ts`:
- Around line 164-167: The current asyncPool task uses stripe.customers.retrieve
directly so a single 404 or transient error aborts the whole export; create a
helper function (e.g., fetchCustomerProfile) that wraps
stripe.customers.retrieve in a try/catch, logs the failure with the customerId,
and returns a safe empty StripeCustomerProfile ({ deleted: false, email: null,
name: null }) on error, then replace the inline call in the asyncPool loop
(where summaries are processed) to call fetchCustomerProfile and set
profilesByCustomerId with getCustomerProfile(awaited result) or the safe
fallback; also reuse the same helper in the scoped --customer-id retrieval path
(the code around the current stripe.customers.retrieve usage at lines ~183-187).

In `@scripts/stripe_paid_invoice_export_utils.ts`:
- Around line 281-284: The writeCsv function currently writes PII to disk
without forcing file permissions; change it so the created CSV file is only
readable/writable by the owner by specifying a file mode of 0o600 when
creating/writing the file (e.g., pass mode: 0o600 to writeFile or set chmod
immediately after writeFile) while continuing to create parent dirs with mkdir({
recursive: true }); update the writeCsv function to use this mode for the output
file so emails/names aren’t world-readable.
- Around line 274-279: escapeCsv currently only quotes values but doesn't
neutralize spreadsheet formulas; update escapeCsv to detect when the string form
of the value begins with =, +, -, or @ (case of external inputs like email/name)
and prepend a neutralizing character (e.g., a leading single quote) before
performing the existing quote/escape logic in escapeCsv so spreadsheets won't
evaluate the cell; keep existing behavior for numbers/booleans/null and ensure
you still replace internal '"' with '""' and wrap in double quotes when
necessary.

In `@src/pages/admin/dashboard/replication.vue`:
- Around line 111-112: Update the stale error message thrown when the session
lacks an access token: locate the check using session?.access_token (the if
(!session?.access_token) throw new Error(...)) and replace the message that
references "replication secret" with a concise, accurate text such as "No
session available; access token required" (or similar) so it reflects the
current behavior where the secret fallback is no longer used.
- Line 108: The error message referencing a "replication secret" is stale—update
the authentication failure handling where useSupabase() and
supabase.auth.getSession() are used (look for the block that calls
supabase.auth.getSession() and throws/logs the replication secret error) to
instead log/throw a message like "Unable to retrieve user session" or similar
that reflects session token auth; also replace the non-standard Tailwind class
"min-h-75" (found in the template markup near the dashboard layout) with a valid
class such as "min-h-72" or "min-h-80" (or add a custom spacing token in
tailwind config if 75 is required).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 852e10b4-e5ea-4469-a698-70781c00f0e5

📥 Commits

Reviewing files that changed from the base of the PR and between 7709b52 and 6e8fff9.

📒 Files selected for processing (6)
  • package.json
  • scripts/export_stripe_paid_customers_without_org.ts
  • scripts/export_stripe_six_month_org_emails.ts
  • scripts/stripe_paid_invoice_export_utils.ts
  • src/components/Sidebar.vue
  • src/pages/admin/dashboard/replication.vue

Comment thread scripts/export_stripe_paid_customers_without_org.ts
Comment thread scripts/stripe_paid_invoice_export_utils.ts
Comment thread scripts/stripe_paid_invoice_export_utils.ts Outdated
Comment thread src/pages/admin/dashboard/replication.vue
Comment thread src/pages/admin/dashboard/replication.vue Outdated
Copy link
Copy Markdown
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.

Caution

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

⚠️ Outside diff range comments (1)
src/pages/admin/dashboard/replication.vue (1)

219-221: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Incorrect null check for maxLagMinutes.

The maxLagMinutes computed property (lines 86-94) returns undefined in fallback cases, never null. The strict equality check === null will always be false when the value is undefined, causing the unit to display 'min' even when there's no meaningful value.

🐛 Proposed fix
             <AdminStatsCard
               title="Max lag"
               :value="maxLagMinutes"
-              :unit="maxLagMinutes === null ? '' : 'min'"
+              :unit="maxLagMinutes == null ? '' : 'min'"
               :subtitle="maxLagSlot"
             />
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/admin/dashboard/replication.vue` around lines 219 - 221, The
template uses a strict null check when deciding the unit (:unit="maxLagMinutes
=== null ? '' : 'min'") but the computed property maxLagMinutes returns
undefined in fallback cases, so the condition never matches; update the
conditional to check for both null and undefined (e.g., use maxLagMinutes ==
null or explicitly check === undefined) in the :unit binding so the unit is
blank when maxLagMinutes is absent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/pages/admin/dashboard/replication.vue`:
- Around line 219-221: The template uses a strict null check when deciding the
unit (:unit="maxLagMinutes === null ? '' : 'min'") but the computed property
maxLagMinutes returns undefined in fallback cases, so the condition never
matches; update the conditional to check for both null and undefined (e.g., use
maxLagMinutes == null or explicitly check === undefined) in the :unit binding so
the unit is blank when maxLagMinutes is absent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b8c51225-e373-4b14-89ef-8f8a6e57c568

📥 Commits

Reviewing files that changed from the base of the PR and between 6e8fff9 and bc23fbb.

📒 Files selected for processing (3)
  • scripts/export_stripe_paid_customers_without_org.ts
  • scripts/stripe_paid_invoice_export_utils.ts
  • src/pages/admin/dashboard/replication.vue
🚧 Files skipped from review as they are similar to previous changes (2)
  • scripts/export_stripe_paid_customers_without_org.ts
  • scripts/stripe_paid_invoice_export_utils.ts

@codspeed-hq
Copy link
Copy Markdown
Contributor

codspeed-hq Bot commented May 11, 2026

Merging this PR will not alter performance

✅ 43 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing codex/stripe-customer-exports (649f9c4) with main (ee54a9c)

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@sonarqubecloud
Copy link
Copy Markdown

@riderx riderx merged commit 8c7653d into main May 11, 2026
45 checks passed
@riderx riderx deleted the codex/stripe-customer-exports branch May 11, 2026 17:28
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.

1 participant