# iOS App Integration

Accept NFC payments from your iOS app using the **MoneiPaySDK**. The SDK handles URL building, completion-redirect parsing, timeouts, and error handling.

**Source:** [github.com/MONEI/monei-pay-ios-sdk](https://github.com/MONEI/monei-pay-ios-sdk)

No entitlements needed

Uses standard URL schemes — no special entitlements, certificates, or Apple approvals required.

## How it works[​](#how-it-works "Direct link to How it works")

1. Your app calls `MoneiPay.acceptPayment(...)` with a token and amount
2. The SDK opens MONEI Pay for the NFC tap-to-pay transaction
3. MONEI Pay redirects back to your app with the result
4. The SDK parses the completion redirect and resolves a `PaymentResult`

Opens with no prompt

The SDK opens MONEI Pay via a Universal Link (`https://pay.monei.com/accept-payment`), so iOS launches the app directly without an "Open in MONEI Pay?" confirmation. MONEI Pay installs that predate Universal Link support fall back automatically to the `monei-pay://` URL scheme. No change to your integration is required.

## Prerequisites[​](#prerequisites "Direct link to Prerequisites")

* [MONEI account](https://dashboard.monei.com/signup)
* iOS 15.0+, Swift 5.9+
* MONEI Pay installed on the device
* POS auth token from your backend (see [Getting Started](https://docs.monei.com/monei-pay/app-integration/getting-started/.md))

## Integration[​](#integration "Direct link to Integration")

### 1. Install the SDK[​](#1-install-the-sdk "Direct link to 1. Install the SDK")

* Swift Package Manager
* CocoaPods

In Xcode: **File → Add Package Dependencies** → enter `https://github.com/MONEI/monei-pay-ios-sdk` → **Up to Next Major Version** from `1.0.0`.

Or in `Package.swift`:

Package.swift

```
dependencies: [

    .package(url: "https://github.com/MONEI/monei-pay-ios-sdk", from: "1.0.0")

]
```

Podfile

```
pod 'MoneiPaySDK', :git => 'https://github.com/MONEI/monei-pay-ios-sdk.git', :tag => 'v1.0.0'
```

Run `pod install` and open the `.xcworkspace`.

### 2. Configure Info.plist[​](#2-configure-infoplist "Direct link to 2. Configure Info.plist")

Info.plist

```
<key>CFBundleURLTypes</key>

<array>

  <dict>

    <key>CFBundleURLSchemes</key>

    <array>

      <string>your-app</string>

    </array>

  </dict>

</array>



<key>LSApplicationQueriesSchemes</key>

<array>

  <string>monei-pay</string>

</array>
```

warning

Replace `your-app` with your app's unique URL scheme (e.g. your bundle ID). Each merchant app must use a different scheme.

### 3. Wire the Completion Handler[​](#3-wire-the-completion-handler "Direct link to 3. Wire the Completion Handler")

* SwiftUI
* UIKit

YourApp.swift

```
import SwiftUI

import MoneiPaySDK



@main

struct YourApp: App {

    var body: some Scene {

        WindowGroup {

            ContentView()

                .onOpenURL { url in

                    MoneiPay.handleCompleteRedirect(url: url)

                }

        }

    }

}
```

AppDelegate.swift

```
import MoneiPaySDK



func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {

    return MoneiPay.handleCompleteRedirect(url: url)

}
```

### 4. Accept a Payment[​](#4-accept-a-payment "Direct link to 4. Accept a Payment")

```
import MoneiPaySDK



func acceptPayment() async {

    do {

        let result = try await MoneiPay.acceptPayment(

            token: "eyJ...",

            amount: 1500,

            description: "Order #123",

            customerName: "John Doe",

            customerEmail: "john@example.com",

            callbackUrl: "https://merchant.com/webhook/monei",

            completeScheme: "your-app"

        )



        if result.success {

            print("Payment approved: \(result.transactionId)")

            print("Card: \(result.cardBrand ?? "") \(result.maskedCardNumber ?? "")")

        }

    } catch let error as MoneiPayError {

        switch error {

        case .moneiPayNotInstalled:

            break

        case .paymentCancelled:

            break

        case .paymentTimeout:

            break

        case .tokenExpired, .invalidToken:

            break

        case .notAuthenticated, .accountNotConfigured:

            break

        case .paymentFailed(let reason):

            print("Payment failed: \(reason ?? "unknown")")

        default:

            print("Error: \(error.localizedDescription)")

        }

    }

}
```

Trust model

`callbackUrl` is a **trusted, signed webhook** delivered server-to-server — use it to fulfill orders. `completeScheme` (and the resulting `complete_url`) is a **client-side redirect only** — it brings the user back to your app but is not signed. See [Result Delivery](https://docs.monei.com/monei-pay/app-integration/getting-started/.md#result-delivery) for details.

### SDK Reference[​](#sdk-reference "Direct link to SDK Reference")

#### `MoneiPay.acceptPayment(...)`[​](#moneipayacceptpayment "Direct link to moneipayacceptpayment")

| Parameter         | Type            | Required | Description                                                                                                                                                                         |
| ----------------- | --------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `token`           | `String`        | Yes      | POS auth token (raw JWT, no "Bearer " prefix)                                                                                                                                       |
| `amount`          | `Int`           | Yes      | Payment amount in cents (e.g. `1500` = 15.00 EUR)                                                                                                                                   |
| `description`     | `String?`       | No       | Payment description                                                                                                                                                                 |
| `customerName`    | `String?`       | No       | Customer name                                                                                                                                                                       |
| `customerEmail`   | `String?`       | No       | Customer email                                                                                                                                                                      |
| `customerPhone`   | `String?`       | No       | Customer phone                                                                                                                                                                      |
| `callbackUrl`     | `String?`       | No       | Trusted signed webhook URL. Must be strict `https://`, max 2048 chars. See [Result Delivery](https://docs.monei.com/monei-pay/app-integration/getting-started/.md#result-delivery). |
| `orderId`         | `String?`       | No       | Your merchant order reference. Set this to surface it in the webhook callback for reconciliation against your POS order. Max 2048 chars. If omitted, MONEI generates one.           |
| `transactionType` | `String?`       | No       | Optional transaction type: `SALE` (default), `AUTH`, `REFUND`, `CAPTURE`, `CANCEL`, `PAYOUT`, `VERIF`. Server-validated; invalid values are rejected.                               |
| `completeScheme`  | `String`        | Yes      | Your app's registered URL scheme — used to redirect the user back after the payment completes                                                                                       |
| `timeout`         | `TimeInterval?` | No       | Timeout in seconds (default: 60)                                                                                                                                                    |

#### `PaymentResult`[​](#paymentresult "Direct link to paymentresult")

| Property           | Type      | Description                            |
| ------------------ | --------- | -------------------------------------- |
| `transactionId`    | `String`  | MONEI transaction ID                   |
| `success`          | `Bool`    | Whether the payment was approved       |
| `amount`           | `Int?`    | Payment amount in cents                |
| `cardBrand`        | `String?` | Card brand (e.g. `visa`, `mastercard`) |
| `maskedCardNumber` | `String?` | Masked card number (e.g. `****1234`)   |

#### Error Types[​](#error-types "Direct link to Error Types")

| Error                    | Description                                                                         |
| ------------------------ | ----------------------------------------------------------------------------------- |
| `moneiPayNotInstalled`   | MONEI Pay is not installed                                                          |
| `paymentInProgress`      | Another payment is already active                                                   |
| `paymentTimeout`         | No response within the timeout                                                      |
| `paymentCancelled`       | User cancelled                                                                      |
| `paymentFailed(reason:)` | Payment declined or failed                                                          |
| `invalidParameters(_:)`  | Invalid input (e.g. non-positive amount, malformed `callbackUrl` or `complete_url`) |
| `tokenExpired`           | POS auth token has expired (>24h)                                                   |
| `invalidToken`           | POS auth token is malformed or signed by the wrong key                              |
| `notAuthenticated`       | No POS auth token and user is not signed into MONEI Pay                             |
| `accountNotConfigured`   | MONEI Pay account is missing required POS configuration                             |
| `failedToOpen`           | Could not open MONEI Pay                                                            |

***

## Manual Integration (URL)[​](#manual-integration-url "Direct link to Manual Integration (URL)")

If you prefer not to use the SDK, you can integrate directly via the MONEI Pay URL.

Open `https://pay.monei.com/accept-payment` — a **Universal Link** that launches MONEI Pay with no "Open in MONEI Pay?" confirmation. The legacy `monei-pay://accept-payment` URL scheme accepts the **same query parameters** and still works, but triggers the iOS confirmation sheet — prefer the HTTPS link.

Show manual URL integration

### Open the Payment URL[​](#open-the-payment-url "Direct link to Open the Payment URL")

```
func acceptPayment(amountInCents: Int) {

    var components = URLComponents(string: "https://pay.monei.com/accept-payment")!

    components.queryItems = [

        URLQueryItem(name: "amount", value: String(amountInCents)),

        URLQueryItem(name: "auth_token", value: "eyJ..."),

        URLQueryItem(name: "callback_url", value: "https://merchant.com/webhook/monei"),

        URLQueryItem(name: "complete_url", value: "your-app://payment-result")

    ]



    guard let url = components.url else { return }

    UIApplication.shared.open(url)

}
```

### Handle the Completion Redirect[​](#handle-the-completion-redirect "Direct link to Handle the Completion Redirect")

```
struct PaymentResult {

    let success: Bool

    let transactionId: String?

    let amount: String?

    let cardBrand: String?

    let maskedCardNumber: String?

    let error: String?



    init(from url: URL) {

        let params = URLComponents(url: url, resolvingAgainstBaseURL: false)?

            .queryItems?

            .reduce(into: [String: String]()) { $0[$1.name] = $1.value } ?? [:]



        self.success = params["success"] == "true"

        self.transactionId = params["transaction_id"]

        self.amount = params["amount"]

        self.cardBrand = params["card_brand"]

        self.maskedCardNumber = params["masked_card_number"]

        self.error = params["error"]

    }

}
```

Trust model

The query parameters on the `complete_url` redirect are **not signed** — they can be spoofed. Use `callback_url` (signed webhook) or [Get Payment](https://docs.monei.com/apis/rest/payments-get/.md) to confirm the actual outcome before fulfilling an order. See [Result Delivery](https://docs.monei.com/monei-pay/app-integration/getting-started/.md#result-delivery).

### URL Parameters[​](#url-parameters "Direct link to URL Parameters")

#### Request Parameters[​](#request-parameters "Direct link to Request Parameters")

| Parameter          | Type    | Required | Description                                                                                                                                |
| ------------------ | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `amount`           | Integer | Yes      | Payment amount in cents (e.g. `1500` = 15.00 EUR)                                                                                          |
| `auth_token`       | String  | Yes      | POS auth token (raw JWT)                                                                                                                   |
| `callback_url`     | String  | No       | Trusted signed webhook URL. Must be strict `https://`, max 2048 chars. Receives a signed POST with `MONEI-Signature` HMAC on completion.   |
| `complete_url`     | String  | No       | URL opened on the device after the payment completes. Accepts `https://`, custom URL schemes, Universal Links. Max 2048 chars. Not signed. |
| `description`      | String  | No       | Payment description                                                                                                                        |
| `customer_name`    | String  | No       | Customer name                                                                                                                              |
| `customer_email`   | String  | No       | Customer email                                                                                                                             |
| `customer_phone`   | String  | No       | Customer phone                                                                                                                             |
| `order_id`         | String  | No       | Merchant order reference. Surfaced in the webhook callback for reconciliation. Max 2048 chars. If omitted, MONEI generates one.            |
| `transaction_type` | String  | No       | Optional transaction type: `SALE`, `AUTH`, `REFUND`, `CAPTURE`, `CANCEL`, `PAYOUT`, `VERIF`. Server-validated.                             |

#### `complete_url` Redirect Parameters (Success)[​](#complete_url-redirect-parameters-success "Direct link to complete_url-redirect-parameters-success")

| Parameter            | Type   | Description                            |
| -------------------- | ------ | -------------------------------------- |
| `success`            | String | `"true"`                               |
| `transaction_id`     | String | MONEI transaction ID                   |
| `amount`             | String | Payment amount in cents                |
| `card_brand`         | String | Card brand (e.g. `visa`, `mastercard`) |
| `masked_card_number` | String | Masked card number (e.g. `****1234`)   |

#### `complete_url` Redirect Parameters (Error)[​](#complete_url-redirect-parameters-error "Direct link to complete_url-redirect-parameters-error")

| Parameter | Type   | Description                                                                                                              |
| --------- | ------ | ------------------------------------------------------------------------------------------------------------------------ |
| `success` | String | `"false"`                                                                                                                |
| `error`   | String | Error code — see [error code list](https://docs.monei.com/monei-pay/app-integration/getting-started/.md#result-delivery) |

## Example app[​](#example-app "Direct link to Example app")

The [`examples/MerchantDemo`](https://github.com/MONEI/monei-pay-ios-sdk/tree/master/examples/MerchantDemo) directory contains a SwiftUI app demonstrating the full flow. Open `MerchantDemo.xcodeproj`, set your signing team, and run on a physical device with MONEI Pay installed. The project references the SDK as a local package, so SDK changes apply immediately.

## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting")

### `moneiPayNotInstalled`[​](#moneipaynotinstalled "Direct link to moneipaynotinstalled")

MONEI Pay must be installed on the device. Install from the [App Store](https://apps.apple.com/app/monei-pay/id1624647668).

### `failedToOpen`[​](#failedtoopen "Direct link to failedtoopen")

Verify `monei-pay` is listed in `LSApplicationQueriesSchemes` in your `Info.plist`. Without it, iOS blocks the `canOpenURL` check.

### `paymentTimeout`[​](#paymenttimeout "Direct link to paymenttimeout")

Default timeout is 60 seconds. Increase via the `timeout` parameter if needed. The timer uses wall-clock time — backgrounding your app does not pause it.

### Completion redirect not received[​](#completion-redirect-not-received "Direct link to Completion redirect not received")

Check that your URL scheme is registered under `CFBundleURLTypes` in `Info.plist`, and that `MoneiPay.handleCompleteRedirect(url:)` is called in `onOpenURL` (SwiftUI) or `application(_:open:options:)` (UIKit). See [Wire the Completion Handler](#3-wire-the-completion-handler).

### Payment works in simulator but not on device[​](#payment-works-in-simulator-but-not-on-device "Direct link to Payment works in simulator but not on device")

NFC payments require a physical device with MONEI Pay installed. Simulators are not supported.
