Payments and payouts · from our own builds

Stripe for a UAE marketplace: payments, refunds and provider payouts

A Stripe integration for a UAE marketplace takes PaymentIntents created on the server in dirhams, a single webhook consumer that verifies signatures against the raw request body, refunds sent back against the payments the money came from, and a ledger that knows what each provider is owed before anyone is paid. For Trainr we built live card payments for packages, classes, upgrades and add-ons, refunds for downgrades and cancelled classes, and an approval queue for trainer payouts released by bank transfer, with a Stripe Connect transfer path ready for when trainers connect their accounts.

Updated 20 October 2026 · checked against our code and the vendors' documentation

Where have we built this?

  1. TrainrLive Stripe card payments in AED for packages and classes, refunds, a zero-sum ledger and trainer payout approvals.

What are the numbers and limits to know?

UAE card fee (standard pricing)
2.9% + AED 1.00 per successful domestic card transaction; +1% for international cards; +1% if currency conversion is neededsource, checked 20 October 2026
Refunds
No fee for refunding a card payment, but the original processing, Connect and conversion fees are not returnedsource, checked 20 October 2026
Disputes
AED 60.00 for each dispute receivedsource, checked 20 October 2026
Payouts to your bank
Free on the standard schedule; Instant Payouts 1.5% of volume, minimum AED 2.00source, checked 20 October 2026
Connect, when you set your users' pricing
AED 7.50 per monthly active account and 0.25% + AED 5.50 per payout sentsource, checked 20 October 2026
Webhook retries
Up to three days with exponential backoff in live mode; signature timestamps tolerated for 5 minutes by defaultsource, checked 20 October 2026
Refund timing
About 5–10 business days to reach the customer; a failed refund can take up to 30 days to return to your balancesource, checked 20 October 2026
Idempotency keys
Up to 255 characters; may be pruned once they are 24 hours oldsource, checked 20 October 2026

How do you integrate Stripe payments, refunds and payouts, step by step?

  1. 1

    Create the Stripe customer once and keep its ID

    Each client gets one Stripe customer, created on first purchase and stored on their profile behind a unique index. If the stored customer has been deleted at Stripe, a new one is created and the stale ID cleared, and a duplicate-key error on save is checked against other clients before it is retried.

  2. 2

    Price on the server and create the PaymentIntent in fils

    For a class booking, the amount comes from the class record and is converted to fils. Payment methods are enabled automatically with redirects turned off, because a mobile app has nowhere to redirect to. Metadata names the booking, class, client and trainer, so any event or support question leads straight back to the booking.

  3. 3

    Confirm on the server, not on the phone's word

    When the app reports success, the backend retrieves the PaymentIntent from Stripe and marks the payment succeeded, adding the client to the class or activating the package, only if its status is succeeded.

  4. 4

    Register a single webhook endpoint

    Trainr has two backends on one MongoDB database, and only the mobile backend has a Stripe webhook endpoint, subscribed to payment_intent.succeeded, payment_intent.payment_failed and charge.refunded. A second endpoint on the web backend could have processed the same event twice against the same data.

  5. 5

    Write every sale and refund as a zero-sum ledger group

    A sale writes the client's payment, the trainer's share less commission as pending, and the platform's commission, under one transaction group ID; a refund writes the mirror image. A group whose entries do not sum to zero within one fil raises an error, and a reconcile check lists any group that does not balance. Each entry stores the commission rate it used, so a rate change applies forward only.

  6. 6

    Refund against the payments the money came from

    A package bought, upgraded and added to may span several PaymentIntents, so a refund walks them newest first until the amount is covered; Stripe allows several partial refunds on one payment up to its original amount. The ledger records each refund under its Stripe refund ID, so the webhook and the direct refund path cannot book it twice.

  7. 7

    Hold provider earnings, then pay out on approval

    A trainer's share stays pending until its settlement date, so a refund inside that window reverses cleanly, and a daily job settles what is due. Trainers request a payout from their settled balance; an operator approves or rejects it and releases it with a bank transfer, PayPal or cash reference. A Stripe Connect transfer path is built for trainers with connected accounts, and a release that fails marks the payout failed instead of losing it.

  8. 8

    Go live in one window, and check every key's shape

    We moved the publishable key, the secret keys and a new live webhook signing secret into every service in one window, and checked each value's prefix against the field it was going into before building the app.

