# Integration Guide

Integrate the Finix Payment Sheet iOS SDK into your own iOS app.

**Before you start**, complete the [Getting Started](/ios/online/getting-started) guide. You'll need:

- A Finix Application ID (`APxxxxxxxxxxxxx`) for each environment you plan to use.
- API credentials (username and password) for both the Sandbox and Production environments. You'll use Sandbox during development and swap in Production credentials when you go live.
- An approved merchant with a Merchant ID (`MUxxxxxxxxxxxxx`) and a display name.


The [Demo App Walkthrough](/ios/online/demo-app-walkthrough) is optional but recommended as a working reference before you build your own integration.

## 1. Collect your SDK values

Collect the values specified in the [Getting Started guide](/ios/online/getting-started) before you initialize the SDK and start making API calls from your backend. Gather one set of values per environment (Sandbox for development, Production for launch); you'll swap the Production set in when you go live.

Your `applicationId` and the credentials your backend uses must all belong to the same Finix environment (Sandbox or Production). On the iOS SDK, Finix's Production environment is represented by `FinixAPIEndpoint.Live`. Mixing environments is a common cause of authentication and token errors.

Keep API credentials off the client when possible
API credentials embedded in an app binary can be extracted. For the normal tokenization flow the iOS SDK only needs the Application ID — keep your API username and password on your backend and have the backend create Payment Instruments from the tokens returned by the SDK.

