Skip to content

Fix Stripe payouts to developers outside the US, UK, EEA, CA and CH - #525

Merged
simonhamp merged 4 commits into
mainfrom
stripe-mx-recipient-payouts
Sep 19, 2026
Merged

simonhamp merged 4 commits into
mainfrom
stripe-mx-recipient-payouts

Conversation

@simonhamp

@simonhamp simonhamp commented Sep 19, 2026

Copy link
Copy Markdown
Member

A plugin developer in Mexico made a sale and the payout failed with:

Funds can't be sent to accounts located in MX when the account is under the full service agreement.

Our Stripe platform account is in the US. A US platform can only transfer to connected accounts on the full service agreement in the US, Canada, the UK, the EEA and Switzerland. Everywhere else needs the recipient agreement. We were creating every developer as an Express account with card_payments, which puts them on full.

So developers in AE, AU, HK, JP, MX, NZ, SG and TH could sign up and onboard, but every transfer to them failed. And 62 of the 103 countries in the onboarding dropdown only allow recipient, so account creation failed there and the developer was told their country isn't supported.

What changed

  • StripeConnectCountries now lists the countries we can pay on full. Developers anywhere else get a recipient account that only requests transfers.
  • New payouts:recreate-connect-account {developerAccount} command for developers who already have the wrong account. Stripe won't change the agreement once it's accepted, so the command creates a fresh account through the new StripeConnectService::replaceConnectAccount() and resets their onboarding. It does nothing if the live account is already recipient or in a country we can pay on full. It also moves that developer's failed payouts back to held.
  • The daily payouts:process-eligible run now checks Stripe for any developer we can't pay yet who has held or due payouts. We used to refresh that status only when a developer came back from onboarding or opened their dashboard, so someone who finished onboarding and closed the tab never got paid. Refreshing also no longer overwrites the onboarding date shown in the admin.
  • Removed ProcessPluginCheckoutJob. Nothing has dispatched it since Refactor plugin checkout to use cart-based approach #261 and its payout logic no longer matched HandleInvoicePaidJob.

I checked the service agreement rules in test mode against our platform. A full MX account gives the same error as production. A recipient MX account accepted a USD transfer with source_transaction and it landed as MXN. Trying every country where Stripe offers full showed that exactly AE, AU, HK, JP, MX, NZ, SG and TH are blocked.

After merging

Run php artisan payouts:recreate-connect-account <id> for the Mexican developer and ask them to redo Stripe onboarding. Their failed payout goes out on the first daily run after they finish. Sales they make in the meantime are held and follow the usual 15 day hold. Any existing developers in AE, AU, HK, JP, NZ, SG or TH need the same.

Things to watch

  • Stripe's cross-border payouts page now says self-serve payouts outside the US, UK, EEA, Canada and Switzerland aren't supported, and points recipient accounts at Global Payouts. The API and Stripe's other docs disagree, and test mode works. If the live retry fails with a different error, we need to ask Stripe to enable cross-border payouts.
  • determineStatus() still requires charges_enabled. An active recipient account reported it as true in test mode, so I left it alone. If the developer finishes onboarding and stays pending, that's the check to change.

🤖 Generated with Claude Code

Our Stripe platform is in the US, so it can only transfer to connected
accounts on the full service agreement in the US, Canada, the UK, the EEA
and Switzerland. Developers everywhere else now get a recipient account
that only requests the transfers capability.

Adds payouts:recreate-connect-account to move an existing developer onto
a new recipient account and hold their failed payouts until they finish
onboarding again.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 19, 2026

Copy link
Copy Markdown

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: Advanced

Run ID: 9ecde252-b249-44d3-b7f5-b2a5c31f1721

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

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

simonhamp and others added 2 commits September 19, 2026 14:07
Nothing has dispatched it since plugin checkout moved to invoices in #261.
Its payout logic had drifted from HandleInvoicePaidJob, which made it easy
to misread how payouts work.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
We only refreshed a developer's Stripe status when they came back from
onboarding or opened their dashboard. If they finished onboarding and
closed the tab, their held payouts never went out. The daily run now
refreshes anyone we can't pay yet who has held or due payouts before it
decides what to send.

Refreshing no longer overwrites the date onboarding was completed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@simonhamp
simonhamp marked this pull request as ready for review September 19, 2026 13:17
payouts:send-daily-summary runs at 12:00, an hour after the payout run,
and emails the support address. It covers payouts sent or failed in the
last 24 hours with Stripe's error for each failure, the upcoming and
pending payouts still waiting, and the total paid out to date.

It only skips a day when nothing was sent and nothing is waiting.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@simonhamp
simonhamp merged commit 5f6d6c7 into main Sep 19, 2026
4 checks passed
@simonhamp
simonhamp deleted the stripe-mx-recipient-payouts branch September 19, 2026 14:30
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