Skip to main content

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

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 completion redirect 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 1.0.0.

Or in Package.swift:

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

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

YourApp.swift
import SwiftUI
import MoneiPaySDK

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

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
callbackUrlString?NoTrusted signed webhook URL. Must be strict https://, max 2048 chars. See Result Delivery.
orderIdString?NoYour 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.
transactionTypeString?NoOptional transaction type: SALE (default), AUTH, REFUND, CAPTURE, CANCEL, PAYOUT, VERIF. Server-validated; invalid values are rejected.
completeSchemeStringYesYour app's registered URL scheme — used to redirect the user back after the payment completes
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, malformed callbackUrl or complete_url)
tokenExpiredPOS auth token has expired (>24h)
invalidTokenPOS auth token is malformed or signed by the wrong key
notAuthenticatedNo POS auth token and user is not signed into MONEI Pay
accountNotConfiguredMONEI Pay account is missing required POS configuration
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_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"]
}
}
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 to confirm the actual outcome before fulfilling an order. See Result Delivery.

URL Parameters

Request Parameters

ParameterTypeRequiredDescription
amountIntegerYesPayment amount in cents (e.g. 1500 = 15.00 EUR)
auth_tokenStringYesPOS auth token (raw JWT)
callback_urlStringNoTrusted signed webhook URL. Must be strict https://, max 2048 chars. Receives a signed POST with MONEI-Signature HMAC on completion.
complete_urlStringNoURL opened on the device after the payment completes. Accepts https://, custom URL schemes, Universal Links. Max 2048 chars. Not signed.
descriptionStringNoPayment description
customer_nameStringNoCustomer name
customer_emailStringNoCustomer email
customer_phoneStringNoCustomer phone
order_idStringNoMerchant order reference. Surfaced in the webhook callback for reconciliation. Max 2048 chars. If omitted, MONEI generates one.
transaction_typeStringNoOptional transaction type: SALE, AUTH, REFUND, CAPTURE, CANCEL, PAYOUT, VERIF. Server-validated.

complete_url Redirect 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)

complete_url Redirect Parameters (Error)

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