Skip to content

Pay developers on Stripe recipient accounts - #540

Merged
simonhamp merged 1 commit into
mainfrom
fix-recipient-connect-status
Sep 28, 2026
Merged

simonhamp merged 1 commit into
mainfrom
fix-recipient-connect-status

Conversation

@simonhamp

Copy link
Copy Markdown
Member

What was wrong

StripeConnectService::determineStatus() only marked a Connect account Active when payouts_enabled and charges_enabled were both true, and we only pay Active accounts.

Since #525, developers in some countries get an account on Stripe's recipient service agreement. Those accounts only have the transfers capability and, per Stripe's docs, "can't process payments or request the card_payments capability". So charges_enabled is always false for them. After onboarding they stayed Pending, and every sale was stored as a Held payout that never went out. That includes the payouts payouts:recreate-connect-account moves back to held, which it says will be sent once the developer finishes onboarding.

Why charges_enabled doesn't matter

The marketplace uses separate charges and transfers. Buyers pay the platform account and we send the developer's share with transfers->create. The developer's account never charges anyone. We only need it to receive transfers and pay them out to a bank.

What changed

  • An account is Active when capabilities.transfers is active and payouts_enabled is true. charges_enabled is still stored, it just isn't part of the decision any more.
  • The requirements.disabled_reason check runs first now, so a disabled account can't come out Active. Before, an account with both flags true and a disabled reason was marked Active.
  • A full-agreement account whose transfers capability is pending or inactive now stays Pending. Before, only the two flags were checked.
  • The admin user page says under "Charges Enabled" that it isn't needed for payouts, so a "No" there doesn't look like the problem.

Nothing else in the app reads charges_enabled. Everything that decides whether to pay goes through DeveloperAccount::canReceivePayouts(), which depends on the status.

Developers who are already stuck

They should sort themselves out on the next daily payouts:process-eligible run. That command refreshes every developer account that can't be paid yet and has held payouts, or pending payouts that are due. With the new rule the refresh marks a recipient account Active, and the same run moves its held payouts to pending and dispatches transfers for any past the 15 day hold. There's a test for exactly that case. A developer opening their dashboard also refreshes their status, but held payouts only move on the payout run.

We couldn't check production data, so I don't know how many developers this affects or whether any of them have something else wrong with their account.

Tests

The old tests set both flags to true, which is how this slipped through. The Stripe fakes now return accounts with capabilities and a service agreement, like Stripe sends. New tests cover a recipient account, a full account, transfers pending or inactive, an account with no capabilities, a disabled account that otherwise looks payable, and the heal path through payouts:process-eligible. Full suite: 1944 passed, 1 skipped.

What to check

In the admin, look for developer accounts in recipient-agreement countries sitting at Pending with held payouts. Opening one of them in the Stripe dashboard should show transfers active and payouts enabled. After the next payout run they should be Active and their payouts pending or transferred.

🤖 Generated with Claude Code

A Connect account was only marked active when both payouts_enabled and
charges_enabled were true. Accounts on the recipient service agreement
can never take charges, so those developers stayed pending and their
payouts stayed held.

We use separate charges and transfers, so the developer's account never
charges anyone. It now counts as active when the transfers capability is
active and payouts are enabled. A disabled account is checked first so it
can never come out active.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@simonhamp
simonhamp marked this pull request as ready for review September 28, 2026 10:10
@simonhamp
simonhamp merged commit 2f95082 into main Sep 28, 2026
3 checks passed
@simonhamp
simonhamp deleted the fix-recipient-connect-status branch September 28, 2026 10:11
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