# Installation

Install the iOS SDK library and start using components in your project.

## Before you start

Make sure you have Xcode 15.0 or later, iOS 14.0 or later as your deployment target, and Swift 5.9+.

## Step 1: Install the iOS SDK library

To install using Swift Package Manager:

1. Open your Xcode project.
2. Go to **File > Add Package Dependencies...**.
3. Enter the repository URL: `https://github.com/PXP-IO/ios-components-sdk.git`.
4. Select a version rule (e.g., *Up to Next Major Version*).
5. Click **Add Package**.
6. Select the **PXPCheckoutSDK** library and add it to your app target.


Add only the `PXPCheckoutSDK` library to your app target. Don't add other products from the same package — dependencies such as Kount, PayPal, and Aerosync are bundled inside `PXPCheckoutSDK`. Linking additional products can cause duplicate symbols and runtime crashes.

```swift
import PXPCheckoutSDK
```

The Paze and Aeropay button components are included in `PXPCheckoutSDK`. You don't need separate payment-method dependencies.

## Step 2: Get your API credentials

To initialise the SDK, you'll need to send authenticated requests to the PXP API.

To get your credentials:

1. In the Unity Portal, go to **Merchant setup > Merchant groups**.
2. Select a merchant group.
3. Click the **Inbound calls** tab.
4. Copy the **Client ID** in the top-right corner.
5. Click **New token**.
6. Choose a number of days before token expiry. For example, `30`.
7. Click **Save** to confirm. Your token is now created.
8. Copy the token ID and token value. Make sure to keep these confidential to protect the integrity of your authentication process.


As best practice, we recommend regularly generating and implementing new tokens.

## Step 3: Get the session data

Now that you have your credentials, you're ready to send an API request to the `sessions` endpoint. This allows you to retrieve the transaction session data from the back-end, so you can supply it when you initialise the SDK.

Our platform uses HMAC (Hash-based Message Authentication Code) with SHA256 for authentication to ensure secure communication and data integrity. This method involves creating a signature by hashing your request data with a secret key, which must then be included in the HTTP headers of your API request.

To create the HMAC signature, you need to prepare a string that includes four parts:

* A timestamp, in Unix format. For example, `1754701373`.
* A unique request ID, in GUID format. For example, `ce244054-b372-42c2-9102-f0d976db69f6`.
* The request path, which is `api/v1/sessions`.
* The request body. Include an `aeropay` intent when Aeropay is enabled in the Unity Portal. Use `USD` for `amounts.currencyCode`. Include only the payment-method intents you need. For example:

```json
{
   "merchant": "MERCHANT-1",
   "site": "SITE-1",
   "sessionTimeout": 120,
   "merchantTransactionId": "0ce72cfd-014d-4256-a006-a56601b2ffc4",
   "amounts": {
     "currencyCode": "USD",
     "transactionValue": 20
   },
   "transactionMethod": {
     "intent": {
       "card": "Authorisation",
       "paypal": "Authorisation",
       "aeropay": "Authorisation"
     }
   }
}
```


| Parameter | Description |
|  --- | --- |
| `merchant`string (≤ 20 characters) | Your unique merchant identifier, as assigned by PXP. You can find it in the Unity Portal, by going to **Merchant setup > Merchants** and checking the *Merchant ID* column or by clicking on a merchant and checking the *General information* section. |
| `site`string (≤ 100 characters) | Your unique site identifier, as assigned by PXP. You can find it in the Unity Portal, by going to **Merchant setup > Sites** and checking the *Site ID* column or by clicking on a site and checking the *General information* section. |
| `merchantTransactionId`string (≤ 50 characters) | A unique identifier of your choice that represents this transaction. |
| `sessionTimeout`number | Optional duration of the session, in minutes. If omitted, the server uses its default expiry. Values above 120 are ignored and the default is used instead. |
| `amounts`object | Details about the transaction amount. |
| `amounts.currencyCode`string (3 characters) | The currency code associated with the transaction, in ISO 4217 format. See [Supported payment currencies](/guides/checkout/components/supported-currencies).Use `USD` for [Paze](/guides/checkout/components/ios/paze/onboarding) and [Aeropay](/guides/checkout/components/ios/aeropay/onboarding). |
| `amounts.transactionValue`number | The transaction amount. The numbers after the decimal will be zero padded if they are less than the expected `currencyCode` exponent. For example, GBP 1.1 = GBP 1.10, EUR 1 = EUR 1.00, or BHD 1.3 = 1.300. The transaction will be rejected if numbers after the decimal are greater than the expected `currencyCode` exponent (e.g., GBP 1.234), or if a decimal is supplied when the `currencyCode` of the exponent does not require it (e.g., JPY 1.0). |
| `transactionMethod`object | Details about the transaction method and intent. |
| `transactionMethod.intent`object | The payment intent for each payment method type. |
| `transactionMethod.intent.card`string | The intent for card, Apple Pay, or Paze transactions.Possible values:- `Authorisation`
- `Purchase`
- `Verification`

 |
