Taking payments from your own iOS / Android app¶
On this page
What: your mobile app sends the person to Gerege App to pay and, after paying, brings them back to your
app. Time: ~2 hours. You need: the backend side of web checkout (exactly the same
redirect intent), your app's Universal Link (iOS) / App Link (Android) host.
A mobile app is a variant of a web shop: your backend creates the intent, the app only opens pay_url and, on
coming back, asks its backend for the outcome. The client secret never goes into the app — anybody can take the
app's binary apart and read it.
1. Flow¶
Your app Your backend Gerege Gerege App
│ «Төлөх» │ │ │
│─ POST /orders/7/pay▶│ POST /payment-intents │ │
│ │ redirect, return_url = │ │
│ │ https://app.shop.mn/paid │ │
│ │─────────────────────────▶│ │
│◀── {pay_url} ──────│◀──── {id, pay_url} ──────│ │
│── open(pay_url) ── Universal Link ─────────────────────────────────────▶│ claim → Face ID → pays
│◀────────── open(https://app.shop.mn/paid?pi=pi_…) — your Universal Link ─│
│─ GET /orders/7 ────▶│ GET /payment-intents/{id}│ │
│◀── {paid: true} ───│◀─────────────────────────│ │
2. Setup¶
- The return address = your app's Universal Link.
return_urlmust behttps://(a custom schemeshopapp://…is not allowed — any app can register that scheme). So use a host of your own such ashttps://app.shop.mn/paid: - iOS:
applinksinapple-app-site-association(path/paid*) + the app's Associated Domainsapplinks:app.shop.mn; - Android:
assetlinks.json+ anintent-filterwithandroid:autoVerify="true". - Add that host to the app's
return_hosts(web checkout §2). - When the app is not installed, the Universal Link opens your web page — that page checks on the server and shows
the outcome like the
donepage of web checkout.
3. Backend: creating the intent¶
The app calls your backend's own route (POST /orders/{id}/pay, with the app user's session). The backend creates
exactly the same redirect intent as in web checkout §3 and returns only the
pay_url:
{ "amount": 45000, "description": "Захиалга ord_8812", "reference": "ord_8812", "expires_in": 900,
"presentment": "redirect", "return_url": "https://app.shop.mn/paid?order=ord_8812" }
4. App: opening pay_url¶
Open pay_url with the system's open call — only then does the Universal Link open Gerege App. Opened in a
WKWebView, an SFSafariViewController or an in-app browser, Gerege App does not open and the web page stays.
iOS (Swift):
// The pay_url from your backend. If Gerege App is installed it opens, otherwise Safari (QR / install page).
UIApplication.shared.open(payURL)
Android (Kotlin):
// The Android version of Gerege App is paused: for now the browser opens the pay_url web page.
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(payUrl)))
5. Coming back¶
After paying, Gerege App opens return_url?…&pi=pi_… → your Universal Link → your app. The app reads only the order
number from the URL and asks its backend (anybody can write the URL):
.onOpenURL { url in
guard url.host == "app.shop.mn", url.path == "/paid",
let order = URLComponents(url: url, resolvingAgainstBaseURL: false)?
.queryItems?.first(where: { $0.name == "order" })?.value else { return }
Task { await model.refreshOrder(order) } // your backend → GET /payment-intents/{id} → paid?
}
The backend's check, and what to show for each status, are the same as in web checkout §5.
6. Coming back to the app by hand¶
The person may come back from Gerege App to your app through the app switcher (return_url not opened). So while an
order is in the «төлөгдөж байна» ("being paid") state, ask your backend for the status every time the app comes to the
foreground (scenePhase == .active, Android onResume). The backend has quite likely learnt it from the webhook
already.
7. Security¶
- The client secret and the webhook secret — only on the backend. The app reaches your backend only with its own session.
- The app does not send the amount: the app says «pay order 7», the backend computes the amount from the catalogue.
pay_url— for that person only: do not put it in push notifications, the share sheet or analytics logs.- Do not trust the return URL;
piis only a hint.
8. Implementation status¶
| Part | Status |
|---|---|
The backend side (redirect intent, return_url?pi=, webhook) |
✓ |
Gerege App iOS: opening pay_url through the Universal Link, opening return_url with UIApplication.open after paying |
✓ (Universal Link — once the core's AASA with /pay/* is in production) |
| A web page (with an install link) on phones without Gerege App | «coming soon» |
| Android Gerege App | paused |