iOS App Integration
Accept NFC payments from your iOS app using the MoneiPaySDK. The SDK handles URL building, callback parsing, timeouts, and error handling.
Source: github.com/MONEI/monei-pay-ios-sdk
MONEI Pay for iOS is in beta. Join TestFlight to install: https://testflight.apple.com/join/kZU2j445
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 callback 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 0.2.2.
Or in Package.swift:
dependencies: [
.package(url: "https://github.com/MONEI/monei-pay-ios-sdk", from: "0.2.2")
]
pod 'MoneiPaySDK', :git => 'https://github.com/MONEI/monei-pay-ios-sdk.git', :tag => 'v0.2.2'
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 Callback Handler
- SwiftUI
- UIKit
import SwiftUI
import MoneiPaySDK
@main
struct YourApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL { url in
MoneiPay.handleCallback(url: url)
}
}
}
}
import MoneiPaySDK
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
return MoneiPay.handleCallback(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",
callbackScheme: "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 .paymentFailed(let reason):
print("Payment failed: \(reason ?? "unknown")")
default:
print("Error: \(error.localizedDescription)")
}
}
}
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 |
callbackScheme | String | Yes | Your app's registered URL scheme |
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) |
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", value: "your-app://payment-result")
]
guard let url = components.url else { return }
UIApplication.shared.open(url)
}
Handle the Callback
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"]
}
}
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 | String | No | Custom URL scheme where MONEI Pay sends the result. Must be a custom scheme (not http/https). |
description | String | No | Payment description |
customer_name | String | No | Customer name |
customer_email | String | No | Customer email |
customer_phone | String | No | Customer phone |
Callback 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) |
Callback Parameters (Error)
| Parameter | Type | Description |
|---|---|---|
success | String | "false" |
error | String | Error code (e.g. PAYMENT_FAILED, INVALID_AMOUNT, NOT_AUTHENTICATED) |
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. During beta, install via TestFlight: https://testflight.apple.com/join/kZU2j445
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.
Callback not received
Check that your URL scheme is registered under CFBundleURLTypes in Info.plist, and that MoneiPay.handleCallback(url:) is called in onOpenURL (SwiftUI) or application(_:open:options:) (UIKit). See Wire the Callback Handler.
Payment works in simulator but not on device
NFC payments require a physical device with MONEI Pay installed. Simulators are not supported.