Take payments in iOS / Android apps

Open the ReBate Pay checkout inside your app, take the payment, and land back in the app. Card entry and 3D Secure happen on the checkout page, so your app never touches card numbers.

View as Markdown

Mobile checkout reuses the web Hosted Checkout and Payment Links: your backend creates a single-use payment link, the app opens it in the system's in-app browser, and after paying the checkout page sends the customer back into your app.

Keep your API key on your backend. Never ship an rbp_... key inside the app — anyone can extract it from the app bundle. The app only talks to your own backend.

Flow

  1. 1Register your app's URL scheme in the console under Developers → Mobile apps, e.g. com.example.shop.
  2. 2The app asks your backend to create an order; the backend calls POST /api/PaymentLink with its API key (singleUse: true) and successUrl set to com.example.shop://checkout/done.
  3. 3The backend returns the full URL (REBATE_PAY_BASE_URL + the response's url) to the app.
  4. 4The app opens it with the ReBate Pay SDK — ASWebAuthenticationSession on iOS, Chrome Custom Tabs on Android. The customer enters their card and completes 3D Secure on the checkout page.
  5. 5After a successful payment the checkout page redirects to com.example.shop://checkout/done?rebatepay_checkout=chks_…&rebatepay_status=paid and the SDK hands the result to the app.
  6. 6The app passes rebatepay_checkout to your backend; the backend calls GET /api/Checkout/{id} to check that the checkout belongs to the order's payment link and has paid: true (or receives the payment.succeeded webhook) before shipping or unlocking anything.

1. Register your app URL scheme

Only registered schemes are accepted as successUrl; any other non-https URL is rejected with VALIDATION_ERROR. Up to 5; reverse-DNS names (com.example.shop) avoid clashes with other apps. Web and system schemes such as http, https, tel, mailto or intent can't be registered. Once a scheme is removed, links using it no longer return to the app (the customer stays on the ReBate Pay confirmation page).

curl -sS -X POST "$REBATE_PAY_BASE_URL/api/PaymentLink" \
  -H "Authorization: Bearer $REBATE_PAY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order_1042" \
  -d '{
    "name": "Order #1042",
    "priceId": "price_...",
    "singleUse": true,
    "successUrl": "com.example.shop://checkout/done?order=1042"
  }'
# => { "id": "plink_...", "url": "/pay/<token>", ... }
# Give the app: $REBATE_PAY_BASE_URL + url

successUrl can carry your own parameters (e.g. ?order=1042); ReBate Pay keeps them and appends rebatepay_checkout and rebatepay_status (paid, or trialing for a trial).

3. Open checkout from the app

// Package: packages/rebatepay-ios (Swift Package "RebatePay", iOS 13+)
import RebatePay
import UIKit

final class CheckoutViewController: UIViewController {
    private let checkout = RebatePayCheckout(callbackScheme: "com.example.shop")

    func pay(orderId: String) async {
        do {
            let paymentURL = try await api.createCheckout(orderId: orderId) // your backend
            let result = try await checkout.present(paymentURL: paymentURL, from: view.window!)
            switch result {
            case .completed(let checkoutId, _):
                // Don't trust the app side: your backend checks GET /api/Checkout/{id}.
                showReceipt(try await api.confirmOrder(orderId: orderId, checkoutId: checkoutId))
            case .canceled:
                showMessage("Payment was not completed")
            }
        } catch {
            showMessage("Could not open checkout: \(error)")
        }
    }
}
iOSAndroid
SDKpackages/rebatepay-ios (Swift Package RebatePay)packages/rebatepay-android (module :checkout)
Minimum OSiOS 13Android 6.0 (API 23)
Opens withASWebAuthenticationSession (ephemeral by default: no shared Safari cookies, no "wants to use … to Sign In" prompt)Chrome Custom Tabs; falls back to the default browser
App setupNo URL Types needed — the system intercepts the callbackmanifestPlaceholders["rebatepayScheme"] set to your scheme
Customer closes checkout.canceledRebatePayResult.Canceled
After a successful payment the checkout page returns to the app automatically after a few seconds. Some Android browsers block app redirects that aren't triggered by a tap; the customer then taps the "Return to store" button.

4. Confirm the payment on your backend

The rebatepay_status the app receives only means the checkout page redirected back — anyone can open com.example.shop://checkout/done?rebatepay_status=paid by hand. Always confirm on your backend before shipping or unlocking anything.

GET/api/Checkout/{id}— Look up a checkout and its payment (needs payment_links:read)
// POST /orders/:id/confirm — the app sends the rebatepay_checkout value it got back
app.post("/orders/:id/confirm", requireUser, async (req, res) => {
  const order = await db.orders.find(req.params.id, req.user.id);
  const r = await fetch(`${process.env.REBATE_PAY_BASE_URL}/api/Checkout/${req.body.checkoutId}`, {
    headers: { Authorization: `Bearer ${process.env.REBATE_PAY_API_KEY}` },
  });
  if (!r.ok) return res.status(400).json({ paid: false });
  const { data: checkout } = await r.json();
  // Must be this order's link, and actually paid.
  const paid = checkout.paymentLinkId === order.paymentLinkId && checkout.paid === true;
  if (paid) await db.orders.markPaid(order.id, checkout.payment?.id);
  res.json({ paid });
});
// Also mark the order paid from the payment.succeeded webhook — whichever arrives first wins.

A payment link's completionCount goes up when the customer submits checkout, while the payment may still be processing or be declined — don't use it to decide whether the order is paid; use paid. Also subscribe to the payment.succeeded webhook: your backend hears about the payment even if the customer never makes it back to the app (they closed it, the phone died).

Not supported

  • Embedding checkout in a WebView: the checkout page refuses to be framed (frame-ancestors 'none') and 3D Secure redirects break. Use the SDKs above.
  • Collecting card numbers in the app and calling the API: cards are only entered on the checkout page, and the developer API rejects raw card data.
  • Calling Apple Pay / Google Pay from the app yourself: wallets come from the checkout page (buttons appear on supported devices, one-time payments only); the app doesn't and can't integrate them directly.
Store rules: the Apple App Store and Google Play usually require their own in-app purchase for digital content and subscriptions used inside the app (memberships, credits, unlocked features); physical goods and real-world services can generally use a third-party payment provider. Check which applies to what you sell before submitting.