What went wrong, and how was it fixed?

Bulk-cancelling classes told clients they were refunded, but never called Stripe

When a trainer cancelled several class dates at once, the bulk path wrote the ledger entry and sent the 'refund processed' notification without creating a Stripe refund, so the money stayed on the client's card. The single-date path issued refunds correctly but had no route. We found it before Trainr took live payments.

Fix: The bulk path now creates the Stripe refund for each booking and only reports success when Stripe does; a failed refund is stored as Failed and the client's message says so. Cancelling a class date in the past is refused.

Money records in three places and no platform revenue

In January 2026 payments were spread across a payments collection and two purchase histories, nothing tracked what the platform had earned, and a refund reversed nothing. Nobody could say what a trainer was owed.

Fix: One unified ledger for clients, trainers and the platform, written as balanced groups, with settlement dates and a reconcile check.

A live secret key in the Google Places field

During the switch to live payments a Stripe secret key had been pasted into the app's Google Places key field. Shipped, it would have been compiled into every installed copy of the app and readable by anyone who unpacked it.

Fix: Checking every key's prefix against its field before the build caught it. Secret keys live only in the server environment files.

Carriage returns in the server environment files

Every line of both backends' environment files ended in a Windows carriage return from a paste. Whether that breaks a Stripe key depends on how the dotenv version trims values, which is not worth finding out in production.

Fix: Stripped the carriage returns on both servers and restarted before taking a live payment.

What should you check before going live?

  • Dashboard shows 2xx deliveries for a real test event
  • Webhook route receives the raw body, with no JSON parser in front
  • Exactly one service consumes Stripe events
  • Ledger reconcile returns no unbalanced groups
  • Full, partial and multi-payment refunds tested
  • Secret keys only on servers; key prefixes checked before the app build
  • Environment files free of carriage returns
  • refund.created and refund.failed handled, as Stripe recommends

Questions about Stripe payments, refunds and payouts

Does Stripe work for a marketplace run from the UAE?

Yes. Trainr, a Dubai trainer marketplace, takes live card payments in dirhams through Stripe in its Android app. Stripe's UAE standard pricing is 2.9% plus AED 1.00 per domestic card payment, with 1% more for international cards and 1% more when currency is converted.

How long does a Stripe refund take to reach the customer?

Stripe says about 5 to 10 business days, depending on the bank. A refund made shortly after the payment can appear as a reversal, where the original charge simply drops off the statement. A refund that fails is returned to your Stripe balance, which can take up to 30 days, so the app needs a state for it.

Do we need Stripe Connect to pay the providers on our marketplace?

Not to start. Trainr's operators release approved trainer payouts by bank transfer, PayPal or cash and record the reference, while a Stripe Connect transfer path waits for trainers to connect accounts. If you set your providers' pricing yourself, Connect in the UAE adds AED 7.50 per monthly active account and 0.25% plus AED 5.50 per payout.

Who pays the Stripe fee when a booking is refunded?

The platform does. Stripe charges nothing to refund a card payment, but it keeps the processing fee from the original payment. On Trainr the ledger writes a refund as the mirror of the sale, so the trainer's share and the platform's commission are both reversed while the trainer's earnings are still pending.

Why create the PaymentIntent on the server rather than in the app?

So the price and what it buys come from your database, not from the phone. For a class booking, Trainr's backend reads the class price, creates the PaymentIntent with metadata naming the booking, and confirms the payment only after it has retrieved the PaymentIntent from Stripe and seen it succeeded.

What does UAE VAT change in a Stripe integration?

Decisions come before code. For Trainr we listed four: whether prices include or exclude VAT, the 5% standard rate and any exceptions, how to treat trainers above and below the AED 375,000 mandatory registration threshold, and whether to collect TRNs at onboarding. Confirm the treatment with an accountant before building it.

Need Stripe payments, refunds and payouts in your app?

We have built it in production code, including the parts that went wrong. Tell us what your app needs and we'll say what it takes.

Ask about this integration