Skip to main content

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

iOS Beta

MONEI Pay for iOS is in beta. Join TestFlight to install: https://testflight.apple.com/join/kZU2j445

No entitlements needed

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

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 callback and resolves a PaymentResult

Prerequisites

Integration

1. Install the SDK

In Xcode: File → Add Package Dependencies → enter https://github.com/MONEI/monei-pay-ios-sdkUp to Next Major Version from 0.2.2.

Or in Package.swift:

Package.swift
dependencies: [
.package(url: "https://github.com/MONEI/monei-pay-ios-sdk", from: "0.2.2")
]

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 Callback Handler

YourApp.swift
import SwiftUI
import MoneiPaySDK

@main
struct YourApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL { url in
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(...)

ParameterTypeRequiredDescription
tokenStringYesPOS auth token (raw JWT, no "Bearer " prefix)
amountIntYesPayment amount in cents (e.g. 1500 = 15.00 EUR)
descriptionString?NoPayment description
customerNameString?NoCustomer name
customerEmailString?NoCustomer email
customerPhoneString?NoCustomer phone
callbackSchemeStringYesYour app's registered URL scheme
timeoutTimeInterval?NoTimeout in seconds (default: 60)

PaymentResult

PropertyTypeDescription
transactionIdStringMONEI transaction ID
successBoolWhether the payment was approved
amountInt?Payment amount in cents
cardBrandString?Card brand (e.g. visa, mastercard)
maskedCardNumberString?Masked card number (e.g. ****1234)

Error Types

ErrorDescription
moneiPayNotInstalledMONEI Pay is not installed
paymentInProgressAnother payment is already active
paymentTimeoutNo response within the timeout
paymentCancelledUser cancelled
paymentFailed(reason:)Payment declined or failed
invalidParameters(_:)Invalid input (e.g. non-positive amount)
failedToOpenCould 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

ParameterTypeRequiredDescription
amountIntegerYesPayment amount in cents (e.g. 1500 = 15.00 EUR)
auth_tokenStringYesPOS auth token (raw JWT)
callbackStringNoCustom URL scheme where MONEI Pay sends the result. Must be a custom scheme (not http/https).
descriptionStringNoPayment description
customer_nameStringNoCustomer name
customer_emailStringNoCustomer email
customer_phoneStringNoCustomer phone

Callback Parameters (Success)

ParameterTypeDescription
successString"true"
transaction_idStringMONEI transaction ID
amountStringPayment amount in cents
card_brandStringCard brand (e.g. visa, mastercard)
masked_card_numberStringMasked card number (e.g. ****1234)

Callback Parameters (Error)

ParameterTypeDescription
successString"false"
errorStringError 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.