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.
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.
rbp_... key inside the app — anyone can extract it from the app bundle. The app only talks to your own backend.Flow
- 1Register your app's URL scheme in the console under Developers → Mobile apps, e.g.
com.example.shop. - 2The app asks your backend to create an order; the backend calls
POST /api/PaymentLinkwith its API key (singleUse: true) andsuccessUrlset tocom.example.shop://checkout/done. - 3The backend returns the full URL (
REBATE_PAY_BASE_URL+ the response'surl) to the app. - 4The app opens it with the ReBate Pay SDK —
ASWebAuthenticationSessionon iOS, Chrome Custom Tabs on Android. The customer enters their card and completes 3D Secure on the checkout page. - 5After a successful payment the checkout page redirects to
com.example.shop://checkout/done?rebatepay_checkout=chks_…&rebatepay_status=paidand the SDK hands the result to the app. - 6The app passes
rebatepay_checkoutto your backend; the backend callsGET /api/Checkout/{id}to check that the checkout belongs to the order's payment link and haspaid: true(or receives thepayment.succeededwebhook) 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).
2. Create the payment link on your backend
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 + urlsuccessUrl 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)")
}
}
}| iOS | Android | |
|---|---|---|
| SDK | packages/rebatepay-ios (Swift Package RebatePay) | packages/rebatepay-android (module :checkout) |
| Minimum OS | iOS 13 | Android 6.0 (API 23) |
| Opens with | ASWebAuthenticationSession (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 setup | No URL Types needed — the system intercepts the callback | manifestPlaceholders["rebatepayScheme"] set to your scheme |
| Customer closes checkout | .canceled | RebatePayResult.Canceled |
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.
/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.
ReBate Pay Docs