Pay developers on Stripe recipient accounts - #540
Merged
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What was wrong
StripeConnectService::determineStatus()only marked a Connect account Active whenpayouts_enabledandcharges_enabledwere 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
transferscapability and, per Stripe's docs, "can't process payments or request the card_payments capability". Socharges_enabledis 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 payoutspayouts:recreate-connect-accountmoves 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
capabilities.transfersisactiveandpayouts_enabledis true.charges_enabledis still stored, it just isn't part of the decision any more.requirements.disabled_reasoncheck 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.Nothing else in the app reads
charges_enabled. Everything that decides whether to pay goes throughDeveloperAccount::canReceivePayouts(), which depends on the status.Developers who are already stuck
They should sort themselves out on the next daily
payouts:process-eligiblerun. 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