Refund Handling
Issue a refund from your store admin and Split Pay PRO automatically reverses the associated Stripe transfers — proportionally for partial refunds, fully for complete refunds. Works on both WooCommerce orders and FluentCart orders. Manual reversal options are also available via the Stripe Dashboard or API.
Automatic refund handling PRO#
Transfers are reversed automatically. When you issue a refund from your store admin (WooCommerce or FluentCart), Split Pay PRO calculates the refund ratio and reverses each transfer proportionally. No manual Stripe Dashboard interaction is needed.
Split Pay listens for refund events on whichever platform is processing the order:
- WooCommerce orders — Split Pay hooks into the
woocommerce_order_refundedaction. - FluentCart orders — Split Pay hooks into FluentCart’s refund event (see the FluentCart integration page for the exact hook). The reversal logic is identical.
You issue a refund — Navigate to the order in your store admin (WooCommerce → Orders or the FluentCart → Orders screen), open the order, click Refund, enter the amount, and confirm.
Split Pay calculates the refund ratio — The plugin compares the refund amount to the original order total. A $50 refund on a $200 order produces a ratio of 25%.
Each transfer is reversed proportionally — The plugin retrieves every transfer recorded for that order and creates a Stripe transfer reversal for the proportional amount.
Order notes are added — Each reversal (success or failure) is documented as an order note on the originating store order, plus a summary at the end.
Full refund example#
A $150 order with two transfers:
| Transfer | Original Amount | Reversal Amount |
|---|---|---|
| Vendor A (acct_xxx) | $45.00 | $45.00 (100%) |
| Vendor B (acct_yyy) | $30.00 | $30.00 (100%) |
Order notes created:
- "Split Pay: Reversed transfer tr_xxx to acct_xxx — $45.00 (full reversal)"
- "Split Pay: Reversed transfer tr_yyy to acct_yyy — $30.00 (full reversal)"
- "Split Pay Refund Summary: 2 transfer(s) reversed, 0 failed. Refund ratio: 100%"
Partial refund example#
Same $150 order, but only a $37.50 refund (25%):
| Transfer | Original Amount | Reversal Amount |
|---|---|---|
| Vendor A (acct_xxx) | $45.00 | $11.25 (25%) |
| Vendor B (acct_yyy) | $30.00 | $7.50 (25%) |
Handling failed reversals#
A transfer reversal can fail if the connected account lacks sufficient balance (e.g., the vendor already paid out their funds):
- The failure is logged as an order note with the Stripe error message.
- Other transfers in the same refund continue processing — one failure doesn't block the others.
- The summary note reports the count of successful and failed reversals.
If a vendor's Stripe balance is insufficient, wait for new payments to build it up. Check the order notes for the specific error. Once the balance is restored (e.g., after the next batch of payments), retry the reversal manually via the Stripe Dashboard or use the Retry Failed Transfers order action where applicable.
Prefer this to happen automatically? Turn on Reclaim shortfalls from future transfers and Split Pay will recover the shortfall for you the next time the vendor is paid — no manual retry needed.
Reclaim shortfalls from future transfers#
By default, a reversal that fails because the vendor's balance is too low is simply logged, and the shortfall sits with you until you reclaim it by hand. Auto refund reclaim closes that gap automatically: when a reversal can't be fully clawed back, Split Pay reverses whatever is available right now and records the rest as outstanding debt for that vendor, then deducts it from their next transfers until the balance reaches zero.
This is an optional feature and is off by default. It is the automatic version of the manual “wait for the balance, then retry” workflow above, and it follows Stripe's own guidance to “reconcile any amount owed back to [the platform] by reducing subsequent transfer amounts.”
Example
A vendor was paid $100 for an order. The customer is refunded in full, but the vendor has already paid out most of their balance — only $50 is available to reverse.
| Step | What Split Pay does |
|---|---|
| Refund issued | Attempts to reverse the full $100. |
| Balance too low | Reverses the $50 that is available immediately. |
| Shortfall recorded | Logs $50 as outstanding debt for that vendor. |
| Vendor's next order ($80 due) | Sends $30 to the vendor and keeps $50 to clear the debt. |
| Debt cleared | The ledger returns to $0; future transfers are paid in full again. |
If a future transfer is smaller than the outstanding debt, the entire transfer is withheld and the remaining balance carries over to the one after that. Each deduction is recorded as an order note so there's always a paper trail.
Enabling it
Go to Split Pay → Global Transfer Settings and tick “Reclaim refund shortfalls from a vendor's future transfers.”
Seeing what each vendor owes
Outstanding balances appear on the Split Pay → Platform Status tab, broken down per connected account, Stripe mode, and currency, along with how much has been recovered so far. If a vendor has left your store and will never receive another transfer, use Mark cleared to write the balance off — this only updates the ledger, it never moves money.
How it stays safe
- Per currency and per Stripe mode — a USD debt is only ever recovered from USD transfers, and a test-mode debt never affects live payouts (and vice-versa).
- Never over-charges a vendor — debt is reserved atomically before each transfer, so two orders processed at the same time can't both claw back the same balance.
- Idempotent — a refund that fires twice (for example a manual refund plus a webhook) records the shortfall only once, and a transfer that's retried never double-deducts.
- Accurate refunds afterwards — the transfer log stores the net amount the vendor actually received, so refunding a debt-reduced order reverses exactly what was paid out, never more.
- Forward-only — turning the feature on doesn't retroactively create debt for refunds that already failed; it applies from the moment it's enabled. Turning it back off pauses recovery and leaves existing balances visible on the Status tab.
Developer note: the amount withheld from any single transfer can be capped with thespp_debt_recovery_max_fractionfilter (default1.0, i.e. up to 100% of each transfer).
Requirements for automatic reversals#
- Split Pay PRO — Automatic refund handling requires the PRO version.
- Transfer log entries — The plugin reverses transfers recorded in its transfer log. Orders placed before installation won't have log entries and must be reversed manually.
- Same currency — Reversals must be in the same currency as the original transfer. This is a Stripe platform rule, not a Split Pay limitation: a reversal can only return funds in the original transfer’s currency — Stripe doesn’t convert at reversal time. See Stripe’s Transfer Reversals API reference.
- Connected account balance — Vendors must have sufficient Stripe balance to cover the reversal amount.
- Refund issued through your store admin, not directly in Stripe — This applies to both WooCommerce and FluentCart. A refund initiated directly in the Stripe Dashboard bypasses the platform refund event Split Pay listens for. See the FluentCart-specific refund-handling note for details on Stack B’s refund event.
Manual reversals via Stripe Dashboard#
You can reverse transfers manually through the Stripe Dashboard — useful for free version users, orders placed before the plugin was installed, or edge cases the automatic process doesn't cover.
Log in to the Stripe Dashboard.
Navigate to Connect → Transfers (or find the transfer via the original payment).
Click on the transfer, then click Reverse transfer.
Enter the full or partial amount to reverse, then confirm.
Reversals via the Stripe API#
You can also reverse transfers programmatically using the Stripe Transfer Reversals API. Don’t hard-code your secret key — load it from an environment variable, a secret manager, or your hosting platform’s config so it never reaches version control:
// Load your platform secret key from the environment, NOT a literal.
// It will start with sk_test_ in test mode or sk_live_ in live mode,
// or rk_test_ / rk_live_ if you are using a restricted key with
// "Connect → Transfers: Write".
$stripe = new \Stripe\StripeClient(getenv('STRIPE_SECRET_KEY'));
// Reverse the full transfer
$reversal = $stripe->transfers->createReversal(
'tr_TRANSFER_ID',
[]
);
// Partial reversal
$reversal = $stripe->transfers->createReversal(
'tr_TRANSFER_ID',
['amount' => 1000] // $10.00 in cents
);
Never commit a literal sk_live_… key. Stripe automatically scans public GitHub for live secret keys and will revoke any it finds, but a leaked key can still be abused before that happens. Use environment variables, your hosting platform’s secret store (e.g. Cloudflare Workers Secrets Store, Vercel env vars, WP-CLI wp config set STRIPE_SECRET_KEY), or a dedicated secret manager.
It's good practice to tell your vendors before reversing a transfer so they understand why funds are being pulled back from their account.
Retrying failed transfers#
If transfers fail during initial processing (e.g., network issues or temporary Stripe errors), you can retry them directly from the order page. See Retry Failed Transfers for details.
Technical implementation#
For developers and store owners who want to understand the internals:
- WordPress hook:
woocommerce_order_refunded(priority 10, receives$order_idand$refund_id) - Refund ratio calculation:
refund_amount / order_total - Stripe API: Uses
$stripe->transfers->createReversal()— full amount when ratio is 100%, calculated amount for partial refunds - Transfer lookup: Queries the
bsd_scsp_transfer_logdatabase table for all successful transfers matching the order ID - Error isolation: Each reversal is wrapped in its own try/catch block so individual failures don't prevent other reversals