| `transactionMethod.intent.paypal`string | The intent for PayPal transactions.Possible values:- `Authorisation`
- `Purchase`
- `Payout` — for payouts, see [PayPal Payouts](/guides/checkout/components/ios/paypal/payouts/how-it-works)

 |
| `transactionMethod.intent.aeropay`string | The intent for Aeropay transactions. Configure Aeropay in the Unity Portal first, then confirm the session response includes `allowedFundingTypes.payByBanks.aeropay`. See [Aeropay onboarding](/guides/checkout/components/ios/aeropay/onboarding#step-4-verify-session-configuration).Possible values:- `Authorisation`
- `Purchase`
- `EstimatedAuthorisation`
- `Payout`

 |


Put these four parts together following this format: `"{timestamp}{requestId}{requestPath}{requestBody}"`. There are no separators between the parts. The `{requestBody}` portion must be the exact JSON bytes sent in the POST body (typically minified, with no extra whitespace or pretty-printing).

The resulting HMAC input is a single string. For example:

```text
1754701373ce244054-b372-42c2-9102-f0d976db69f6api/v1/sessions{"merchant":"MERCHANT-1","site":"SITE-1","sessionTimeout":120,"merchantTransactionId":"0ce72cfd-014d-4256-a006-a56601b2ffc4","amounts":{"currencyCode":"USD","transactionValue":20},"transactionMethod":{"intent":{"card":"Authorisation","paypal":"Authorisation","aeropay":"Authorisation"}}}
```

Compute an HMAC-SHA256 over that string using your token value (secret) as the key. Put the token ID, timestamp, and hex signature in the `Authorization` header as shown below. You can find your token ID and token value in the Unity Portal. Here's an example of an `hmacSignature` after you've signed the data:

```json
1DE2DFC390D7CD746A972140F26846AFA81CF85F5A0BAABA95DBC95301795EA6
```

You can now put together your `Authorization` header. It follows this format: `PXP-UST1 {tokenId}:{timestamp}:{hmacSignature}`. For example:

```
PXP-UST1 9aac6071-38d0-4545-9d2f-15b936af6d7f:1754701373:1DE2DFC390D7CD746A972140F26846AFA81CF85F5A0BAABA95DBC95301795EA6
```

Lastly, send your request to the Sessions API. You'll need to add a request ID of your choice and include your client ID, which you can find in the Unity Portal.

Here's a full example of what your request might look like:

```curl
curl -i -X POST \
  'https://api-services.pxp.io/api/v1/sessions' \
  -H 'Authorization: PXP-UST1 9aac6071-38d0-4545-9d2f-15b936af6d7f:1754701373:1DE2DFC390D7CD746A972140F26846AFA81CF85F5A0BAABA95DBC95301795EA6' \
  -H 'X-Request-Id: 550e8400-e29b-41d4-a716-446655440000' \
  -H 'X-Client-Id: f47ac10b-58cc-4372-a567-0e02b2c3d479' \
  -H 'Content-Type: application/json' \
  -d '{
  "merchant": "MERCHANT-1",
  "site": "SITE-1",
  "sessionTimeout": 120,
  "merchantTransactionId": "0ce72cfd-014d-4256-a006-a56601b2ffc4",
  "amounts": {
    "currencyCode": "USD",
    "transactionValue": 20
  },
  "transactionMethod": {
    "intent": {
      "card": "Authorisation",
      "paypal": "Authorisation",
      "aeropay": "Authorisation"
    }
  }
}'
```

If your request is successful, you'll receive a `200` response containing the session data. After a successful request for Aeropay, confirm the response includes `allowedFundingTypes.payByBanks.aeropay` with non-empty `externalMerchantId` and `configurationId` (see the response table below).

```json
{
  "sessionId": "c5f0799b-0839-43ce-abc5-5b462a98f250",
  "hmacKey": "904bc42395d4af634e2fd48ee8c2c7f52955a1da97a3aa3d82957ff12980a7bb",
  "encryptionKey": "20d175a669ad3f8c195c9c283fc86155",
  "sessionExpiry": "2025-05-19T13:39:20.3843454Z",
  "allowedFundingTypes": {
    "cards": [
      "Visa",
      "Diners",
      "Mastercard",
      "AmericanExpress"
    ],
    "wallets": {
      "paypal": {
        "allowedFundingOptions": [
          "paylater", 
          "paypal"
        ],
        "merchantId": "ST9US6Q5XW2KN"
      },
      "applepay": {
        "merchantId": "merchant.com.yourcompany.store"
      },
      "paze": {
        "clientId": "your-paze-client-id",
        "merchantCategoryCode": "5812"
      }
    },
    "payByBanks": {
      "aeropay": {
        "externalMerchantId": "your-aeropay-merchant-id",
        "configurationId": "your-aeropay-configuration-id"
      }
    }
  },
  "restrictions": {
    "card": {
      "ownerTypes": ["Consumer"],
      "fundingSources": ["Credit", "Debit"]
    }
  }
}
```

| Parameter | Description |
|  --- | --- |
| `sessionId`string (UUID) | The unique identifier for the newly-created session. |
| `hmacKey`string | The HMAC key generated for securing session communications. |
| `encryptionKey`string | A key used for encrypting sensitive session data during communication. |
| `sessionExpiry`string | The timestamp indicating when the session will expire, in ISO 8601 format. |
| `allowedFundingTypes`object | Details about the funding types allowed for this session.Possible values:- `cards`
- `wallets`
- `payByBanks`

 |
| `allowedFundingTypes.cards`array of strings or null | The list of supported card schemes. |
| `allowedFundingTypes.wallets`object or null | Details about the supported digital wallets. |
| `allowedFundingTypes.wallets.paypal`object or null | PayPal wallet configuration. |
| `allowedFundingTypes.wallets.paypal.allowedFundingOptions`array of strings | The list of PayPal funding options available.Possible values:- `paypal`
- `paylater`
- `credit`

 |
| `allowedFundingTypes.wallets.paypal.merchantId`string | The PayPal merchant ID associated with this session. |
| `allowedFundingTypes.wallets.applepay`object or null | Apple Pay wallet configuration. |
| `allowedFundingTypes.wallets.applepay.merchantId`string | The Apple Pay merchant ID to be used for this session. |
| `allowedFundingTypes.wallets.paze`object or null | Paze wallet configuration. Required for the [Paze button component](/guides/checkout/components/ios/paze/implementation). Enable Paze in the Unity Portal first. See [Paze onboarding](/guides/checkout/components/ios/paze/onboarding). |
| `allowedFundingTypes.wallets.paze.clientId`string | The Paze client ID from your *Paze Account Settings* in the Unity Portal. Missing or empty `clientId` causes `SDK0118` at `PxpCheckout.create(.pazeButton, …)` (message: `Paze is missing in allow funding types.`). Validation during the Paze flow may also surface `SDK1202` for the same field. |
| `allowedFundingTypes.wallets.paze.merchantCategoryCode`string | Merchant category code (MCC). Required when the SDK completes a Paze payment; missing values cause `SDK1202A`. |
| `allowedFundingTypes.payByBanks`object or null | Object containing pay-by-bank configurations. |
| `allowedFundingTypes.payByBanks.aeropay`object or null | Aeropay funding configuration. Required for the [Aeropay button component](/guides/checkout/components/ios/aeropay/implementation). |
| `allowedFundingTypes.payByBanks.aeropay.externalMerchantId`string | The Aeropay merchant ID from the Unity Portal. Missing or empty values cause `SDK0113` when you create the component. The SDK message is generic (`Aeropay is missing in allow funding types.`), not field-specific. See [Aeropay troubleshooting](/guides/checkout/components/ios/aeropay/troubleshooting#portal-and-session-setup) for distinguishing portal and session misconfiguration from missing intent, currency, or entry type (`SDK0115`, `SDK0116`, `SDK0114`). |
| `allowedFundingTypes.payByBanks.aeropay.configurationId`string | The Aerosync configuration ID from the Unity Portal. Missing or empty values cause `SDK0113` when you create the component, with the same generic funding-type message as for a missing `aeropay` block or empty `externalMerchantId`. |
| `restrictions`object (optional) | Card restrictions such as `ownerTypes` and `fundingSources`. Pass through to `SessionData` when returned by your backend. |


## Step 4: Configure your iOS project

Depending on which payment methods you plan to support, you'll need to configure your Xcode project accordingly.

Apple Pay
### Add required capabilities

1. In Xcode, select your project target.
2. Go to the `Signing & Capabilities` tab.
3. Click `+ Capability` and add **Apple Pay**.


### Configure your merchant ID

In the Apple Pay capability section, add your merchant ID:

1. Click the `+` button under *Merchant IDs*.
2. Enter your merchant ID (e.g., `merchant.com.yourcompany.store`). Ensure it matches exactly with your Apple Developer Console configuration.


### Update Info.plist

Add the following entries to your `Info.plist`:

```xml
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <false/>
    <key>NSAllowsArbitraryLoadsInWebContent</key>
    <false/>
</dict>
```

PayPal
### Configure PayPal for the button component

PayPal wallet configuration for the PayPal button comes from the Sessions API response (`allowedFundingTypes.wallets.paypal`, including `merchantId`) and your Unity Portal setup. The SDK loads the PayPal client ID from bundled SDK configuration. You don't supply a PayPal Developer Dashboard client ID in the app for the button flow.

For PayPal payout OAuth credentials and Dashboard setup, see [PayPal Payouts](/guides/checkout/components/ios/paypal/payouts/how-it-works) when those docs apply to your integration.

### Update Info.plist

Add the following entries to your `Info.plist`:

```xml
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <false/>
    <key>NSAllowsArbitraryLoadsInWebContent</key>
    <false/>
</dict>
```

Unlike Apple Pay, PayPal doesn't require any special Xcode capabilities.

Paze
### Register your URL callback scheme

Paze checkout opens in `ASWebAuthenticationSession` and returns to your app at `{callbackScheme}://paze`. The default callback scheme is `pxpcheckout`.

Add the scheme to your `Info.plist`:

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

Use a URL scheme that is unique to your app. If another app registers the same scheme, checkout callbacks may be intercepted.

Paze doesn't require special Xcode capabilities. For portal setup, session fields, and component integration, see [Paze implementation](/guides/checkout/components/ios/paze/implementation#step-2-register-your-url-callback-scheme).

Aeropay
### Register your Aerosync callback scheme

Aeropay bank linking returns to your app at `{callbackScheme}://aerosync/callback`. The callback scheme comes from the SDK configuration (`PXP_CALLBACK_SCHEME`) and defaults to `pxpcheckout`.

Add the scheme to your `Info.plist`:

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

Use a URL scheme that is unique to your app. If another app registers the same scheme, bank-linking callbacks may be intercepted.

Aeropay doesn't require special Xcode capabilities. For portal setup, session fields, and component integration, see [Aeropay implementation](/guides/checkout/components/ios/aeropay/implementation).

## Step 5: Initialise the SDK and create components

To initialise the SDK, you need to pass the session data from Step 3 back to your iOS application, along with details about the environment, owner ID and type, merchant shopper ID, and transaction data.

Once the SDK is initialised, you can create payment components for your desired payment methods.

Apple Pay
```swift
import UIKit
import SwiftUI
import PXPCheckoutSDK

class ViewController: UIViewController {
    
    private var pxpCheckout: PxpCheckout?
    private var applePayComponent: ApplePayButtonComponent?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        Task {
            await initialiseTheSDK()
        }
    }
    
    private func initialiseTheSDK() async {
        do {
            // 1. Get the session data from the back-end
            let sessionData = try await getSessionDataFromBackend()
            
            // 2. Create checkout configuration
            let config = CheckoutConfig(
                environment: .test,
                session: sessionData,
                transactionData: TransactionData(
                    amount: Decimal(25.00),
                    currency: "USD",
                    entryType: .ecom,
                    intent: TransactionIntentData(
                        card: .authorisation,
                        paypal: nil
                    ),
                    merchantTransactionId: UUID().uuidString,
                    merchantTransactionDate: { Date() }
                ),
                merchantShopperId: "Shopper_01",
                ownerType: "merchantGroup",
                ownerId: "Unity",
                kountDisabled: false // OPTIONAL: Set to true to disable Kount fraud detection
            )
            
            // 3. Initialise the SDK
            let pxpCheckoutSdk = try PxpCheckout.initialize(config: config)
            self.pxpCheckout = pxpCheckoutSdk
            
            setupApplePayComponent()
            
        } catch {
            showError("Failed to initialise SDK: \(error.localizedDescription)")
        }
    }
    
    private func setupApplePayComponent() {
        guard let pxpCheckout = pxpCheckout else { return }
        
        // Create Apple Pay component configuration
        let config = ApplePayButtonComponentConfig()
        config.countryCode = "US"
        config.currencyCode = "USD"
        config.supportedNetworks = [.visa, .masterCard, .amex, .discover]
        config.merchantCapabilities = [.threeDSecure]
        config.buttonType = .buy
        config.buttonStyle = .black
        config.requiredBillingContactFields = [.postalAddress, .name, .emailAddress]
        config.requiredShippingContactFields = [.postalAddress, .name, .phoneNumber]
        
        // Required: Set the total payment amount
        config.totalPaymentItem = ApplePayPaymentSummaryItem(
            amount: Decimal(25.00),
            type: .final,
            label: "Your Store Name"
        )
        
        // Set up callbacks on config
        config.onPreAuthorisation = { [weak self] in
            self?.handlePreAuthorisation()
        }
        
        config.onPostAuthorisation = { [weak self] submitResult, applePayResult in
            self?.handlePostAuthorisation(submitResult: submitResult, applePayResult: applePayResult)
        }
        
        config.onError = { [weak self] error in
            self?.showError("Apple Pay error: \(error.errorMessage)")
        }
        
        config.onCancel = { [weak self] error in
            print("Apple Pay cancelled: \(error.errorMessage)")
        }
        
        // Create the component and host the SwiftUI button in UIKit
        do {
            let component = try pxpCheckout.create(.applePayButton, componentConfig: config)
            applePayComponent = component as? ApplePayButtonComponent

            if let buttonView = applePayComponent?.buildContent() {
                let host = UIHostingController(rootView: buttonView)
                addChild(host)
                view.addSubview(host.view)
                host.view.translatesAutoresizingMaskIntoConstraints = false
                NSLayoutConstraint.activate([
                    host.view.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
                    host.view.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
                    host.view.centerYAnchor.constraint(equalTo: view.centerYAnchor),
                    host.view.heightAnchor.constraint(equalToConstant: 50),
                ])
                host.didMove(toParent: self)
            }
        } catch {
            showError("Failed to create Apple Pay component: \(error.localizedDescription)")
        }
    }
    
    private func handlePreAuthorisation() -> ApplePayTransactionInitData? {
        return ApplePayTransactionInitData(
            riskScreeningData: RiskScreeningData(
                performRiskScreening: true,
                userIp: "192.168.1.100",
                account: RiskScreeningAccount(
                    id: "user_12345678",
                    creationDateTime: ISO8601DateFormatter().date(from: "2024-01-15T10:30:00Z")
                ),
                items: [
                    RiskScreeningItem(
                        price: Decimal(25.00),
                        quantity: 1,
                        category: "General"
                    )
                ],
                fulfillments: [
                    RiskScreeningFulfillment(
                        type: .shipped,
                        recipientPerson: RiskScreeningRecipientPerson(
                            phoneNumber: "+1234567890"
                        )
                    )
                ]
            )
        )
    }
    
    private func handlePostAuthorisation(submitResult: BaseSubmitResult, applePayResult: ApplePayResult) {
        if let merchantResult = submitResult as? MerchantSubmitResult {
            print("Payment success: \(merchantResult.systemTransactionId)")
            // Handle successful payment
        } else if let failedResult = submitResult as? FailedSubmitResult {
            showError("Payment failed: \(failedResult.errorReason ?? "")")
        }
    }
    
    private func getSessionDataFromBackend() async throws -> SessionData {
        // Implement your backend session request here
        // This should match the curl request shown above
        fatalError("Implement session data retrieval from your backend")
    }
    
    private func showError(_ message: String) {
        DispatchQueue.main.async {
            let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "OK", style: .default))
            self.present(alert, animated: true)
        }
    }
}
```

PayPal
```swift
import SwiftUI
import PXPCheckoutSDK

struct PayPalPaymentView: View {
    @State private var pxpCheckout: PxpCheckout?
    @State private var paypalComponent: BaseComponent?
    @State private var isLoading = true
    @State private var errorMessage: String?
    
    var body: some View {
        VStack(spacing: 16) {
            if isLoading {
                ProgressView("Initialising PayPal...")
            } else if let component = paypalComponent {
                component.buildContent()
                    .frame(height: 50)
            } else if let error = errorMessage {
                Text("Error: \(error)")
                    .foregroundColor(.red)
            }
        }
        .padding()
        .onAppear {
            createPayPalComponent()
        }
    }
    
    private func createPayPalComponent() {
        Task {
            do {
                // 1. Get the session data from the back-end
                let sessionData = try await getSessionDataFromBackend()
                
                // 2. Initialise the SDK
                let transactionData = TransactionData(
                    amount: Decimal(25.00),
                    currency: "USD",
                    entryType: .ecom,
                    intent: TransactionIntentData(
                        card: nil,
                        paypal: .authorisation
                    ),
                    merchantTransactionId: "tx-\(UUID().uuidString)",
                    merchantTransactionDate: { Date() }
                )
                
                let checkoutConfig = CheckoutConfig(
                    environment: .test,
                    session: sessionData,
                    transactionData: transactionData,
                    merchantShopperId: "Shopper_01",
                    ownerId: "Unity",
                    kountDisabled: false // OPTIONAL: Set to true to disable Kount fraud detection
                )
                
                let pxpCheckout = try PxpCheckout.initialize(config: checkoutConfig)
                self.pxpCheckout = pxpCheckout
                
                // 3. Create PayPal component
                let config = PayPalButtonComponentConfig()
                config.fundingSource = .paypal
                config.payeeEmailAddress = "merchant@example.com"
                config.paymentDescription = "Order #12345"
                
                // Event handlers
                config.onPreAuthorisation = { PayPalTransactionInitData(
                        riskScreeningData: RiskScreeningData(performRiskScreening: true, userIp: "192.168.1.100")
                    )
                }
                
                config.onApprove = { approvalData in
                    print("Payment successful - Order ID: \(approvalData.orderID)")
                    // Handle successful payment
                }
                
                config.onError = { error in
                    print("Payment error: \(error.errorMessage)")
                    // Show error message to user
                }
                
                config.onCancel = { error in
                    print("Payment cancelled by user")
                    // Handle cancellation
                }
                
                let component = try pxpCheckout.create(
                    .paypalButton,
                    componentConfig: config
                )
                
                await MainActor.run {
                    paypalComponent = component
                    isLoading = false
                }
            } catch {
                await MainActor.run {
                    errorMessage = error.localizedDescription
                    isLoading = false
                }
            }
        }
    }
    
    private func getSessionDataFromBackend() async throws -> SessionData {
        // Implement your backend session request here
        // This should match the curl request shown in Step 3
        fatalError("Implement session data retrieval from your backend")
    }
}
```

Paze
```swift
import SwiftUI
import PXPCheckoutSDK

struct PazePaymentView: View {
    @State private var pxpCheckout: PxpCheckout?
    @State private var pazeComponent: BaseComponent?
    @State private var isLoading = true
    @State private var errorMessage: String?

    var body: some View {
        VStack(spacing: 16) {
            if isLoading {
                ProgressView("Initialising Paze...")
            } else if let component = pazeComponent {
                component.buildContent()
                    .frame(height: 50)
            } else if let error = errorMessage {
                Text("Error: \(error)")
                    .foregroundColor(.red)
            }
        }
        .padding()
        .onAppear {
            createPazeComponent()
        }
    }

    private func createPazeComponent() {
        Task {
            do {
                let sessionData = try await getSessionDataFromBackend()

                let transactionData = TransactionData(
                    amount: Decimal(25.00),
                    currency: "USD",
                    entryType: .ecom,
                    intent: TransactionIntentData(card: .authorisation),
                    merchantTransactionId: "tx-\(UUID().uuidString)",
                    merchantTransactionDate: { Date() }
                )

                let checkoutConfig = CheckoutConfig(
                    environment: .test,
                    session: sessionData,
                    transactionData: transactionData,
                    merchantShopperId: "Shopper_01",
                    ownerId: "Unity",
                    clientName: "Your Merchant",
                    siteName: "Your Store",
                    onGetShopper: { TransactionShopper(
                            id: "shopper-123",
                            firstName: "John",
                            lastName: "Doe",
                            email: "customer@example.com"
                        )
                    }
                )

                let pxpCheckout = try PxpCheckout.initialize(config: checkoutConfig)
                self.pxpCheckout = pxpCheckout

                let config = PazeButtonComponentConfig()
                config.emailAddress = "customer@example.com"
                config.style = PazeButtonStyleConfig(
                    color: .auto,
                    shape: .rounded,
                    label: .checkoutWith
                )
                config.onPostAuthorisation = { result in
                    print("Paze success: \(result.systemTransactionId)")
                }
                config.onError = { error in
                    print("Paze error: \(error.errorMessage)")
                }

                let component = try pxpCheckout.create(
                    .pazeButton,
                    componentConfig: config
                )

                await MainActor.run {
                    pazeComponent = component
                    isLoading = false
                }
            } catch {
                await MainActor.run {
                    errorMessage = error.localizedDescription
                    isLoading = false
                }
            }
        }
    }

    private func getSessionDataFromBackend() async throws -> SessionData {
        // Implement your backend session request here
        // This should match the curl request shown in Step 3
        fatalError("Implement session data retrieval from your backend")
    }
}
```

Paze requires `USD`, `.ecom`, a card intent, and Paze funding fields in the session. For portal setup, URL scheme, and full callbacks, see [Paze implementation](/guides/checkout/components/ios/paze/implementation).

Aeropay
```swift
import SwiftUI
import PXPCheckoutSDK

struct AeropayPaymentView: View {
    @State private var pxpCheckout: PxpCheckout?
    @State private var aeropayComponent: BaseComponent?
    @State private var isLoading = true
    @State private var errorMessage: String?

    var body: some View {
        VStack(spacing: 16) {
            if isLoading {
                ProgressView("Initialising Aeropay...")
            } else if let component = aeropayComponent {
                component.buildContent()
                    .frame(maxWidth: .infinity)
            } else if let error = errorMessage {
                Text("Error: \(error)")
                    .foregroundColor(.red)
            }
        }
        .padding()
        .onAppear {
            createAeropayComponent()
        }
    }

    private func createAeropayComponent() {
        Task {
            do {
                let sessionData = try await getSessionDataFromBackend()

                let transactionData = TransactionData(
                    amount: Decimal(25.00),
                    currency: "USD",
                    entryType: .ecom,
                    intent: TransactionIntentData(aeropay: .authorisation),
                    merchantTransactionId: "tx-\(UUID().uuidString)",
                    merchantTransactionDate: { Date() }
                )

                let checkoutConfig = CheckoutConfig(
                    environment: .test,
                    session: sessionData,
                    transactionData: transactionData,
                    merchantShopperId: "Shopper_01",
                    ownerId: "Unity",
                    onGetShopper: { TransactionShopper(
                            id: "shopper-123",
                            firstName: "John",
                            lastName: "Doe",
                            email: "john.doe@example.com",
                            phoneNumber: "+14155550123"
                        )
                    }
                )

                let pxpCheckout = try PxpCheckout.initialize(config: checkoutConfig)
                self.pxpCheckout = pxpCheckout

                let config = AeropayButtonComponentConfig(label: "Pay by bank")
                config.onPreAuthorisation = { true }
                config.onPostAuthorisation = { result in
                    print("Aeropay success: \(result.systemTransactionId)")
                }
                config.onSubmitError = { result in
                    if let failed = result as? FailedSubmitResult {
                        print("Aeropay submit failed: \(failed.errorCode ?? "") — \(failed.errorReason ?? "")")
                    }
                }
                config.onError = { error in
                    print("Aeropay error: \(error.errorMessage)")
                }

                let component = try pxpCheckout.create(
                    .aeropayButton,
                    componentConfig: config
                )

                await MainActor.run {
                    aeropayComponent = component
                    isLoading = false
                }
            } catch {
                await MainActor.run {
                    errorMessage = error.localizedDescription
                    isLoading = false
                }
            }
        }
    }

    private func getSessionDataFromBackend() async throws -> SessionData {
        // Implement your backend session request here
        // This should match the curl request shown in Step 3
        fatalError("Implement session data retrieval from your backend")
    }
}
```

Aeropay requires `USD`, `.ecom`, an Aeropay intent, and `payByBanks.aeropay` in the session. For portal setup, Aerosync callback scheme, and full callbacks, see [Aeropay implementation](/guides/checkout/components/ios/aeropay/implementation).

The following table describes the `CheckoutConfig` parameters used when you initialise the SDK:

| Parameter | Description |
|  --- | --- |
| `environment`Environment | The environment type.Possible values:- `.test`
- `.live`

 |
| `session`SessionData | Details about the checkout session. |
| `transactionData`TransactionData | Details about the transaction. |
| `transactionData.amount`Decimal | The transaction amount (for example, `Decimal(25.00)`). |
| `transactionData.currency`String | The currency code associated with the transaction, in ISO 4217 format (for example, `"USD"`, `"EUR"`, `"GBP"`). Use `"USD"` for Paze and Aeropay. |
| `transactionData.entryType`EntryType
 | The entry type.Possible values:
- `.ecom`
- `.moto`

Use `.ecom` for Paze and Aeropay.
 |
| `transactionData.intent`TransactionIntentData | The transaction intents for each payment method. Set only the methods you need. Example: `TransactionIntentData(aeropay: .authorisation)`. |
| `transactionData.intent.card`CardIntentType?
 | The intent for card, Apple Pay, or Paze transactions.Possible values:
- `.authorisation`
- `.estimatedAuthorisation`
- `.purchase`
- `.payout`
- `.verification`

For Paze, use `.authorisation` or `.purchase`. See [card intents](/guides/checkout/components/ios/apple-pay/how-it-works#supported-transaction-intents).
 |
| `transactionData.intent.paypal`PayPalIntentType? | The intent for PayPal transactions.Possible values:- `.authorisation`
- `.purchase`
- `.payout` — requires `paypalConfig`. See [PayPal Payouts](/guides/checkout/components/ios/paypal/payouts/how-it-works)

 |
| `transactionData.intent.aeropay`AeropayIntentType?
 | The intent for Aeropay transactions.Possible values:
- `.authorisation`
- `.purchase`
- `.estimatedAuthorisation`
- `.payout`

See [Aeropay intents](/guides/checkout/components/ios/aeropay/how-it-works#transaction-intents).
 |
| `transactionData.merchantTransactionId`String | A unique identifier for this transaction. |
| `transactionData.merchantTransactionDate`() -> Date | A closure that returns the date and time of the transaction. Use `{ Date() }` for the current date. |
| `transactionData.cardAcceptorName`String? | Card acceptor name for the transaction. |
| `transactionData.recurring`RecurringType? | Recurring payment configuration. Use `RecurringType(frequencyInDays: Int?, frequencyExpiration: String?)`. |
| `transactionData.linkId`String? | Transaction link ID for linking related transactions. |
| `merchantShopperId`String | A unique identifier for this shopper. |
| `ownerType`String? | The type of owner.Possible values:- `"merchantGroup"`
- `"merchant"`
- `"site"`

 |
| `ownerId`String | The identifier of the owner related to the `ownerType`. |
| `onGetShippingAddress`(() async -> ShippingAddress?)? | Optional async callback that returns shipping address data when the SDK needs it. Example: `onGetShippingAddress: { await fetchShippingAddressFromBackend() }`.`ShippingAddress` fields:- `address` (String)
- `addressLine2` (String?)
- `city` (String)
- `postalCode` (String)
- `countryCode` (String, ISO 3166-1 alpha-2)

 |
| `localisation`Localisation? | Custom UI text overrides for localisation. |
| `locale`String? | Locale for language/region (for example, `"en-US"`, `"es-ES"`, `"el-GR"`). |
| `paypalConfig`PayPalConfig? | PayPal-specific configuration. Required for PayPal payout transactions. [Learn more](#paypal-payout-configuration). |
| `restrictions`Restrictions? | Optional card restrictions for owner types (corporate or consumer) and funding sources (credit, debit, or prepaid). When both session and config restrictions are provided, they're merged as a union. |
| `kountDisabled`Bool | Disable Kount fraud detection. Default: `false` (fraud detection enabled). |
| `clientName`String? | Optional client display name forwarded to provider experiences that support it (used by Paze). |
| `siteName`String? | Optional brand-facing site name forwarded to provider experiences (used by Paze). |
| `onGetShopper`(() async -> TransactionShopper?)? | Optional async callback that returns shopper information. |
| `analyticsEvent`((BaseAnalyticsEvent) -> Void)? | Handler for analytics events. |


## PayPal payout configuration

For PayPal payout integrations, you must configure `paypalConfig` with payout-specific settings. This is required when using `.payout` intent for PayPal transactions.

### Basic PayPal payout setup

```swift
let config = CheckoutConfig(
    environment: .test,
    session: sessionData,
    transactionData: TransactionData(
        amount: Decimal(100.00),
        currency: "USD",
        entryType: .ecom,
        intent: TransactionIntentData(
            card: nil,
            paypal: .payout  // Must use .payout intent
        ),
        merchantTransactionId: UUID().uuidString,
        merchantTransactionDate: { Date() }
    ),
    merchantShopperId: "customer-123",
    ownerId: "merchant-id",
    kountDisabled: false, // OPTIONAL: Set to true to disable Kount fraud detection
    paypalConfig: PayPalConfig(
        payout: PayPalPayoutConfig(
            paypalWallet: PayPalWallet(
                email: "recipient@example.com",
                payerId: "PAYERID123",  // Max 13 alphanumeric characters
                proceedPayoutWithSdk: false  // false = automatic payout
            )
        )
    )
)
```

### PayPal payout with approval callback

Set `proceedPayoutWithSdk: true` on `PayPalWallet` or `VenmoWallet` to gate whether the payout submission component runs its approval callback before processing. Configure `onPrePayoutSubmit` on the payout submission component — not on `CheckoutConfig`:

```swift
let config = CheckoutConfig(
    environment: .test,
    session: sessionData,
    transactionData: TransactionData(
        amount: Decimal(100.00),
        currency: "USD",
        entryType: .ecom,
        intent: TransactionIntentData(
            card: nil,
            paypal: .payout
        ),
        merchantTransactionId: UUID().uuidString,
        merchantTransactionDate: { Date() }
    ),
    merchantShopperId: "customer-123",
    ownerId: "merchant-id",
    kountDisabled: false, // OPTIONAL: Set to true to disable Kount fraud detection
    paypalConfig: PayPalConfig(
        payout: PayPalPayoutConfig(
            paypalWallet: PayPalWallet(
                email: "recipient@example.com",
                payerId: "PAYERID123",
                proceedPayoutWithSdk: true
            )
        )
    )
)

let pxpCheckout = try PxpCheckout.initialize(config: config)

let payoutConfig = PayoutSubmissionComponentConfig()
payoutConfig.onPrePayoutSubmit = {
    // Return approval result before payout proceeds
    PrePayoutSubmitResult(isApproved: true)
}

let payoutComponent = try pxpCheckout.create(
    .payoutSubmission,
    componentConfig: payoutConfig
)
```

### Venmo payout configuration

Use `venmoWallet` inside `PayPalPayoutConfig` when the payout should go to a Venmo recipient instead of a PayPal wallet:

```swift
let config = CheckoutConfig(
    environment: .test,
    session: sessionData,
    transactionData: TransactionData(
        amount: Decimal(50.00),
        currency: "USD",
        entryType: .ecom,
        intent: TransactionIntentData(
            card: nil,
            paypal: .payout
        ),
        merchantTransactionId: UUID().uuidString,
        merchantTransactionDate: { Date() }
    ),
    merchantShopperId: "customer-123",
    ownerId: "merchant-id",
    kountDisabled: false, // OPTIONAL: Set to true to disable Kount fraud detection
    paypalConfig: PayPalConfig(
        payout: PayPalPayoutConfig(
            venmoWallet: VenmoWallet(
                recipientType: .email,  // or .phone, .userHandle
                receiver: "user@example.com",  // or phone number, or @username
                proceedPayoutWithSdk: false
            )
        )
    )
)
```

### PayPal payout key points

* **PayerId validation:** Must be max 13 characters, alphanumeric only.
* **Intent requirement:** Must set `paypal: .payout` in `TransactionIntentData`.
* **proceedPayoutWithSdk flag:**
  * `false` (default): SDK proceeds automatically with payout using provided Payer ID.
  * `true`: configure `onPrePayoutSubmit` on the payout submission component, not on `CheckoutConfig`.
* **Email vs PayerId:** Provide either email or payerId (or both) for PayPal wallet identification.


For a complete guide on implementing PayPal payouts, see [PayPal payouts](/guides/checkout/components/ios/paypal/payouts/how-it-works).

## What's next?

You've successfully installed the SDK and created your first payment component! Here are some recommended next steps:

* **For Apple Pay:** Learn more about [Apple Pay](/guides/checkout/components/ios/apple-pay/how-it-works).
* **For PayPal:** Learn more about [PayPal button component documentation](/guides/checkout/components/ios/paypal/how-it-works).
* **For Paze:** Learn more about [Paze](/guides/checkout/components/ios/paze/how-it-works) for portal setup, URL callback scheme, and component integration.
* **For Aeropay:** Learn more about [Aeropay](/guides/checkout/components/ios/aeropay/how-it-works) for portal setup, Aerosync callback scheme, and component integration.
* **Testing:** Use the test environment to validate your integration before going live.
* **Webhooks:** Subscribe to webhooks in the Unity Portal for real-time payment notifications. [Learn more about webhooks](/guides/get-started/about-webhooks).