# 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[​](#example "Direct link to 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[​](#is-supported "Direct link to 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[​](#prepare "Direct link to Prepare the reader")

Call `TapToPay.prepare(token:)` on launch, and again after each [token renewal](https://docs.monei.com/monei-pay/in-app-tap-to-pay/backend.md#token-lifecycle).

* `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[​](#accept-payment "Direct link to Take a payment")

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

| Parameter     | Type   | Description                                                                                                                                                |
| ------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `amount`      | Int    | The amount in euro cents, more than 0. For example, `1050` is 10.50 EUR.                                                                                   |
| `orderId`     | String | Your 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. |
| `callbackUrl` | URL?   | The URL where MONEI sends the signed webhook with the payment result. Always set it. See [Confirm the payment with the webhook](#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`](#result) or throws a [`TapToPayError`](https://docs.monei.com/monei-pay/in-app-tap-to-pay/errors.md).

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](https://docs.monei.com/monei-pay/in-app-tap-to-pay/errors.md#outcome-unknown).

## Read the result[​](#result "Direct link to Read the result")

`acceptPayment` returns a `PaymentResult`:

| Property            | Type    | Description                                                                                                                                                  |
| ------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `paymentId`         | String  | The MONEI payment ID.                                                                                                                                        |
| `status`            | Status  | `.approved` or `.declined`.                                                                                                                                  |
| `cardBrand`         | String? | 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. |
| `last4`             | String? | The last 4 digits of the card, or `nil`.                                                                                                                     |
| `orderId`           | String  | The `orderId` of the payment.                                                                                                                                |
| `amount`            | Int     | The amount in cents.                                                                                                                                         |
| `currency`          | String  | The currency as an ISO 4217 code, for example `EUR`.                                                                                                         |
| `statusCode`        | String? | The MONEI status code, for example `E000`, or `nil`.                                                                                                         |
| `statusMessage`     | String? | The text of the MONEI status code, or `nil`. A declined result also has the reason, for example insufficient funds.                                          |
| `authorizationCode` | String? | The authorization code of an approved result, or `nil`. A declined result has no authorization code.                                                         |
| `cardType`          | String? | `credit`, `debit` or `prepaid`, or `nil`.                                                                                                                    |
| `cardCountry`       | String? | The country of the card as an ISO 3166-1 alpha-2 code, for example `ES`, or `nil`.                                                                           |

Only `status` tells you if the payment is approved. Use the other fields to show the result in your UI. The [signed webhook](#webhook) stays the only trusted payment result.

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`.

### Show the decline reason[​](#decline-reason "Direct link to Show the decline reason")

A declined result has the reason in `statusMessage`. Show it to the customer. You do not need a call to MONEI for it. `statusMessage` can be `nil`. Then show a general text.

```
func showDeclined(_ result: PaymentResult) {

  // statusMessage can be nil. Show a general text then.

  let reason = result.statusMessage ?? "The card was declined."

  showMessage("Payment declined: \(reason)")

}
```

## Confirm the payment with the webhook[​](#webhook "Direct link to 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](https://docs.monei.com/guides/verify-signature.md).
* 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](https://docs.monei.com/guides/webhooks.md#payment-callback).

## Show Apple's education screens[​](#education "Direct link to 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[​](#unknown-default "Direct link to 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[​](#api-reference "Direct link to API reference")

| Symbol                                                                                                  | Description                                                                                                                                                                                     |
| ------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `TapToPay.isSupported: Bool`                                                                            | `true` when the device supports Tap to Pay on iPhone.                                                                                                                                           |
| `TapToPay.prepare(token: String) async throws`                                                          | Stores 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 -> PaymentResult` | Takes one card payment. `amount` is in euro cents and must be more than 0. `orderId` must not be empty.                                                                                         |
| `TapToPay.presentEducation(from: UIViewController) async throws`                                        | Shows Apple's screens that teach how to tap a card.                                                                                                                                             |
| `PaymentResult`                                                                                         | `paymentId`, `status`, `cardBrand`, `last4`, `orderId`, `amount`, `currency`, `statusCode`, `statusMessage`, `authorizationCode`, `cardType` and `cardCountry`. See [Read the result](#result). |
| `TapToPayError`                                                                                         | The error that all calls throw. See [Handle errors and unclear outcomes](https://docs.monei.com/monei-pay/in-app-tap-to-pay/errors.md).                                                         |

## Common questions[​](#common-questions "Direct link to Common questions")

### Why does the first payment take longer?[​](#first-payment "Direct link to 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?[​](#foreground "Direct link to 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?[​](#new-order-id "Direct link to 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](https://docs.monei.com/monei-pay/in-app-tap-to-pay/errors.md#outcome-unknown).