Enabling 3D Secure is the exception: the SDK calls `/3d_secure_sessions` directly from the device, so the 3DS-enabled `Configuration` initializer requires the merchant ID, API username, API password, amount, and currency. See [Step 10](#10-enable-3d-secure-optional). Treat those credentials as Sandbox-only or use a scoped, rotatable key for mobile use.

## 2. Install and Initialize the SDK

### System requirements

| Requirement | Value |
|  --- | --- |
| iOS | 15.0+ |
| Xcode | 15.0+ |
| Swift | 5.9+ |


### Add the package

The SDK is distributed via Swift Package Manager.

1. In Xcode, choose **File → Add Package Dependencies…**
2. Enter the package URL: `https://github.com/finix-payments/finix-paymentsheet-ios-sdk`
3. Select **Up to Next Major Version** starting at `1.0.0`.


Or add it to your `Package.swift`:


```swift
dependencies: [
    .package(url: "https://github.com/finix-payments/finix-paymentsheet-ios-sdk", from: "1.0.0"),
],
targets: [
    .target(
        name: "YourApp",
        dependencies: [
            .product(name: "FinixPaymentSheet", package: "finix-paymentsheet-ios-sdk"),
        ]
    ),
]
```

Then import the SDK in your source files:


```swift
import FinixPaymentSheet
```

No bridging headers or additional configuration are required.

### Initialize PaymentAction

Build a `FinixCredentials` with the Application ID gathered from [Step 1](#1-collect-your-sdk-values), then create a `PaymentAction` once per view controller:


```swift
let credentials = FinixCredentials(
    applicationId: "APxxxxxxxxxxxxx",
    environment: .Sandbox // use .Live for live payments
)

paymentAction = PaymentAction(credentials: credentials, delegate: self)
```

Initialization makes no network calls; the first request happens when you present a payment sheet. See the [PaymentAction](/ios/online/payment-action) reference for the full initializer signature.

## 3. Pick a Form Style

Pick a [Form Style](/ios/online/form-styles) to control which fields the buyer sees:

| Style | Swift Value | Fields Collected |
|  --- | --- | --- |
| **Minimal** | `.minimal` | Card Number, Expiration, CVV |
| **Basic** | `.basic` | Name, Card Number, Expiration, CVV |
| **Partial** | `.partial` | Name, Card Number, Expiration, CVV, ZIP |
| **Complete** | `.complete` | Name, Card Number, Expiration, CVV, Full Address |


For ACH, skip the Form Style and use the Bank Account sheet directly in the next step.

## 4. Present the Payment Sheet

Build the sheet with `paymentSheet(style:)` (for cards) or `bankPaymentSheet()` (for ACH), then display it with `present(from:paymentSheet:animated:)`. The `from:` argument is the `UIViewController` that will present the sheet modally; in a UIKit view controller this is usually `self`. In a SwiftUI app, resolve the current top view controller through the connected `UIWindowScene`.

### Card


```swift
func showPaymentSheet() {
    let sheet = paymentAction.paymentSheet(style: .complete)
    paymentAction.present(from: self, paymentSheet: sheet, animated: true)
}
```

### Bank Account


```swift
func showBankSheet() {
    let bankSheet = paymentAction.bankPaymentSheet()
    paymentAction.present(from: self, paymentSheet: bankSheet, animated: true)
}
```

Modal is the default and recommended presentation. The [Demo App Walkthrough](/ios/online/demo-app-walkthrough#presentation-styles) also shows a push-style wrapper that fits inside an existing navigation flow. Use it as a reference if you need to drop the sheet into a multi-step checkout.

## 5. Get the Token

Conform to `PaymentActionDelegate` to receive tokenization results. On success, you'll receive a [TokenizedResponse](/ios/online/token-response) containing the token ID; forward it to your backend to use in the next steps.


```swift
extension CheckoutViewController: PaymentActionDelegate {
    func didSucceed(paymentController: PaymentInputController, instrument: TokenizedResponse) {
        // Send instrument.id to your backend to use in the next steps.
        sendTokenToBackend(tokenId: instrument.id)
    }

    func didCancel(paymentController: PaymentInputController) {
        // Buyer dismissed the sheet.
    }

    func didFail(paymentController: PaymentInputController, error: Error) {
        // Show an error to the buyer and let them retry.
        print("Tokenization failed: \(error.localizedDescription)")
    }
}
```

See the [TokenizedResponse](/ios/online/token-response) reference for the full response shape and the [PaymentAction delegate reference](/ios/online/payment-action#param-delegate) for the full delegate contract.

Token Expiration
Tokens expire **30 minutes** after creation. Use the token to create a Payment Instrument as soon as your backend receives it.

## 6. Use a Buyer Identity

A Buyer Identity is required before you can process a payment. You can create a new Buyer Identity with the Finix API, or use an existing one.

Buyer Identity data
All Buyer Identity fields are optional. Finix recommends including basic information (name, email, address, and phone) to make payment operations easier.

Use an existing Buyer
To find and reuse an existing Buyer Identity:

1. Log in to your [Finix Dashboard](https://finix.payments-dashboard.com/Login).
2. In the left sidebar, select **Data Resources** → **Identities**.
3. Search for the Buyer Identity you want to use, and confirm its **Role** is **Buyer**.
4. Copy the Buyer Identity ID (for example, `IDjWktr7BPDGhz4amrPJZoXg`).


Create a new Buyer
Send a `POST` request to POST /identities with the `BUYER` role to create a new Buyer Identity.


```shell Buyer Identity Request
curl -i -X POST \
  -u USfdccsr1Z5iVbXDyYt7hjZZ:313636f3-fac2-45a7-bff7-a334b93e7bda \
  https://finix.sandbox-payments-api.com/identities \
  -H 'Content-Type: application/json' \
  -H 'Finix-Version: 2022-02-01' \
  -d '{
    "entity": {
      "phone": "7145677613",
      "first_name": "John",
      "last_name": "Smith",
      "email": "finix_example@finix.com",
      "personal_address": {
        "city": "San Mateo",
        "country": "USA",
        "region": "CA",
        "line2": "Apartment 7",
        "line1": "741 Douglass St",
        "postal_code": "94114"
      }
    },
    "identity_roles": [
      "BUYER"
    ],
    "tags": {
      "key": "value"
    },
    "type": "PERSONAL"
  }'
```

A successful request returns the Buyer Identity. Copy its `id` to use in the following steps.


```json Buyer Identity Response
{
  "id": "IDmkDvJfxZWMEK56FDFtbozo",
  "created_at": "2024-08-09T09:34:36.87Z",
  "updated_at": "2024-08-09T09:34:36.87Z",
  "application": "APgPDQrLD52TYvqazjHJJchM",
  "entity": {
    "ach_max_transaction_amount": 0,
    "amex_mid": null,
    "annual_card_volume": 0,
    "business_address": null,
    "business_name": null,
    "business_phone": null,
    "business_tax_id_provided": false,
    "business_type": null,
    "default_statement_descriptor": null,
    "discover_mid": null,
    "dob": null,
    "doing_business_as": null,
    "email": "finix_example@finix.com",
    "first_name": "John",
    "has_accepted_credit_cards_previously": false,
    "incorporation_date": null,
    "last_name": "Smith",
    "max_transaction_amount": 0,
    "mcc": null,
    "ownership_type": null,
    "personal_address": {
      "line1": "741 Douglass St",
      "line2": "Apartment 7",
      "city": "San Mateo",
      "region": "CA",
      "postal_code": "94114",
      "country": "USA"
    },
    "phone": "7145677613",
    "principal_percentage_ownership": null,
    "short_business_name": null,
    "tax_authority": null,
    "tax_id_provided": false,
    "title": null,
    "url": null
  },
  "identity_roles": [
    "BUYER"
  ],
  "tags": {
    "key": "value"
  },
  "type": "PERSONAL",
  "_links": {
    "self": {
      "href": "https://finix.sandbox-payments-api.com/identities/IDmkDvJfxZWMEK56FDFtbozo"
    },
    "verifications": {
      "href": "https://finix.sandbox-payments-api.com/identities/IDmkDvJfxZWMEK56FDFtbozo/verifications"
    },
    "merchants": {
      "href": "https://finix.sandbox-payments-api.com/identities/IDmkDvJfxZWMEK56FDFtbozo/merchants"
    },
    "settlements": {
      "href": "https://finix.sandbox-payments-api.com/identities/IDmkDvJfxZWMEK56FDFtbozo/settlements"
    },
    "authorizations": {
      "href": "https://finix.sandbox-payments-api.com/identities/IDmkDvJfxZWMEK56FDFtbozo/authorizations"
    },
    "transfers": {
      "href": "https://finix.sandbox-payments-api.com/identities/IDmkDvJfxZWMEK56FDFtbozo/transfers"
    },
    "payment_instruments": {
      "href": "https://finix.sandbox-payments-api.com/identities/IDmkDvJfxZWMEK56FDFtbozo/payment_instruments"
    },
    "associated_identities": {
      "href": "https://finix.sandbox-payments-api.com/identities/IDmkDvJfxZWMEK56FDFtbozo/associated_identities"
    },
    "disputes": {
      "href": "https://finix.sandbox-payments-api.com/identities/IDmkDvJfxZWMEK56FDFtbozo/disputes"
    },
    "application": {
      "href": "https://finix.sandbox-payments-api.com/applications/APgPDQrLD52TYvqazjHJJchM"
    }
  }
}
```

## 7. Create a Payment Instrument with Finix

After tokenization, use the returned token to create a Payment Instrument. Pass the token in the `token` field and set `type` to `TOKEN`. Pick the request that matches what you tokenized: card details via `paymentSheet(style:)`, or bank account details via `bankPaymentSheet()`.

A successful request returns the newly created Payment Instrument. Copy its `id`; you'll use it to process the payment.

Card Response
Payment Instrument - Token - Card

```json Payment Instrument - Token - Card
{
  "id": "PImmCg3Po7oNi7jaZcXhfkEu",
  "created_at": "2022-10-10T05:32:17.78Z",
  "updated_at": "2022-10-10T05:32:17.78Z",
  "account_updater_enabled": false,
  "application": "APgPDQrLD52TYvqazjHJJchM",
  "created_via": "API",
  "currency": "USD",
  "enabled": true,
  "fingerprint": "FPRiCenDk2SoRng7WjQTr7RJY",
  "identity": "IDjvxGeXBLKH1V9YnWm1CS4n",
  "instrument_type": "PAYMENT_CARD",
  "address": {
    "line1": "900 Metro Center Blv",
    "line2": null,
    "city": "San Francisco",
    "region": "CA",
    "postal_code": "94404",
    "country": "USA"
  },
  "address_verification": "UNKNOWN",
  "bin": "520082",
  "brand": "MASTERCARD",
  "card_type": "DEBIT",
  "expiration_month": 12,
  "expiration_year": 2029,
  "issuer_country": "USA",
  "last_four": "8210",
  "name": "Amy White",
  "network_token_enabled": false,
  "network_token_state": "NOT_ENABLED",
  "security_code_verification": "UNKNOWN",
  "tags": {
    "card_name": "Business Card"
  },
  "type": "PAYMENT_CARD",
  "_links": {
    "self": {
      "href": "https://finix.sandbox-payments-api.com/payment_instruments/PImmCg3Po7oNi7jaZcXhfkEu"
    },
    "authorizations": {
      "href": "https://finix.sandbox-payments-api.com/payment_instruments/PImmCg3Po7oNi7jaZcXhfkEu/authorizations"
    },
    "transfers": {
      "href": "https://finix.sandbox-payments-api.com/payment_instruments/PImmCg3Po7oNi7jaZcXhfkEu/transfers"
    },
    "verifications": {
      "href": "https://finix.sandbox-payments-api.com/payment_instruments/PImmCg3Po7oNi7jaZcXhfkEu/verifications"
    },
    "application": {
      "href": "https://finix.sandbox-payments-api.com/applications/APgPDQrLD52TYvqazjHJJchM"
    },
    "identity": {
      "href": "https://finix.sandbox-payments-api.com/identities/IDjvxGeXBLKH1V9YnWm1CS4n"
    },
    "updates": {
      "href": "https://finix.sandbox-payments-api.com/payment_instruments/PImmCg3Po7oNi7jaZcXhfkEu/updates"
    }
  }
}
```

Bank Account Response
Payment Instrument - Token - Bank Account

```json Payment Instrument - Token - Bank Account
{
  "id": "PIkfiTDWYhQ4VbtQkC1U1ceJ",
  "created_at": "2024-06-21T04:09:03.77Z",
  "updated_at": "2024-06-21T04:09:03.77Z",
  "application": "APgPDQrLD52TYvqazjHJJchM",
  "created_via": "API",
  "currency": "USD",
  "disabled_code": null,
  "disabled_message": null,
  "enabled": true,
  "fingerprint": "FPRxpMNPZbZqVQfcHzRUJnzkR",
  "identity": "IDjvxGeXBLKH1V9YnWm1CS4n",
  "instrument_type": "BANK_ACCOUNT",
  "account_type": "BUSINESS_CHECKING",
  "bank_account_validation_check": "NOT_ATTEMPTED",
  "bank_code": "122105278",
  "country": "USA",
  "institution_number": null,
  "masked_account_number": "XXXXXX0019",
  "name": "Smith & Associates Consulting",
  "transit_number": null,
  "tags": {},
  "third_party": null,
  "third_party_token": null,
  "type": "BANK_ACCOUNT",
  "_links": {
    "self": {
      "href": "https://finix.sandbox-payments-api.com/payment_instruments/PIkfiTDWYhQ4VbtQkC1U1ceJ"
    },
    "authorizations": {
      "href": "https://finix.sandbox-payments-api.com/payment_instruments/PIkfiTDWYhQ4VbtQkC1U1ceJ/authorizations"
    },
    "transfers": {
      "href": "https://finix.sandbox-payments-api.com/payment_instruments/PIkfiTDWYhQ4VbtQkC1U1ceJ/transfers"
    },
    "verifications": {
      "href": "https://finix.sandbox-payments-api.com/payment_instruments/PIkfiTDWYhQ4VbtQkC1U1ceJ/verifications"
    },
    "application": {
      "href": "https://finix.sandbox-payments-api.com/applications/APgPDQrLD52TYvqazjHJJchM"
    },
    "identity": {
      "href": "https://finix.sandbox-payments-api.com/identities/IDjvxGeXBLKH1V9YnWm1CS4n"
    }
  }
}
```

## 8. Process a payment

With the Payment Instrument created, charge the buyer by creating a Transfer (immediate sale) or an Authorization (reserve funds now, capture later). In both cases, set `source` to the Payment Instrument ID from [Step 7](#7-create-a-payment-instrument-with-finix) and set `merchant` to an `APPROVED` Merchant ID.

### Transfer

Create a Transfer to charge the buyer immediately. Funds move from the Payment Instrument to your merchant in a single call.

Example
API Definition
A successful request returns the Transfer with its current `state`.

Transfer - Minimal Fields

```json Transfer - Minimal Fields
{
  "id": "TRhGHpgpTkFhNVNQsDX6QdfD",
  "created_at": "2026-09-25T20:06:00.46Z",
  "updated_at": "2026-09-25T20:06:00.46Z",
  "3d_secure_session": null,
  "additional_buyer_charges": null,
  "additional_healthcare_data": null,
  "additional_lodging_data": null,
  "additional_motor_vehicle_rental_data": null,
  "additional_purchase_data": null,
  "address_verification": "POSTAL_CODE_AND_STREET_MATCH",
  "amount": 10000,
  "amount_requested": 10000,
  "application": "APc9vhYcPsRuTSpKD9KpMtPe",
  "created_via": "UNKNOWN",
  "currency": "USD",
  "destination": null,
  "externally_funded": "UNKNOWN",
  "failure_code": null,
  "failure_message": null,
  "fee": 0,
  "fee_profile": "FPuizPqrhzYLbmJm88u7aqfj",
  "idempotency_id": null,
  "ip_address_details": null,
  "merchant": "MUmfEGv5bMpSJ9k5TFRUjkmm",
  "merchant_identity": "ID6UfSm1d4WPiWgLYmbyeo3H",
  "messages": [],
  "network_details": {
    "brand": "VISA",
    "authorization_code": "153601",
    "acquirer_reference_number": null
  },
  "operation_key": "CARD_NOT_PRESENT_SALE",
  "parent_transfer": null,
  "parent_transfer_trace_id": null,
  "processed_at": "2026-09-25T20:06:00.81Z",
  "raw": null,
  "ready_to_settle_at": "2026-09-26T20:06:00.46Z",
  "receipt_last_printed_at": null,
  "security_code_verification": "MATCHED",
  "source": "PI6iQcTtJNCS8GZAVKYi5Ueb",
  "split_transfers": [],
  "state": "SUCCEEDED", // [!code highlight]
  "statement_descriptor": "FLX*FINIX FLOWERS",
  "subtype": "API",
  "supplemental_fee": null,
  "tags": {},
  "third_party_details": null,
  "tip_amount": null,
  "trace_id": "515841cd-5305-409e-90fe-cfa711b3bb10",
  "type": "DEBIT", // [!code highlight]
  "_links": {
    "application": {
      "href": "https://finix.sandbox-payments-api.com/applications/APc9vhYcPsRuTSpKD9KpMtPe"
    },
    "self": {
      "href": "https://finix.sandbox-payments-api.com/transfers/TRhGHpgpTkFhNVNQsDX6QdfD"
    },
    "merchant_identity": {
      "href": "https://finix.sandbox-payments-api.com/identities/ID6UfSm1d4WPiWgLYmbyeo3H"
    },
    "payment_instruments": {
      "href": "https://finix.sandbox-payments-api.com/transfers/TRhGHpgpTkFhNVNQsDX6QdfD/payment_instruments"
    },
    "reversals": {
      "href": "https://finix.sandbox-payments-api.com/transfers/TRhGHpgpTkFhNVNQsDX6QdfD/reversals"
    },
    "fees": {
      "href": "https://finix.sandbox-payments-api.com/fees?linked_to=TRhGHpgpTkFhNVNQsDX6QdfD"
    },
    "disputes": {
      "href": "https://finix.sandbox-payments-api.com/transfers/TRhGHpgpTkFhNVNQsDX6QdfD/disputes"
    },
    "source": {
      "href": "https://finix.sandbox-payments-api.com/payment_instruments/PI6iQcTtJNCS8GZAVKYi5Ueb"
    },
    "fee_profile": {
      "href": "https://finix.sandbox-payments-api.com/fee_profiles/FPuizPqrhzYLbmJm88u7aqfj"
    }
  }
}
```

### Authorization

Create an Authorization to reserve funds now, then capture them later (for example, after you ship the order). Use the same `source` and `merchant` as a Transfer.

Example
API Definition
A successful request returns an Authorization in the `SUCCEEDED` state with funds reserved but not yet captured. Copy its `id`; you'll pass it to the capture call below.

Authorization - Minimal Fields

```json Authorization - Minimal Fields
{
  "id": "AUibEDBG2DzmPJ3L75pxfFu2",
  "created_at": "2026-09-25T20:05:04.31Z",
  "updated_at": "2026-09-25T20:05:04.31Z",
  "3d_secure_session": null,
  "3ds_redirect_url": null,
  "additional_buyer_charges": null,
  "additional_healthcare_data": null,
  "additional_lodging_data": null,
  "additional_motor_vehicle_rental_data": null,
  "additional_purchase_data": null,
  "address_verification": "POSTAL_CODE_AND_STREET_MATCH",
  "amount": 10000,
  "amount_requested": 10000,
  "application": "APc9vhYcPsRuTSpKD9KpMtPe",
  "created_via": "UNKNOWN",
  "currency": "USD",
  "expires_at": "2026-10-05T20:05:04.31Z",
  "failure_code": null,
  "failure_message": null,
  "idempotency_id": null,
  "ip_address_details": null,
  "is_void": false,
  "merchant": "MUmfEGv5bMpSJ9k5TFRUjkmm",
  "merchant_identity": "ID6UfSm1d4WPiWgLYmbyeo3H",
  "messages": [],
  "network_details": {
    "brand": "VISA",
    "authorization_code": "766600"
  },
  "raw": null,
  "receipt_last_printed_at": null,
  "security_code_verification": "MATCHED",
  "source": "PIkxmtueemLD6dN9ZoWGHT44",
  "state": "SUCCEEDED",
  "supplemental_fee": null,
  "tags": {},
  "trace_id": "72e93698-4cd9-4ccd-bb1c-a965fb5c9cef",
  "transfer": null,
  "void_state": "UNATTEMPTED",
  "_links": {
    "self": {
      "href": "https://finix.sandbox-payments-api.com/authorizations/AUibEDBG2DzmPJ3L75pxfFu2"
    },
    "application": {
      "href": "https://finix.sandbox-payments-api.com/applications/APc9vhYcPsRuTSpKD9KpMtPe"
    },
    "merchant_identity": {
      "href": "https://finix.sandbox-payments-api.com/identities/ID6UfSm1d4WPiWgLYmbyeo3H"
    }
  }
}
```

#### Capture the Authorization

Send a PUT /authorizations/{id} with the `capture_amount` (in cents) to settle the Authorization. The capture produces a Transfer referenced by the Authorization's `transfer` field.

Example
API Definition
Captured Authorization

```json Captured Authorization
{
  "id": "AUbWeT4YfQRgjKwfXk1pwb5E",
  "created_at": "2026-09-25T20:36:20.39Z",
  "updated_at": "2026-09-25T20:36:21.28Z",
  "3d_secure_session": null,
  "3ds_redirect_url": null,
  "additional_buyer_charges": null,
  "additional_healthcare_data": null,
  "additional_lodging_data": null,
  "additional_motor_vehicle_rental_data": null,
  "additional_purchase_data": null,
  "address_verification": "POSTAL_CODE_AND_STREET_MATCH",
  "amount": 10000,
  "amount_requested": 10000,
  "application": "APc9vhYcPsRuTSpKD9KpMtPe",
  "created_via": "UNKNOWN",
  "currency": "USD",
  "expires_at": "2026-10-05T20:36:20.39Z",
  "failure_code": null,
  "failure_message": null,
  "idempotency_id": null,
  "ip_address_details": null,
  "is_void": false,
  "merchant": "MUmfEGv5bMpSJ9k5TFRUjkmm",
  "merchant_identity": "ID6UfSm1d4WPiWgLYmbyeo3H",
  "messages": [],
  "network_details": {
    "brand": "VISA",
    "authorization_code": "004086"
  },
  "raw": null,
  "receipt_last_printed_at": null,
  "security_code_verification": "MATCHED",
  "source": "PIkxmtueemLD6dN9ZoWGHT44",
  "state": "SUCCEEDED", // [!code highlight]
  "supplemental_fee": null,
  "tags": {},
  "trace_id": "6aa71f62-504c-40e3-8598-c074f98b3183",
  "transfer": "TRj2ac3ocT3UKfSCvNt2eGFr", // [!code highlight]
  "void_state": "UNATTEMPTED",
  "_links": {
    "self": {
      "href": "https://finix.sandbox-payments-api.com/authorizations/AUbWeT4YfQRgjKwfXk1pwb5E"
    },
    "application": {
      "href": "https://finix.sandbox-payments-api.com/applications/APc9vhYcPsRuTSpKD9KpMtPe"
    },
    "transfer": {
      "href": "https://finix.sandbox-payments-api.com/transfers/TRj2ac3ocT3UKfSCvNt2eGFr"
    },
    "merchant_identity": {
      "href": "https://finix.sandbox-payments-api.com/identities/ID6UfSm1d4WPiWgLYmbyeo3H"
    }
  }
}
```

For the full lifecycle, including voids and partial captures, see [Creating and capturing an Authorization](/guides/online-payments/payment-features/auth-and-captures).

## 9. Customize the Payment Sheet (Optional)

Pass a [Configuration](/ios/online/configuration) to `PaymentAction` to override the sheet title, submit button label, header branding, and card scanning behavior. Both `Configuration` and `Branding` are nested on `PaymentInputController`:


```swift
let branding = PaymentInputController.Branding(
    image: UIImage(named: "MyLogo"),
    title: "My Company"
)

let configuration = PaymentInputController.Configuration(
    title: "Pay Now",
    branding: branding,
    buttonTitle: "Submit",
    enableCardScanning: true
)

paymentAction = PaymentAction(
    credentials: credentials,
    delegate: self,
    configuration: configuration
)
```

### Card scanning

Card scanning is enabled by default. To use it, add a camera usage description to your app. Use the tab that matches how your project manages `Info.plist`.

Xcode Info tab (UI)
In Xcode, select the app target, open the **Info** tab, hover any row, and click **+**. Add:

| Key | Type | Value |
|  --- | --- | --- |
| `Privacy - Camera Usage Description` | String | `Used to scan credit cards for faster entry.` |


Info.plist (XML)

```xml
<key>NSCameraUsageDescription</key>
<string>Used to scan credit cards for faster entry.</string>
```

Without this key, iOS denies camera access and the scanner won't work. To hide the scan button entirely, set `enableCardScanning: false` on `Configuration`. See the [Card Scanner](/ios/online/card-scanner) reference for details.

## 10. Enable 3D Secure (Optional)

Enable [3D Secure (3DS)](/guides/online-payments/fraud-and-risk/3d-secure) to run cardholder authentication after tokenization. 3DS adds a session, an optional `WKWebView` challenge, and liability-shift metadata to the token response.

### Register the redirect URL scheme

The issuer's challenge page redirects back to your app using a custom URL scheme. Register the scheme you'll pass as `ThreeDSConfiguration.redirectScheme`. Use the tab that matches how your project manages `Info.plist`.

Xcode Info tab (UI)
1. In Xcode, select the app target and open the **Info** tab. Add a parent key:


| Key | Type | Value |
|  --- | --- | --- |
| `URL types` | Array | (empty) |


1. Expand the row (click the ▸), hover inside, and click **+** to add **Item 0** as a **Dictionary**. Inside that dictionary, add:


| Key | Type | Value |
|  --- | --- | --- |
| `URL Schemes` | Array | (empty) |


1. Expand it and add **Item 0** as a **String** with your scheme (e.g., `yourappscheme`).


Info.plist (XML)

```xml
<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>yourappscheme</string>
        </array>
    </dict>
</array>
```

If the scheme is missing or doesn't match `redirectScheme`, the challenge window stays open until the SDK's 5-minute timeout fires `didFail` with `ThreeDSTimeout`.

### Forward incoming redirect URLs to the SDK

Registering the scheme lets iOS route the issuer's redirect to your app. Your app must then hand that URL to `PaymentAction.handleThreeDSRedirect(url:)` so the SDK can finish the challenge and resolve the session. Without this hop, the SDK never sees the result and the 3DS flow times out.

The method returns `Bool` indicating whether the SDK consumed the URL. Use that return value if your app also handles its own URLs; discard it with `_ =` otherwise.

SwiftUI

```swift
import SwiftUI
import FinixPaymentSheet

@main
struct YourApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .onOpenURL { url in
                    _ = PaymentAction.handleThreeDSRedirect(url: url)
                }
        }
    }
}
```

UIKit (SceneDelegate)

```swift
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
    for context in URLContexts {
        _ = PaymentAction.handleThreeDSRedirect(url: context.url)
    }
}
```

Also check `connectionOptions.urlContexts` in `scene(_:willConnectTo:options:)` when the scene is created with a URL.

UIKit (AppDelegate)

```swift
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
    return PaymentAction.handleThreeDSRedirect(url: url)
}
```

### Configure and handle 3DS

The 3DS-enabled `Configuration` initializer takes more arguments than the plain one. In addition to the usual title/branding/button/scanning fields, it requires a `ThreeDSConfiguration`, the merchant ID, your API credentials, the transaction amount in cents, and the currency, because the SDK calls `/3d_secure_sessions` directly from the device.

Then use the `PaymentSheetResponse`-based success callback in your delegate. The 3DS result arrives as `response.threeDSResponse`:


```swift
let threeDSConfig = ThreeDSConfiguration(redirectScheme: "yourappscheme")

let configuration = PaymentInputController.Configuration(
    title: "Pay Now",
    branding: PaymentInputController.Branding(image: nil, title: "My Company"),
    buttonTitle: "Submit",
    enableCardScanning: true,
    threeDSConfiguration: threeDSConfig,
    merchantId: "MUxxxxxxxxxxxxx",
    apiUsername: "USxxxxxxxxxxxxx",
    apiPassword: "{api_password}",
    amount: 100,          // cents
    currency: "USD"
)

paymentAction = PaymentAction(
    credentials: credentials,
    delegate: self,
    configuration: configuration
)

func didSucceed(paymentController: PaymentInputController, response: PaymentSheetResponse) {
    let tokenId = response.tokenizedResponse.id
    if let threeDS = response.threeDSResponse {
        // Forward both the token and the 3DS session ID to your backend.
        sendTokenToBackend(tokenId: tokenId, threeDSSessionId: threeDS.sessionId)
    } else {
        sendTokenToBackend(tokenId: tokenId, threeDSSessionId: nil)
    }
}
```

`PaymentActionDelegate` exposes two success callbacks: `didSucceed(paymentController:, instrument: TokenizedResponse)` (used in [Step 5](#5-get-the-token)) and `didSucceed(paymentController:, response: PaymentSheetResponse)`. Only the `PaymentSheetResponse` form carries `threeDSResponse`, so prefer it when 3DS is enabled.

See [ThreeDSResponse](/ios/online/token-response#threedsresponse) for the full response shape and [3D Secure error codes](/ios/online/payment-action#3d-secure-error-codes) for the failure cases.

## 11. Test Your Integration

Run through the full flow in Sandbox before switching to Production:

1. Build and run your app (simulator or physical device).
2. Trigger the Payment Sheet to tokenize card and bank details.
3. Confirm your backend created a Payment Instrument from the token and a Transfer from that Payment Instrument.
4. Open the **Payments** section of the Finix Dashboard and verify the Transfer is listed with the expected merchant, amount, and card details.


Repeat for each Form Style, presentation, and Configuration option you plan to ship. If you enabled 3D Secure, confirm `response.threeDSResponse` is populated on successful authentications and that your backend forwards `sessionId` alongside the token.