Skip to main content

Accept payments

The SDK has four calls: isSupported, prepare, acceptPayment and presentEducation. The API is @MainActor. Call it from the main actor, for example from a SwiftUI view.

Example​

import MoneiTapToPay

// On launch, and again after each token renewal.
func startTapToPay() async {
guard TapToPay.isSupported else { return } // Hide Tap to Pay on iPhone in your UI.
do {
let token = try await myServer.fetchPosToken() // The "token" field only.
try await TapToPay.prepare(token: token)
} catch {
// See "Handle errors and unclear outcomes".
}
}

// When the customer pays.
func pay(amountInCents: Int, orderId: String) async {
do {
let result = try await TapToPay.acceptPayment(
amount: amountInCents,
orderId: orderId,
callbackUrl: URL(string: "https://example.com/monei/webhook"))
switch result.status {
case .approved: showApproved(result)
case .declined: showDeclined(result)
@unknown default: showPending(orderId)
}
} catch TapToPayError.outcomeUnknown(let orderId) {
// Do not retry. Check this orderId in MONEI.
showPending(orderId)
} catch {
// See "Handle errors and unclear outcomes".
}
}

Check device support​

TapToPay.isSupported is true when the device supports Tap to Pay on iPhone: an iPhone XS or newer with iOS 18.5 or later. When it is false, hide Tap to Pay on iPhone in your UI. On an unsupported device, all calls throw notSupported.

Prepare the reader​

Call TapToPay.prepare(token:) on launch, and again after each token renewal.

  • prepare stores the token and prepares the reader in the background, so that the first payment starts faster.
  • The token stays in memory only. Call prepare again after each launch.
  • prepare does not show Apple's terms.
  • prepare asks for location permission if the user did not answer yet, and waits until the user answers. There is no time limit for the answer. If the user does not allow location access, prepare throws locationDenied. If Info.plist has no NSLocationWhenInUseUsageDescription, prepare throws locationDenied at once.
  • The SDK needs a location fix to prepare the reader. The first prepare or acceptPayment after launch waits up to 15 seconds for it. If the device gets no location in this time, the call throws paymentFailed(code: .locationTimeout).

When the app comes back to the foreground, the SDK prepares the reader again. You do not need to do this.

Take a payment​

Call TapToPay.acceptPayment(amount:orderId:callbackUrl:) when the customer pays.

ParameterTypeDescription
amountIntThe amount in euro cents, more than 0. For example, 1050 is 10.50 EUR.
orderIdStringYour own reference for the order. It must not be empty. Use a new orderId for each order. MONEI stores it with the payment, and you use it to reconcile.
callbackUrlURL?The URL where MONEI sends the signed webhook with the payment result. Always set it. See Confirm the payment with the webhook.

What occurs during a payment:

  1. If the account is not linked yet, the first acceptPayment on a device shows Apple's Tap to Pay on iPhone terms. The user must accept them.
  2. Apple shows the tap screen. The customer taps the card.
  3. MONEI processes the payment.
  4. acceptPayment returns a PaymentResult or throws a TapToPayError.

Only one payment can run at a time. During a payment, acceptPayment throws busy, so disable your pay button.

The SDK never retries a payment

If acceptPayment throws outcomeUnknown, the card can be charged. Do not retry, and do not charge again with a new orderId. See Unclear outcome.

Read the result​

acceptPayment returns a PaymentResult:

PropertyTypeDescription
paymentIdStringThe MONEI payment ID.
statusStatus.approved or .declined.
cardBrandString?The card network name in lowercase, for example visa or amex. unknown for a network that the SDK does not know. nil if the network is not available.
last4String?The last 4 digits of the card, or nil.
orderIdStringThe orderId of the payment.

A result with the status .declined is not an error. The card was read and the issuer declined the payment. The payment is in MONEI with its paymentId.

Confirm the payment with the webhook​

Always pass callbackUrl. MONEI sends a signed webhook with the payment result to this URL. The signed webhook is the only trusted payment result. Use the result in the app for the UI only.

  • Verify the signature on your server before you fulfil the order. See Verify signature.
  • The callbackUrl must use https, have 2048 characters or fewer, and its host must not be a private IP address. The SDK does not check the URL. MONEI checks it after the card read. If the URL is not valid, MONEI does not create the payment, and acceptPayment can throw outcomeUnknown. Make sure that the URL is valid before you take payments.
  • For when MONEI sends this webhook, see Payment callback.

Show Apple's education screens​

Call TapToPay.presentEducation(from:) to show Apple's screens that teach how to tap a card. Show them, for example, on first use and from your help menu.

try await TapToPay.presentEducation(from: viewController)

Switch statements​

The SDK uses library evolution, and its public enums are not frozen. A switch over PaymentResult.Status, TapToPayError or TapToPayErrorCode needs an @unknown default case. Without it, your app does not compile.

API reference​

SymbolDescription
TapToPay.isSupported: Booltrue when the device supports Tap to Pay on iPhone.
TapToPay.prepare(token: String) async throwsStores the token and prepares the reader in the background. It does not show Apple's terms. It asks for location permission if the user did not answer yet.
TapToPay.acceptPayment(amount: Int, orderId: String, callbackUrl: URL?) async throws -> PaymentResultTakes one card payment. amount is in euro cents and must be more than 0. orderId must not be empty.
TapToPay.presentEducation(from: UIViewController) async throwsShows Apple's screens that teach how to tap a card.
PaymentResultpaymentId, status, cardBrand, last4 and orderId. See Read the result.
TapToPayErrorThe error that all calls throw. See Handle errors and unclear outcomes.

Common questions​

Why does the first payment take longer?​

On the first launch, the SDK asks for location permission and waits for the answer of the user. Then it needs a location fix before it can prepare the reader, and it waits up to 15 seconds for it. On the first payment on a device, Apple can also show its Tap to Pay on iPhone terms. Call prepare on launch, so that the reader is ready before the customer pays.

Must I call prepare again when the app comes back from the background?​

No. The SDK prepares the reader again when the app comes back to the foreground. Call prepare again only after a launch and after a token renewal.

Does each order need a new orderId?​

Yes. Use a new, non-empty orderId for each order. MONEI stores it with the payment, and you use it to reconcile. After outcomeUnknown, do not charge again with a new orderId until you know the result of the first payment. See Unclear outcome.