Skip to content

fix(cloud): persist Stripe Connect capability booleans so fiat payouts aren't permanently rejected (#11172) - #11190

Merged
lalalune merged 1 commit into
developfrom
nubs/stripe-connect-persist-payouts-enabled
Jul 2, 2026
Merged

lalalune merged 1 commit into
developfrom
nubs/stripe-connect-persist-payouts-enabled

Conversation

@NubsCarson

Copy link
Copy Markdown
Member

Closes #11172.

Problem (HIGH — launch-blocking for the fiat payout rail)

The Connect account.updated webhook derived status from the capability booleans but dropped the booleans. payouts_enabled defaults false (migration 0150) and the payout transfer gate reads it directly:

// transfer/route.ts
if (account.status !== "active" || !account.payouts_enabled)  reject

So a creator fully onboards → Stripe fires account.updated (charges_enabled=true, payouts_enabled=true) → we stored status='active' but left payouts_enabled=false forever → every Stripe Connect fiat payout permanently rejected, with no recovery path. (On-chain hot-wallet payouts are unaffected — this is the Stripe fiat rail specifically.)

Fix (option a — keep the column truthful)

mapConnectWebhookEvent now returns the raw chargesEnabled/payoutsEnabled from account.updated, and the webhook route persists them alongside status via updateByAccountId (which already accepts charges_enabled?/payouts_enabled?). The column reflects reality: true when Stripe reports the caps, false when it doesn't.

await stripeConnectAccountsRepository.updateByAccountId(outcome.accountId, {
  ...(outcome.status ? { status: outcome.status } : {}),
  ...(outcome.chargesEnabled !== undefined ? { charges_enabled: outcome.chargesEnabled } : {}),
  ...(outcome.payoutsEnabled !== undefined ? { payouts_enabled: outcome.payoutsEnabled } : {}),
});

Chose (a) over (b) (dropping the redundant gate check) so the persisted column stays accurate for any other reader, not just the transfer gate.

Tests

  • stripe-connect-payout.test.ts: mapper surfaces the booleans (both true → also active; charges-true/payouts-false → surfaced truthfully).
  • stripe-connect-webhook-route.test.ts: the account.updated persist now asserts { status: "active", charges_enabled: true, payouts_enabled: true } — the regression guard (previously { status: "active" } alone, which is exactly the bug).
bun test packages/cloud/shared/src/lib/services/stripe-connect-payout.test.ts            # 12 pass
bun test packages/cloud/api/__tests__/stripe-connect-webhook-route.test.ts               # 13 pass

packages/cloud/shared typecheck + packages/cloud/api build + biome all clean.

Evidence

  • The write/read contract is proven by the route test asserting the exact persisted patch (the payout gate then reads a true column). No mock stands in for the persisted value.
  • N/A (no UI / no new migration / no model): the column + repo method already exist; this wires the webhook to write them.

Money-path — flagging for maintainer review/merge rather than self-merge. [cloud-security]

…s aren't permanently rejected (#11172)

The Connect webhook derived `status` from the account.updated capability booleans
but DROPPED the booleans themselves. `payouts_enabled` defaults false (migration
0150) and the payout transfer gate reads it directly
(transfer/route.ts: `if (account.status !== "active" || !account.payouts_enabled)`),
so after a creator fully onboards (Stripe sends charges_enabled=true,
payouts_enabled=true) we stored status='active' but left payouts_enabled=false
forever → every Stripe Connect fiat payout permanently rejected, no recovery
path. Launch-blocking for the fiat payout rail.

Fix (option a — keep the column truthful): mapConnectWebhookEvent now returns the
raw chargesEnabled/payoutsEnabled from account.updated, and the webhook route
persists them alongside status via updateByAccountId (which already accepts the
columns). The column now reflects reality — true when Stripe reports the caps,
false when it doesn't.

Tests:
- stripe-connect-payout.test.ts: mapper surfaces the booleans (both true → also
  active; charges true / payouts false → surfaced truthfully).
- stripe-connect-webhook-route.test.ts: the account.updated persist now asserts
  { status: "active", charges_enabled: true, payouts_enabled: true } — the
  regression guard (was { status: "active" } alone, which left the column false).

Money-path — flagging for maintainer review/merge. [cloud-security]

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Your trial has ended. Reactivate Greptile to resume code reviews.

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7d9e4835-d761-49f8-a232-b7c7d31cd561

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch nubs/stripe-connect-persist-payouts-enabled

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.

@lalalune
lalalune merged commit 082d726 into develop Jul 2, 2026
35 of 43 checks passed
@lalalune
lalalune deleted the nubs/stripe-connect-persist-payouts-enabled branch July 2, 2026 05:30
NubsCarson added a commit that referenced this pull request Jul 2, 2026
…2400s install timeout (#10839)

Prod deploys run from main (production environment allows only the main
branch), but main cloud-cf-deploy still had the install-hang config: bun
canary + install cache under $PWD (box /tmp, slow FS) + 900s per-attempt cap.
Every money-integrity-wave prod deploy hung on bun install. Ports the three
fixes already merged to develop (#11235/#11268/#11304): pin bun to latest,
put the install cache on local $HOME (persistent/warm), and raise the
per-attempt timeout to 2400s so the slow-but-completing install finishes.

With this on main, a main-push deploy installs, then migrate-db runs
(branch=main satisfies the production environment policy) pending the
required-reviewer approval, then Worker/Pages deploy. Ships the merged money
fixes (#11190 payout gate, #11189 cron, escrow, #11163 launch gate) to prod.
NubsCarson added a commit that referenced this pull request Jul 2, 2026
…2400s install timeout (#10839) (#11314)

Prod deploys run from main (production environment allows only the main
branch), but main cloud-cf-deploy still had the install-hang config: bun
canary + install cache under $PWD (box /tmp, slow FS) + 900s per-attempt cap.
Every money-integrity-wave prod deploy hung on bun install. Ports the three
fixes already merged to develop (#11235/#11268/#11304): pin bun to latest,
put the install cache on local $HOME (persistent/warm), and raise the
per-attempt timeout to 2400s so the slow-but-completing install finishes.

With this on main, a main-push deploy installs, then migrate-db runs
(branch=main satisfies the production environment policy) pending the
required-reviewer approval, then Worker/Pages deploy. Ships the merged money
fixes (#11190 payout gate, #11189 cron, escrow, #11163 launch gate) to prod.
@claude

claude Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Claude encountered an error —— View job


I'll analyze this and get back to you.

@github-actions github-actions Bot added the Tests label Jul 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

security/money: Stripe Connect payouts_enabled never persisted → every fiat payout permanently rejected (HIGH, launch)

2 participants