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
Uses standard URL schemes — no special entitlements, certificates, or Apple approvals required.
How It Works
- Your app calls
MoneiPay.acceptPayment(...)with a token and amount - The SDK opens MONEI Pay for the NFC tap-to-pay transaction
- MONEI Pay redirects back to your app with the result
- The SDK parses the completion redirect and resolves a
PaymentResult
Prerequisites
- MONEI account
- iOS 15.0+, Swift 5.9+
- MONEI Pay installed on the device
- POS auth token from your backend (see Getting Started)
Integration
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:
dependencies: [
.package(url: "https://github.com/MONEI/monei-pay-ios-sdk", from: "1.0.0")
]
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
<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>
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
- SwiftUI
- UIKit
import SwiftUI
import MoneiPaySDK
@main
struct YourApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL { url in
MoneiPay.handleCompleteRedirect(url: url)
}
}
}
}
import MoneiPaySDK
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
return MoneiPay.handleCompleteRedirect(url: url)
}
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)")
}
}
}
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 for details.
SDK Reference
MoneiPay.acceptPayment(...)
| 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. |
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
| 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 | 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 Scheme)
If you prefer not to use the SDK, you can integrate directly via URL schemes.
Show manual URL scheme integration
Open the Payment URL
func acceptPayment(amountInCents: Int) {
var components = URLComponents(string: "monei-pay://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
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"]
}
}
The query parameters on the complete_url redirect are not signed — they can be spoofed. Use callback_url (signed webhook) or Get Payment to confirm the actual outcome before fulfilling an order. See Result Delivery.
URL Parameters
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)
| 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)
| Parameter | Type | Description |
|---|---|---|
success | String | "false" |
error | String | Error code — see error code list |
Example App
The 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
moneiPayNotInstalled
MONEI Pay must be installed on the device. Install from the App Store.
failedToOpen
Verify monei-pay is listed in LSApplicationQueriesSchemes in your Info.plist. Without it, iOS blocks the canOpenURL check.
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
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.
Payment works in simulator but not on device
NFC payments require a physical device with MONEI Pay installed. Simulators are not supported.