Before you start
- ✓A decision on who is merchant of record (usually the promoter)
- ✓Stripe Connect and Square developer accounts, with their test environments
- ✓Your fee model per ticket, and who absorbs the processing fee
- ✓A payment test plan covering each scenario on both providers
Deciding who is the merchant of record
The promoter sells the ticket, so the promoter should usually be merchant of record: their name on the statement, their refunds, their chargebacks. On a ticketing platform we built, payments go directly on the promoter's account, with the platform's cut as an application fee, avoiding settings that would quietly make the platform merchant of record. We got there in three iterations: transfers between three parties, then a fee plus a single transfer, then direct charges that mirror how Square works.
Connecting promoters
- Stripe: promoters connect their own full (Standard) Stripe account and keep their own dashboard.
- Square: ask for every permission you'll need up front (payments, payouts, customers if you'll store cards). Adding one later means every promoter reconnecting.
- Identify the promoter from a one-time token, not whoever is logged in, and let it survive a slow sign-up (we use 30 minutes).
- Encrypt stored tokens and renew Square's early: ours renew daily for any expiring within 7 days.
Working out fees per ticket
The booking fee is a percentage plus a fixed amount per ticket, rounded to the penny, with settings cascading from platform to promoter to event. It's fixed when the ticket enters the basket, so a settings change can't alter it mid-checkout. The platform fee is then capped at 60% of the charge to fit both providers' limits. The capped fee is recorded on the order and the uncapped figure kept for reporting.
Apple Pay, Google Pay and Klarna on every account
These need your checkout's web address registered on each promoter's account. We register it on connect or reconnect, checking what's already there, and gave existing promoters a backfill button.
Recording the real processing fee
On Stripe, the fee reported on the promoter's account includes your platform fee, so count only Stripe's own. On Square the fee isn't known at payment time, so it's filled in when Square reports the update. Never store an estimate: the promoter's payout report depends on it.
Planning for missed notifications
- Tell Stripe when a notification fails briefly, so it retries.
- Reconcile every minute on each provider, to catch payments whose notifications were missed, or acknowledged even though processing them failed.
- Record each payout once, and never move one back from a final state.
Keeping the providers in step
A bug fixed on Stripe is checked and fixed on Square in the same change, and the payment test plan runs every scenario on both, embedded in promoters' websites and standalone.
What usually goes wrong
From our own projects, not the manual.
Apple Pay, Google Pay and Klarna vanished in live mode
The card form worked, but wallets and Klarna never appeared. When payments are taken on the promoter's account, Stripe needs your checkout domain registered on that account, and only enforces it in live mode. Register it on connect and backfill existing accounts.
Square rejected discounted orders with INVALID_FEES
A discount shrank the charge but not the platform fee, which then broke Square's fee limit. Cap the platform fee as a share of the actual charge on both providers.
Verified cards were still declined on Square
UK and EU cards passed the 3-D Secure check, but Square returned a generic decline because the proof of that check was never sent with the payment. Pass it through.
Customers couldn't retry after a decline
The retry reused the previous attempt's unique reference with a new card, so the provider rejected it. Issue a new reference only after a definite decline, and keep it after timeouts or server errors, or you risk charging twice.
Promoters' net takings were overstated
On the promoter's account, Stripe's fee figure includes the platform fee. Count only Stripe's own processing fee, and backfill it from charge updates.
OAuth connected the wrong promoter
The fixed return address carries no promoter details, so the connection step guessed from the logged-in user. Identify the promoter from the one-time token instead.
Square payments failed for some event names
Square's statement description forbids characters such as quotes, asterisks and angle brackets. Clean them out before sending.
Do it yourself, or get help?
If you're adding one provider to a simple shop, the provider's own documentation will get you most of the way. If you're a marketplace taking fees on other businesses' sales, supporting two providers, or putting checkout inside promoters' own websites, get help: the problems above only show up in live mode, with real cards, at real events.
Get a developer on it