Skip to content

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

  1. The return address = your app's Universal Link. return_url must be https:// (a custom scheme shopapp://… is not allowed — any app can register that scheme). So use a host of your own such as https://app.shop.mn/paid:
  2. iOS: applinks in apple-app-site-association (path /paid*) + the app's Associated Domains applinks:app.shop.mn;
  3. Android: assetlinks.json + an intent-filter with android:autoVerify="true".
  4. Add that host to the app's return_hosts (web checkout §2).
  5. 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 done page 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; pi is 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