# Transactions

To run a transaction, call `startTransaction(amount:currency:type:identityId:)`. The SDK presents Apple's tap sheet, reads the card, submits the payment to Finix, and returns the completed result. Progress updates arrive on the `transactionEvents` publisher so you can update your UI at each stage.

This page covers the full transaction lifecycle: preparing the reader, taking a payment, handling the result, and localizing the payment experience.

## Prepare the reader

`prepareReader()` configures Apple's payment card reader on the device: it authenticates with Finix, hands Apple a reader token, and brings up the secure card-reading session. The first preparation after install can take several seconds. Later preparations are much faster, and a prepared session is reused across transactions.

**Before you start:** the SDK must be [initialized](/ios/tap-to-pay/installation) and the merchant's Apple Account [linked](/ios/tap-to-pay/account-linking). The app must be in the foreground.

Apple expects the reader to be ready before the Buyer starts waiting, so warm it up ahead of checkout:

| When | Why |
|  --- | --- |
| At app launch or foreground (once linked) | Apple requires warm-up at launch or foreground so the first tap is fast. `autoPrepareOnForeground` only re-prepares when a session already exists, so your app is still responsible for the first `prepareReader()` call. |
| **After every transaction** | The SDK does not re-prepare automatically after a transaction completes. Call `prepareReader()` again after each transaction (for example, in a `defer` as shown in [Process a sale](#process-a-sale)) so the reader is ready for the next Buyer. |
| After `refreshConfiguration()` | Refreshing invalidates the reader session; prepare again before the next transaction. |
| Before retrying after `.readerPreparationFailed` | Resolve the cause (see [Troubleshooting and support](/ios/tap-to-pay/support)), then prepare again. |


## Warm-up pattern

Prepare once per foreground transition and avoid stacking duplicate calls. Call `warmUpIfNeeded` from your scene's foreground transition (for example, when `scenePhase` becomes `.active`). Only warm up once the merchant has completed Tap to Pay on iPhone setup: gate on your "device provisioned and linked" state so fresh installs don't attempt a preparation that will always fail.

If the user is waiting on a preparation (for example, during first-time setup), show an indeterminate progress indicator. The SDK does not report preparation progress percentages.

If preparation fails, `prepareReader()` throws `TapToPayError.readerPreparationFailed(String?)` with the underlying reason in the associated message, including Apple configuration codes such as 2011, 2012, and 2013. See the [Error reference](/ios/tap-to-pay/errors) and [Troubleshooting and support](/ios/tap-to-pay/support) for what each code means and how to recover.

## Take a payment

The `type` parameter is a `TransactionType`: `.sale` (the default), `.authorization`, or `.refund`. Each provides a `displayName` (`"Sale"`, `"Authorization"`, `"Refund"`) you can use for UI labels.

**Before you start:**

- The SDK is [initialized](/ios/tap-to-pay/installation) with an activated Device, the account is [linked](/ios/tap-to-pay/account-linking), and the reader is [prepared](#prepare-the-reader).
- Your app is in the **foreground**. Reader calls from the background throw `.backgroundRequestNotAllowed`.
- You are subscribed to `transactionEvents`. Subscribe **before** starting the transaction.


## Subscribe to transaction events

`transactionEvents` is a Combine publisher (`AnyPublisher<TapToPayTransactionEvent, Never>`) that emits the transaction's progress:

| Event | When it fires | Suggested UI |
|  --- | --- | --- |
| `.preparingTransaction` | The transaction is starting. | Show your processing screen now. Apple expects your UI to respond within about a second of the merchant starting a payment. |
| `.readingCard` | Apple's tap sheet is on screen, waiting for a tap. | No UI changes needed while Apple's sheet is on screen. |
| `.cardRead` | The card read completed; the tap sheet dismisses. | Keep your processing screen up. |
| `.processing` | The SDK is submitting the payment to Finix. | Keep your processing screen up. |
| `.success(TapToPayTransactionResult)` | The payment succeeded. | Show your approved screen. The result includes the Finix Transfer. See [Handle the result](#handle-the-result). |
| `.failure(TapToPayError)` | The payment failed. | Show your declined or error screen. See the [Error reference](/ios/tap-to-pay/errors). |


Handle all current cases and include `@unknown default` so future SDK versions can add events without breaking your build.

The publisher replays the latest event
A new subscriber immediately receives the most recent event, which can include a `.success` or `.failure` left over from a previous transaction. Subscribe before you call `startTransaction`, and ignore terminal events that arrive before you have started a new transaction. Tracking your own "transaction in progress" flag is a simple way to do this.

## Process a sale

Amounts are integers in the currency's minor unit: cents for USD (`1250` is $12.50).

If `startTransaction` returns, the payment succeeded. The result's `transferState` is `"SUCCEEDED"` and `transferId` identifies the Finix Transfer. Declines and processing failures throw `TapToPayError.transactionFailed` (and emit `.failure`) instead. The SDK submits the Transfer itself, so do not create another Transfer for the same payment.

The example re-prepares the reader in a `defer` because the SDK does not re-prepare automatically after a transaction. This keeps the reader ready for the next tap.

## Authorize now, capture later

Place a hold now and capture it later. This is useful for tabs or delayed fulfillment.

The tap experience is identical to a sale. The difference is in how Finix records the transaction. [Capture](/api/authorizations/captureauthorization), [void](/api/authorizations/voidauthorization), or let the authorization expire using the Finix API. See [Auth and captures](/guides/online-payments/payment-features/auth-and-captures) for the full flow.

## Refund to a tapped card

Return funds to a Buyer's card without referencing an earlier payment (an *unreferenced* refund). The Buyer taps the card that should receive the funds.

To refund a specific earlier payment instead, [reverse its Transfer](/api/transfers/createtransferreversal) through the Finix API using the `transferId` you stored from the original result.

## Associate a Buyer Identity

To tie the payment to a Finix Buyer Identity (such as a stored Buyer profile), pass its ID. `identityId` is optional and defaults to `nil`.

## Cancel a transaction

The Buyer can always dismiss Apple's tap sheet. When that happens, your `startTransaction` call throws `TapToPayError.transactionCancelled`. To cancel programmatically (for example, from a cancel button on your processing screen), call `cancelTransaction()`.

Handle the cancellation where you called `startTransaction` (as in the sale example above). No separate cancellation event is published.

## Handle the result

A returned `TapToPayTransactionResult` is an approved payment. Use its fields to build receipts and to reconcile the payment on your backend.

| Field | Type | Description |
|  --- | --- | --- |
| `amount` | `Int` | Amount in the currency's minor unit (cents for USD). |
| `currency` | `String` | Currency code, e.g. `"USD"`. |
| `cardBrand` | `String?` | Card brand, e.g. `"VISA"`, `"MASTERCARD"`. |
| `last4` | `String?` | Last four digits of the card number. |
| `maskedCardNumber` | `String?` | Masked PAN, e.g. `"**** **** **** 1234"`. |
| `cardType` | `String?` | Reserved. Currently `nil` on live transactions; do not rely on it. |
| `emvData` | `String?` | Reserved. Currently `nil` on live transactions; do not rely on it. |
| `timestamp` | `Date` | When the transaction completed. |
| `readerIdentifier` | `String?` | Apple reader identifier. Useful in support requests. |
| `transactionIdentifier` | `String?` | Apple transaction identifier. |
| `transferId` | `String?` | **The Finix `Transfer` ID**. The payment as it appears in the Finix Dashboard and API. |
| `transferState` | `String?` | Transfer state; `"SUCCEEDED"` on returned results. |
| `approvalCode` | `String?` | Processor approval code. |
| `traceId` | `String?` | Finix trace ID. Include it in support requests. |


`TapToPayTransactionResult` is `Equatable` and `Sendable`. You cannot construct one directly; in your tests, use the [`mock` factory](/ios/tap-to-pay/testing#write-unit-tests).

## Record and reconcile

Store a reference to the payment as soon as you have the result. Use the Finix `transferId` when present, and fall back to Apple's identifier otherwise.

On your backend, the payment is a regular Finix `Transfer`. [Fetch it](/api/transfers/gettransfer) any time to reconcile:


```bash
curl -i https://finix.sandbox-payments-api.com/transfers/{transfer_id} \
  -u {your_api_key}:{your_api_secret} \
  -H 'Accept: application/hal+json' \
  -H 'Finix-Version: 2022-02-01'
```

Tap to Pay on iPhone transfers include card-present details (brand, masked account number, approval code) with `entry_mode` `CONTACTLESS`.

## Receipts

Apple requires that merchants can offer the Buyer a digital receipt after every outcome, and the delivery method must keep the Buyer's contact details confidential. SMS, email, QR codes, and the share sheet all qualify. Render receipts from the result fields above (brand, last four, amount, approval code, timestamp). App Review checks for this. See [Prepare for App Review](/ios/tap-to-pay/app-review).

## Localize the payment experience

By default, Apple's tap sheet follows the device language. If your app has its own language selection (for example, a merchant who serves Buyers in another language), set the language on the SDK with `setUserInterfaceLanguage(_:)`.

Use BCP-47 tags
Pass hyphenated BCP-47 identifiers like `"fr-CA"`. Don't pass the output of `Locale.identifier`, which produces underscore-separated values like `fr_CA` that Apple's tap sheet does not accept. The SDK passes your tag through without validation.

Scope of the setting:

- It changes **Buyer-facing** screens only (the tap sheet and PIN-style prompts where applicable).
- Merchant-facing UI, including Apple's Terms and Conditions sheet, always follows the device language (an Apple platform behavior).
- SDK error messages are not localized; `errorDescription` strings are English. Map error cases to your own localized copy for user-facing messages.
- The setting persists on the SDK instance for subsequent transactions until you change it or pass `nil`.


**Next:** [Error reference](/ios/tap-to-pay/errors).