# Accept payments in a mobile app Building with an AI agent? Start at https://docs.payme.io/agents.md ## Prerequisites - A server that holds your seller_payme_id (MPL) and receives callbacks - Sandbox credentials (see Authentication) ## Related - [Create a sale (hosted payment page)](/payments/hosted-payment-page/post-generate-sale) - [Hosted Payment Page options](/accepting-payments/checkout-and-in-person/hosted-payment-page-options) - [Callbacks](/payments/getting-started/callbacks) - [List sales](/payments/query-apis/post-get-sales) - [Understand the sale lifecycle](/payments/codes-statuses/sale-lifecycle) - [Let the buyer choose how to pay](/accepting-payments/checkout-and-in-person/let-the-buyer-choose) - [Save a card and charge it later](/accepting-payments/capture-and-saved-cards/save-a-card-and-charge-it-later) - [Hosted Fields JSAPI guide](/accepting-payments/checkout-and-in-person/hosted-fields-jsapi-guide) - [Authentication](/payments/getting-started/authentication) - [Get API access and go live: from sandbox to production](/payments/getting-started/go-live) - [iOS SDK](/payments/ios-sdk) - [Android SDK](/payments/android-sdk) To take payments in an iOS or Android app, create the sale on your server and open PayMe's payment page in a secure in-app browser. The buyer types the card details into PayMe's page, your app holds no PayMe credentials, and your server confirms the result before you deliver the order. This guide shows the flow, the parts that are specific to apps, and what to test. ## How it works 1. The buyer taps **Pay** in your app. Your app asks your own server to start a checkout for the order. 2. Your server calls [`generate-sale`](/payments/hosted-payment-page/post-generate-sale) with your MPL and sends the `sale_url` back to the app. 3. The app opens `sale_url` in a secure in-app browser: `SFSafariViewController` on iOS, Chrome Custom Tabs on Android. 4. The buyer pays. PayMe sends a callback to your server and redirects the in-app browser to your `sale_return_url`. 5. Your return page hands the buyer back to the app, and the app asks your server for the order's status. 6. Your server confirms the sale with [`get-sales`](/payments/query-apis/post-get-sales) before it marks the order paid. The app talks only to your server. Your server is the only part that talks to PayMe. ## Step 1: Create the sale on your server When the app asks for a checkout, your server creates the sale: ```bash curl -X POST https://sandbox.payme.io/api/generate-sale \ -H 'Content-Type: application/json' \ -d '{ "seller_payme_id": "MPLDEMO-MPLDEMO-MPLDEMO-1234567", "sale_price": 1000, "currency": "ILS", "product_name": "Order 1001", "transaction_id": "order-1001", "sale_callback_url": "https://your-server.example.com/payme/callback", "sale_return_url": "https://your-site.example.com/app/payment-done", "language": "en" }' ``` The response holds `sale_url` and `payme_sale_id`. Store `payme_sale_id` with the order on your server, and send only `sale_url` to the app. - **Protect your own endpoint.** The app calls your server's checkout endpoint with your app's own user authentication. Your server decides the price, never the app. - **One sale per order.** If the buyer leaves and comes back to the same unpaid order, reopen the same `sale_url` instead of creating a new sale. A second sale gives the buyer two payable pages. To close a sale after a set time, send `sale_expiry_seconds`. See [Understand the sale lifecycle](/payments/codes-statuses/sale-lifecycle). - **Pre-fill the buyer's details** with `sale_name`, `sale_email` and `sale_mobile`, so the buyer types less on a phone. See [Hosted Payment Page options](/accepting-payments/checkout-and-in-person/hosted-payment-page-options). ## Step 2: Open `sale_url` in a secure in-app browser | Platform | Open `sale_url` in | Not in | |---|---|---| | iOS | `SFSafariViewController` | `WKWebView` | | Android | Chrome Custom Tabs | `WebView` | A secure in-app browser runs the page in the system browser, apart from your app: your app can't read or change the page, and the buyer can see its address. In a WebView, your app controls the page and could read what the buyer types, so card details would pass through your app. With a cross-platform framework such as Flutter or React Native, use a plugin that opens these same system browsers, not one that embeds a WebView. Open `sale_url` exactly as returned. You can add the pre-fill query parameters from [Hosted Payment Page options](/accepting-payments/checkout-and-in-person/hosted-payment-page-options#pre-fill-the-payment-page). ## Step 3: Bring the buyer back to the app After a successful payment, PayMe redirects the in-app browser to your `sale_return_url`, with the sale details as query parameters. See [Success redirect](/accepting-payments/checkout-and-in-person/hosted-payment-page-options#success-redirect). `sale_return_url` must be an `http://` or `https://` address with a public host. A custom app scheme, such as `myapp://payment-done`, fails the whole `generate-sale` call with error `21`. See [Callback URL rules](/payments/getting-started/callbacks#callback-url-rules). So: 1. Point `sale_return_url` at an HTTPS page on your own domain. 2. From that page, hand the buyer back to the app. Register the page's address as a Universal Link (iOS) or an App Link (Android), so the system can open your app for it. Also show a **Return to the app** button on the page that opens your app. 3. Keep the page useful on its own: it shows the result and the button. Whether a redirect opens your app directly depends on the operating system, the browser and your app's link setup, so test it on real devices. The buyer can also close the in-app browser without paying. Then there is no redirect. Handle that case too: on iOS, `SFSafariViewControllerDelegate` calls `safariViewControllerDidFinish(_:)`; on Android, your activity resumes. In both cases, ask your server for the order's status. Use the query parameters only to choose what the app shows next. The order's status comes from your server. ## Step 4: Confirm the payment on your server PayMe sends a `sale-complete` callback to your `sale_callback_url`: a form-encoded `POST`. Reply `200` within 5 seconds. See [Callbacks](/payments/getting-started/callbacks). Before you deliver, confirm the sale. Call `get-sales` with the `payme_sale_id` you stored: ```bash curl -X POST https://sandbox.payme.io/api/get-sales \ -H 'Content-Type: application/json' \ -d '{ "seller_payme_id": "MPLDEMO-MPLDEMO-MPLDEMO-1234567", "sale_payme_id": "SALE1767-704312AB-CD3FGH12-JKLM4567" }' ``` Check that the sale in `items` has `sale_status` `completed` and the `sale_price` of your order. Then mark the order paid. When the app asks for the order's status, answer from your own records. If the app asks before the callback arrives, your server can call `get-sales` itself. By default `get-sales` leaves out unpaid sales (status `initial`), so include `initial` in `sale_status`, for example `"sale_status": ["initial", "completed", "failed"]`. A `failed` sale isn't final: the buyer can still pay it on the same page. See [Understand the sale lifecycle](/payments/codes-statuses/sale-lifecycle). Partners also verify `payme_signature` on the callback. A seller without a partner key relies on the `get-sales` check above. ## Payment methods inside an in-app browser - **Apple Pay and Google Pay** appear on PayMe's page only when the buyer's browser can use them. Inside an in-app browser, that depends on the device, the operating system version and the browser. Test each wallet in your app on real devices before you offer it, and keep the card as the fallback. See [Let the buyer choose how to pay](/accepting-payments/checkout-and-in-person/let-the-buyer-choose). - **Bit and 3D Secure** can take the buyer to another app or to the bank's page, and back. Test that the buyer returns to the in-app browser and finishes the payment. ## Save a card for faster checkouts The same flow can save the buyer's card: create the sale with `capture_buyer` (charge and save) or `sale_type` `token` (save only). Later charges run from your server with the `buyer_key`, with no payment page and nothing to open in the app. See [Save a card and charge it later](/accepting-payments/capture-and-saved-cards/save-a-card-and-charge-it-later). ## Your own checkout design If you need your own checkout design, build it as a web page on your domain with [Hosted Fields](/accepting-payments/checkout-and-in-person/hosted-fields-jsapi-guide), and open that page in the in-app browser in the same way. The page uses the public key only, and your server charges the token and confirms the result. Wallets on your own page need their own setup: see [Accept Apple Pay](/accepting-payments/payment-methods/apple-pay) and [Accept Google Pay](/accepting-payments/payment-methods/google-pay). ## Keep credentials out of the app - Never put `seller_payme_id` (the MPL), `payme_client_key` or the client password in your app, its configuration or its network calls. Anyone can unpack an app, and the MPL alone authenticates calls such as refunds. See [Authentication](/payments/getting-started/authentication). - The flow on this page needs no PayMe key in the app at all. - The callback goes to your server, never to the app. ## The legacy iOS and Android SDKs PayMe's [iOS SDK](/payments/ios-sdk/ios-sdk) and [Android SDK](/payments/android-sdk/android-sdk) are legacy. They collect card details inside your app and put your MPL in it, so they aren't recommended for new apps. Their pages stay available for existing integrations. Build new apps with the flow on this page. ## Before you release - [ ] The app calls only your server. No PayMe credential is in the app. - [ ] `sale_url` opens in `SFSafariViewController` or Chrome Custom Tabs, not in a WebView. - [ ] `sale_return_url` is an HTTPS page on your domain that leads back to the app, tested on iOS and Android devices. - [ ] The app handles a buyer who closes the browser without paying. - [ ] Your server marks an order paid only after the callback and a `get-sales` check. - [ ] Each payment method you offer works in the in-app browser on real devices. - [ ] Production calls use `https://live.payme.io/api` and your production MPL. See [Get API access and go live](/payments/getting-started/go-live).