{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-guides/sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":["accordion","img","code-walkthrough","step","admonition"]},"type":"markdown"},"seo":{"title":"Quickstart","description":"Transform your commerce with PXP's unified platform—seamless payments, real-time insights, and global growth in one powerful integration.","lang":"en-UK","siteUrl":"https://developer.pxp.io","llmstxt":{"hide":false,"sections":[{"title":"Table of contents","includeFiles":["**/*"],"excludeFiles":[]}],"excludeFiles":[]}},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"quickstart","__idx":0},"children":["Quickstart"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"span","attributes":{"style":{"display":"inline-block","fontSize":"20px","color":"#3A3D40","borderRadius":"0","fontWeight":"500","verticalAlign":"middle","lineHeight":"1.5","margin":"0 auto 20px auto!important"}},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Follow our walkthrough to get Checkout Drop-in running in minutes."]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"pre-requisites","__idx":1},"children":["Pre-requisites"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Before you start, make sure you have:"]},{"$$mdtype":"Tag","name":"ul","attributes":{"className":"code-walkthrough-list"},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Xcode 14 or higher installed on your computer"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["iOS 14.0 or higher as your deployment target"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Swift 5.9 or higher"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Your API credentials from the ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"https://portal.pxp.io","target":"_blank"},"children":["Unity Portal"]}]}]},{"$$mdtype":"Tag","name":"Accordion","attributes":{"title":"Where to find your credentials","defaultOpen":false},"children":[{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["In the ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"https://portal.pxp.io","target":"_blank"},"children":["Unity Portal"]},", go to ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Merchant setup > Merchant groups"]}," and select a merchant group."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Click the ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Inbound calls"]}," tab. Your client ID is in the top right:",{"$$mdtype":"Tag","name":"Image","attributes":{"src":"/assets/copy-client-id.0a23af7974c95fd4eb1889f740594750a69954f5f6ea5dbc5f3acf5a56d25922.fa48401d.png","alt":"","framed":false,"withLightbox":true,"className":"screenshot","width":"","height":""},"children":[]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Click ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["+ New token"]}," to create a token, then copy both the ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["ID"]}," and the ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Value"]},".",{"$$mdtype":"Tag","name":"Image","attributes":{"src":"/assets/copy-token-value.51ecc9c77324b3383f3b7837f1e085d7bf7959bfe13773ed49fcfea8f9a78f0d.fa48401d.png","alt":"","framed":false,"withLightbox":true,"className":"screenshot","width":"","height":""},"children":[]}]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"add-the-sdk-dependency","__idx":2},"children":["Add the SDK dependency"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["To get started, add the iOS SDK to your project using Swift Package Manager."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["In Xcode:"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Go to ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["File > Add Package Dependencies"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Enter the package URL: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["https://github.com/PXP-IO/ios-components-sdk"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Select the latest version and click ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Add Package"]},"."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"create-a-session-on-your-backend","__idx":3},"children":["Create a session on your backend"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Drop-in needs a session from the PXP API. This must happen on your backend using HMAC authentication."]},{"$$mdtype":"Tag","name":"CodeWalkthrough","attributes":{"__idx":1,"filters":{"Backend":{"items":[{"label":"Node.js","value":"nodejs"},{"label":"Python","value":"python"}],"default":"nodejs"}},"filesets":[{"files":[{"path":"guides/checkout/drop-in/ios/_filesets/installation/create-session.js","content":["// Node.js backend: Create a session for Checkout Drop-in\r","const crypto = require('crypto');\r","const fetch = require('node-fetch');\r","const express = require('express');\r","\r","const app = express();\r","\r","// [!code highlight:4]\r","// Step 1: Store your credentials securely\r","const CLIENT_ID = process.env.PXP_CLIENT_ID;\r","const TOKEN_ID = process.env.PXP_TOKEN_ID;\r","const TOKEN_VALUE = process.env.PXP_TOKEN_VALUE;\r","\r","// [!code highlight:14]\r","// Step 2: Create the HMAC signature function\r","function createHmacSignature(tokenId, timestamp, requestId, requestPath, requestBody) {\r","  const tokenValue = TOKEN_VALUE;\r","  \r","  // Concatenate the values with colons\r","  const message = `${tokenId}:${timestamp}:${requestId}:${requestPath}:${requestBody}`;\r","  \r","  // Create HMAC SHA256 hash\r","  const hmac = crypto.createHmac('sha256', tokenValue);\r","  hmac.update(message);\r","  const signature = hmac.digest('hex');\r","  \r","  return `PXP-WCA1 ${tokenId}:${timestamp}:${signature}`;\r","}\r","\r","// [!code highlight:22]\r","// Step 3: Build the session request body\r","async function createSession() {\r","  const timestamp = Date.now().toString();\r","  const requestId = crypto.randomUUID();\r","  const requestPath = 'api/v1/sessions';\r","  \r","  const requestBody = JSON.stringify({\r","    merchant: 'MERCHANT-1',\r","    site: 'SITE-1',\r","    sessionTimeout: 120,\r","    merchantTransactionId: crypto.randomUUID(),\r","    transactionMethod: {\r","      intent: {\r","        card: 'Authorisation',\r","        paypal: 'Purchase'\r","      }\r","    },\r","    amounts: {\r","      currencyCode: 'USD',\r","      transactionValue: 49.99\r","    },\r","    allowTransaction: true,\r","    serviceType: 'CheckoutDropIn'\r","    // Optional — when Paze is enabled in the Unity Portal, the session response\r","    // includes allowedFundingTypes.wallets.paze. You may also request it explicitly:\r","    // allowedFundingTypes: {\r","    //   wallets: {\r","    //     paze: {\r","    //       clientId: 'your-paze-client-id',\r","    //       merchantCategoryCode: '5812'\r","    //     }\r","    //   }\r","    // }\r","  });\r","\r","  // [!code highlight:10]\r","  // Step 4: Send the session creation request\r","  const authHeader = createHmacSignature(TOKEN_ID, timestamp, requestId, requestPath, requestBody);\r","  \r","  const response = await fetch('https://api-services.pxp.io/api/v1/sessions', {\r","    method: 'POST',\r","    headers: {\r","      'X-Client-Id': CLIENT_ID,\r","      'X-Request-Id': requestId,\r","      'Authorization': authHeader,\r","      'Content-Type': 'application/json'\r","    },\r","    body: requestBody\r","  });\r","\r","  // [!code highlight:3]\r","  // Step 5: Return the session data to your app\r","  const sessionData = await response.json();\r","  return sessionData;\r","}\r","\r","// Express.js endpoint example\r","app.get('/api/create-session', async (req, res) => {\r","  try {\r","    const sessionData = await createSession();\r","    res.json(sessionData);\r","  } catch (error) {\r","    console.error('Session creation failed:', error);\r","    res.status(500).json({ error: 'Failed to create session' });\r","  }\r","});\r",""],"metadata":{"steps":[]},"basename":"create-session.js","language":"javascript"}],"downloadAssociatedFiles":[{"path":"guides/checkout/drop-in/ios/_filesets/installation/create-session.js","content":["// Node.js backend: Create a session for Checkout Drop-in\r","const crypto = require('crypto');\r","const fetch = require('node-fetch');\r","const express = require('express');\r","\r","const app = express();\r","\r","// [!code highlight:4]\r","// Step 1: Store your credentials securely\r","const CLIENT_ID = process.env.PXP_CLIENT_ID;\r","const TOKEN_ID = process.env.PXP_TOKEN_ID;\r","const TOKEN_VALUE = process.env.PXP_TOKEN_VALUE;\r","\r","// [!code highlight:14]\r","// Step 2: Create the HMAC signature function\r","function createHmacSignature(tokenId, timestamp, requestId, requestPath, requestBody) {\r","  const tokenValue = TOKEN_VALUE;\r","  \r","  // Concatenate the values with colons\r","  const message = `${tokenId}:${timestamp}:${requestId}:${requestPath}:${requestBody}`;\r","  \r","  // Create HMAC SHA256 hash\r","  const hmac = crypto.createHmac('sha256', tokenValue);\r","  hmac.update(message);\r","  const signature = hmac.digest('hex');\r","  \r","  return `PXP-WCA1 ${tokenId}:${timestamp}:${signature}`;\r","}\r","\r","// [!code highlight:22]\r","// Step 3: Build the session request body\r","async function createSession() {\r","  const timestamp = Date.now().toString();\r","  const requestId = crypto.randomUUID();\r","  const requestPath = 'api/v1/sessions';\r","  \r","  const requestBody = JSON.stringify({\r","    merchant: 'MERCHANT-1',\r","    site: 'SITE-1',\r","    sessionTimeout: 120,\r","    merchantTransactionId: crypto.randomUUID(),\r","    transactionMethod: {\r","      intent: {\r","        card: 'Authorisation',\r","        paypal: 'Purchase'\r","      }\r","    },\r","    amounts: {\r","      currencyCode: 'USD',\r","      transactionValue: 49.99\r","    },\r","    allowTransaction: true,\r","    serviceType: 'CheckoutDropIn'\r","    // Optional — when Paze is enabled in the Unity Portal, the session response\r","    // includes allowedFundingTypes.wallets.paze. You may also request it explicitly:\r","    // allowedFundingTypes: {\r","    //   wallets: {\r","    //     paze: {\r","    //       clientId: 'your-paze-client-id',\r","    //       merchantCategoryCode: '5812'\r","    //     }\r","    //   }\r","    // }\r","  });\r","\r","  // [!code highlight:10]\r","  // Step 4: Send the session creation request\r","  const authHeader = createHmacSignature(TOKEN_ID, timestamp, requestId, requestPath, requestBody);\r","  \r","  const response = await fetch('https://api-services.pxp.io/api/v1/sessions', {\r","    method: 'POST',\r","    headers: {\r","      'X-Client-Id': CLIENT_ID,\r","      'X-Request-Id': requestId,\r","      'Authorization': authHeader,\r","      'Content-Type': 'application/json'\r","    },\r","    body: requestBody\r","  });\r","\r","  // [!code highlight:3]\r","  // Step 5: Return the session data to your app\r","  const sessionData = await response.json();\r","  return sessionData;\r","}\r","\r","// Express.js endpoint example\r","app.get('/api/create-session', async (req, res) => {\r","  try {\r","    const sessionData = await createSession();\r","    res.json(sessionData);\r","  } catch (error) {\r","    console.error('Session creation failed:', error);\r","    res.status(500).json({ error: 'Failed to create session' });\r","  }\r","});\r",""],"metadata":{"steps":[]},"basename":"create-session.js","language":"javascript"}],"when":{"Backend":"nodejs"}},{"files":[{"path":"guides/checkout/drop-in/ios/_filesets/installation/create-session.py","content":["# Python backend: Create a session for Checkout Drop-in\r","import os\r","import hmac\r","import hashlib\r","import json\r","import time\r","import uuid\r","import requests\r","from flask import Flask, jsonify\r","\r","app = Flask(__name__)\r","\r","# [!code highlight:4]\r","# Step 1: Store your credentials securely\r","CLIENT_ID = os.environ.get('PXP_CLIENT_ID')\r","TOKEN_ID = os.environ.get('PXP_TOKEN_ID')\r","TOKEN_VALUE = os.environ.get('PXP_TOKEN_VALUE')\r","\r","# [!code highlight:11]\r","# Step 2: Create the HMAC signature function\r","def create_hmac_signature(token_id, timestamp, request_id, request_path, request_body):\r","    \"\"\"Generate HMAC signature for authentication\"\"\"\r","    # Concatenate the values with colons\r","    message = f\"{token_id}:{timestamp}:{request_id}:{request_path}:{request_body}\"\r","    \r","    # Create HMAC SHA256 hash\r","    signature = hmac.new(\r","        TOKEN_VALUE.encode('utf-8'),\r","        message.encode('utf-8'),\r","        hashlib.sha256\r","    ).hexdigest()\r","    \r","    return f\"PXP-WCA1 {token_id}:{timestamp}:{signature}\"\r","\r","# [!code highlight:22]\r","# Step 3: Build the session request body\r","def create_session():\r","    \"\"\"Create a new checkout session\"\"\"\r","    timestamp = str(int(time.time() * 1000))\r","    request_id = str(uuid.uuid4())\r","    request_path = 'api/v1/sessions'\r","    \r","    request_body = json.dumps({\r","        'merchant': 'MERCHANT-1',\r","        'site': 'SITE-1',\r","        'sessionTimeout': 120,\r","        'merchantTransactionId': str(uuid.uuid4()),\r","        'transactionMethod': {\r","            'intent': {\r","                'card': 'Authorisation',\r","                'paypal': 'Purchase'\r","            }\r","        },\r","        'amounts': {\r","            'currencyCode': 'USD',\r","            'transactionValue': 49.99\r","        },\r","        'allowTransaction': True,\r","        'serviceType': 'CheckoutDropIn'\r","        # Optional — when Paze is enabled in the Unity Portal, the session response\r","        # includes allowedFundingTypes.wallets.paze. You may also request it explicitly:\r","        # 'allowedFundingTypes': {\r","        #     'wallets': {\r","        #         'paze': {\r","        #             'clientId': 'your-paze-client-id',\r","        #             'merchantCategoryCode': '5812'\r","        #         }\r","        #     }\r","        # }\r","    }, separators=(',', ':'))  # Minified JSON\r","\r","    # [!code highlight:11]\r","    # Step 4: Send the session creation request\r","    auth_header = create_hmac_signature(TOKEN_ID, timestamp, request_id, request_path, request_body)\r","    \r","    response = requests.post(\r","        'https://api-services.pxp.io/api/v1/sessions',\r","        headers={\r","            'X-Client-Id': CLIENT_ID,\r","            'X-Request-Id': request_id,\r","            'Authorization': auth_header,\r","            'Content-Type': 'application/json'\r","        },\r","        data=request_body\r","    )\r","    \r","    response.raise_for_status()\r","    \r","    # [!code highlight:2]\r","    # Step 5: Return the session data to your app\r","    return response.json()\r","\r","# Flask endpoint example\r","@app.route('/api/create-session', methods=['GET'])\r","def create_session_endpoint():\r","    \"\"\"API endpoint to create a checkout session\"\"\"\r","    try:\r","        session_data = create_session()\r","        return jsonify(session_data)\r","    except Exception as e:\r","        print(f'Session creation failed: {e}')\r","        return jsonify({'error': 'Failed to create session'}), 500\r","\r","if __name__ == '__main__':\r","    app.run(debug=True)\r",""],"metadata":{"steps":[]},"basename":"create-session.py","language":"python"}],"downloadAssociatedFiles":[{"path":"guides/checkout/drop-in/ios/_filesets/installation/create-session.py","content":["# Python backend: Create a session for Checkout Drop-in\r","import os\r","import hmac\r","import hashlib\r","import json\r","import time\r","import uuid\r","import requests\r","from flask import Flask, jsonify\r","\r","app = Flask(__name__)\r","\r","# [!code highlight:4]\r","# Step 1: Store your credentials securely\r","CLIENT_ID = os.environ.get('PXP_CLIENT_ID')\r","TOKEN_ID = os.environ.get('PXP_TOKEN_ID')\r","TOKEN_VALUE = os.environ.get('PXP_TOKEN_VALUE')\r","\r","# [!code highlight:11]\r","# Step 2: Create the HMAC signature function\r","def create_hmac_signature(token_id, timestamp, request_id, request_path, request_body):\r","    \"\"\"Generate HMAC signature for authentication\"\"\"\r","    # Concatenate the values with colons\r","    message = f\"{token_id}:{timestamp}:{request_id}:{request_path}:{request_body}\"\r","    \r","    # Create HMAC SHA256 hash\r","    signature = hmac.new(\r","        TOKEN_VALUE.encode('utf-8'),\r","        message.encode('utf-8'),\r","        hashlib.sha256\r","    ).hexdigest()\r","    \r","    return f\"PXP-WCA1 {token_id}:{timestamp}:{signature}\"\r","\r","# [!code highlight:22]\r","# Step 3: Build the session request body\r","def create_session():\r","    \"\"\"Create a new checkout session\"\"\"\r","    timestamp = str(int(time.time() * 1000))\r","    request_id = str(uuid.uuid4())\r","    request_path = 'api/v1/sessions'\r","    \r","    request_body = json.dumps({\r","        'merchant': 'MERCHANT-1',\r","        'site': 'SITE-1',\r","        'sessionTimeout': 120,\r","        'merchantTransactionId': str(uuid.uuid4()),\r","        'transactionMethod': {\r","            'intent': {\r","                'card': 'Authorisation',\r","                'paypal': 'Purchase'\r","            }\r","        },\r","        'amounts': {\r","            'currencyCode': 'USD',\r","            'transactionValue': 49.99\r","        },\r","        'allowTransaction': True,\r","        'serviceType': 'CheckoutDropIn'\r","        # Optional — when Paze is enabled in the Unity Portal, the session response\r","        # includes allowedFundingTypes.wallets.paze. You may also request it explicitly:\r","        # 'allowedFundingTypes': {\r","        #     'wallets': {\r","        #         'paze': {\r","        #             'clientId': 'your-paze-client-id',\r","        #             'merchantCategoryCode': '5812'\r","        #         }\r","        #     }\r","        # }\r","    }, separators=(',', ':'))  # Minified JSON\r","\r","    # [!code highlight:11]\r","    # Step 4: Send the session creation request\r","    auth_header = create_hmac_signature(TOKEN_ID, timestamp, request_id, request_path, request_body)\r","    \r","    response = requests.post(\r","        'https://api-services.pxp.io/api/v1/sessions',\r","        headers={\r","            'X-Client-Id': CLIENT_ID,\r","            'X-Request-Id': request_id,\r","            'Authorization': auth_header,\r","            'Content-Type': 'application/json'\r","        },\r","        data=request_body\r","    )\r","    \r","    response.raise_for_status()\r","    \r","    # [!code highlight:2]\r","    # Step 5: Return the session data to your app\r","    return response.json()\r","\r","# Flask endpoint example\r","@app.route('/api/create-session', methods=['GET'])\r","def create_session_endpoint():\r","    \"\"\"API endpoint to create a checkout session\"\"\"\r","    try:\r","        session_data = create_session()\r","        return jsonify(session_data)\r","    except Exception as e:\r","        print(f'Session creation failed: {e}')\r","        return jsonify({'error': 'Failed to create session'}), 500\r","\r","if __name__ == '__main__':\r","    app.run(debug=True)\r",""],"metadata":{"steps":[]},"basename":"create-session.py","language":"python"}],"when":{"Backend":"python"}}],"steps":[{"id":"credentials-setup","heading":"Store your credentials securely"},{"id":"create-signature-function","heading":"Create the HMAC signature function"},{"id":"build-request-body","heading":"Build the session request body"},{"id":"send-session-request","heading":"Send the session creation request"},{"id":"handle-response","heading":"Return the session data to your app"}],"inputs":{},"toggles":{}},"children":[{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"credentials-setup","heading":"Store your credentials securely"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Set up your API credentials as environment variables — never hardcode them in your application."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"create-signature-function","heading":"Create the HMAC signature function"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This function generates a secure authentication hash by combining the timestamp, request ID, request path, and request body, then hashing with your token value using HMAC SHA256."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"build-request-body","heading":"Build the session request body"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Create a request with your merchant details and transaction information. The request body must be minified (no whitespace) for the HMAC signature. Set ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["serviceType"]}," to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["\"CheckoutDropIn\""]}," for Drop-in integrations."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"send-session-request","heading":"Send the session creation request"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["POST to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["https://api-services.pxp.io/api/v1/sessions"]}," with your authentication headers and request body."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"handle-response","heading":"Return the session data to your app"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The API returns the complete session object with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["sessionId"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["hmacKey"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["encryptionKey"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["allowedFundingTypes"]},", and optional ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["restrictions"]},"."]}]}]},{"$$mdtype":"Tag","name":"CodeWalkthrough","attributes":{"__idx":2,"filters":{},"filesets":[{"files":[{"path":"guides/checkout/drop-in/ios/_filesets/installation/CheckoutView.swift","content":["// {% step id=\"import-dependencies\" %}\r","import SwiftUI\r","import PXPCheckoutSDK\r","// {% /step %}\r","\r","// {% step id=\"setup-view\" %}\r","struct CheckoutView: View {\r","    @StateObject private var viewModel = CheckoutViewModel()\r","    \r","    var body: some View {\r","        Group {\r","            if let dropIn = viewModel.dropIn {\r","                // {% step id=\"render-content\" %}\r","                dropIn.buildContent()\r","                // {% /step %}\r","            } else if let error = viewModel.errorMessage {\r","                VStack(spacing: 16) {\r","                    Text(\"Unable to load checkout\")\r","                        .font(.headline)\r","                    Text(error)\r","                        .font(.body)\r","                        .foregroundColor(.secondary)\r","                    Button(\"Retry\") {\r","                        Task {\r","                            await viewModel.loadDropIn()\r","                        }\r","                    }\r","                }\r","                .padding()\r","            } else {\r","                ProgressView(\"Loading checkout...\")\r","            }\r","        }\r","        .task {\r","            await viewModel.loadDropIn()\r","        }\r","    }\r","}\r","// {% /step %}\r","\r","@MainActor\r","final class CheckoutViewModel: ObservableObject {\r","    @Published var dropIn: CheckoutDropIn?\r","    @Published var errorMessage: String?\r","    \r","    func loadDropIn() async {\r","        do {\r","            // {% step id=\"fetch-session\" %}\r","            let url = URL(string: \"https://your-backend.com/api/create-session\")!\r","            let (data, _) = try await URLSession.shared.data(from: url)\r","            \r","            // Decode the complete SessionData response — pass all fields to Drop-in (including wallets.paze when enabled)\r","            let session = try JSONDecoder().decode(SessionData.self, from: data)\r","            // {% /step %}\r","            \r","            // {% step id=\"configure-transaction\" %}\r","            let transactionData = DropInTransactionData(\r","                amount: Decimal(string: \"49.99\") ?? 0,\r","                currency: \"USD\",\r","                entryType: .ecom,\r","                intent: DropInTransactionIntentData(\r","                    card: .authorisation,\r","                    paypal: .authorisation\r","                ),\r","                merchantTransactionId: UUID().uuidString,\r","                merchantTransactionDate: { Date() }\r","            )\r","            // {% /step %}\r","            \r","            // {% step id=\"initialize-dropin\" %}\r","            let config = CheckoutDropInConfig(\r","                environment: .test,\r","                session: session,\r","                transactionData: transactionData,\r","                merchantShopperId: \"shopper-123\",\r","                ownerId: \"your-merchant-group-id\",\r","                // {% step id=\"setup-shopper\" %}\r","                onGetShopper: { async in\r","                    TransactionShopper(id: \"shopper-123\")\r","                },\r","                // {% /step %}\r","                // {% step id=\"success-callback\" %}\r","                onSuccess: { result in\r","                    print(\"Payment successful: \\(result.systemTransactionId)\")\r","                    // paymentData is nil for card, PayPal, and Apple Pay; Paze may return .paze(...)\r","                    Task {\r","                        await self.verifyPaymentOnBackend(result)\r","                    }\r","                },\r","                // {% /step %}\r","                // {% step id=\"error-callback\" %}\r","                onError: { paymentMethod, error in\r","                    print(\"Payment failed: \\(error.errorMessage)\")\r","                    Task { @MainActor in\r","                        self.errorMessage = error.errorMessage\r","                    }\r","                }\r","                // {% /step %}\r","            )\r","            \r","            let instance = try CheckoutDropIn(config: config)\r","            // {% /step %}\r","            \r","            // {% step id=\"create-dropin\" %}\r","            await instance.create()\r","            dropIn = instance\r","            // {% /step %}\r","            \r","        } catch {\r","            errorMessage = error.localizedDescription\r","        }\r","    }\r","    \r","    private func verifyPaymentOnBackend(_ result: DropInSubmitResult) async {\r","        // Send transaction details to your backend for verification\r","        do {\r","            let url = URL(string: \"https://your-backend.com/api/verify-payment\")!\r","            var request = URLRequest(url: url)\r","            request.httpMethod = \"POST\"\r","            request.setValue(\"application/json\", forHTTPHeaderField: \"Content-Type\")\r","            \r","            let body = [\r","                \"systemTransactionId\": result.systemTransactionId,\r","                \"merchantTransactionId\": result.merchantTransactionId ?? \"\"\r","            ]\r","            request.httpBody = try JSONEncoder().encode(body)\r","            \r","            let (data, _) = try await URLSession.shared.data(for: request)\r","            let verification = try JSONDecoder().decode(VerificationResponse.self, from: data)\r","            \r","            if verification.success {\r","                print(\"Payment verified! Order ID: \\(verification.orderId ?? \"\")\")\r","            }\r","        } catch {\r","            print(\"Verification error: \\(error.localizedDescription)\")\r","        }\r","    }\r","}\r","\r","struct VerificationResponse: Codable {\r","    let success: Bool\r","    let orderId: String?\r","    let error: String?\r","}\r",""],"metadata":{"steps":[]},"basename":"CheckoutView.swift","language":"swift"}],"downloadAssociatedFiles":[{"path":"guides/checkout/drop-in/ios/_filesets/installation/CheckoutView.swift","content":["// {% step id=\"import-dependencies\" %}\r","import SwiftUI\r","import PXPCheckoutSDK\r","// {% /step %}\r","\r","// {% step id=\"setup-view\" %}\r","struct CheckoutView: View {\r","    @StateObject private var viewModel = CheckoutViewModel()\r","    \r","    var body: some View {\r","        Group {\r","            if let dropIn = viewModel.dropIn {\r","                // {% step id=\"render-content\" %}\r","                dropIn.buildContent()\r","                // {% /step %}\r","            } else if let error = viewModel.errorMessage {\r","                VStack(spacing: 16) {\r","                    Text(\"Unable to load checkout\")\r","                        .font(.headline)\r","                    Text(error)\r","                        .font(.body)\r","                        .foregroundColor(.secondary)\r","                    Button(\"Retry\") {\r","                        Task {\r","                            await viewModel.loadDropIn()\r","                        }\r","                    }\r","                }\r","                .padding()\r","            } else {\r","                ProgressView(\"Loading checkout...\")\r","            }\r","        }\r","        .task {\r","            await viewModel.loadDropIn()\r","        }\r","    }\r","}\r","// {% /step %}\r","\r","@MainActor\r","final class CheckoutViewModel: ObservableObject {\r","    @Published var dropIn: CheckoutDropIn?\r","    @Published var errorMessage: String?\r","    \r","    func loadDropIn() async {\r","        do {\r","            // {% step id=\"fetch-session\" %}\r","            let url = URL(string: \"https://your-backend.com/api/create-session\")!\r","            let (data, _) = try await URLSession.shared.data(from: url)\r","            \r","            // Decode the complete SessionData response — pass all fields to Drop-in (including wallets.paze when enabled)\r","            let session = try JSONDecoder().decode(SessionData.self, from: data)\r","            // {% /step %}\r","            \r","            // {% step id=\"configure-transaction\" %}\r","            let transactionData = DropInTransactionData(\r","                amount: Decimal(string: \"49.99\") ?? 0,\r","                currency: \"USD\",\r","                entryType: .ecom,\r","                intent: DropInTransactionIntentData(\r","                    card: .authorisation,\r","                    paypal: .authorisation\r","                ),\r","                merchantTransactionId: UUID().uuidString,\r","                merchantTransactionDate: { Date() }\r","            )\r","            // {% /step %}\r","            \r","            // {% step id=\"initialize-dropin\" %}\r","            let config = CheckoutDropInConfig(\r","                environment: .test,\r","                session: session,\r","                transactionData: transactionData,\r","                merchantShopperId: \"shopper-123\",\r","                ownerId: \"your-merchant-group-id\",\r","                // {% step id=\"setup-shopper\" %}\r","                onGetShopper: { async in\r","                    TransactionShopper(id: \"shopper-123\")\r","                },\r","                // {% /step %}\r","                // {% step id=\"success-callback\" %}\r","                onSuccess: { result in\r","                    print(\"Payment successful: \\(result.systemTransactionId)\")\r","                    // paymentData is nil for card, PayPal, and Apple Pay; Paze may return .paze(...)\r","                    Task {\r","                        await self.verifyPaymentOnBackend(result)\r","                    }\r","                },\r","                // {% /step %}\r","                // {% step id=\"error-callback\" %}\r","                onError: { paymentMethod, error in\r","                    print(\"Payment failed: \\(error.errorMessage)\")\r","                    Task { @MainActor in\r","                        self.errorMessage = error.errorMessage\r","                    }\r","                }\r","                // {% /step %}\r","            )\r","            \r","            let instance = try CheckoutDropIn(config: config)\r","            // {% /step %}\r","            \r","            // {% step id=\"create-dropin\" %}\r","            await instance.create()\r","            dropIn = instance\r","            // {% /step %}\r","            \r","        } catch {\r","            errorMessage = error.localizedDescription\r","        }\r","    }\r","    \r","    private func verifyPaymentOnBackend(_ result: DropInSubmitResult) async {\r","        // Send transaction details to your backend for verification\r","        do {\r","            let url = URL(string: \"https://your-backend.com/api/verify-payment\")!\r","            var request = URLRequest(url: url)\r","            request.httpMethod = \"POST\"\r","            request.setValue(\"application/json\", forHTTPHeaderField: \"Content-Type\")\r","            \r","            let body = [\r","                \"systemTransactionId\": result.systemTransactionId,\r","                \"merchantTransactionId\": result.merchantTransactionId ?? \"\"\r","            ]\r","            request.httpBody = try JSONEncoder().encode(body)\r","            \r","            let (data, _) = try await URLSession.shared.data(for: request)\r","            let verification = try JSONDecoder().decode(VerificationResponse.self, from: data)\r","            \r","            if verification.success {\r","                print(\"Payment verified! Order ID: \\(verification.orderId ?? \"\")\")\r","            }\r","        } catch {\r","            print(\"Verification error: \\(error.localizedDescription)\")\r","        }\r","    }\r","}\r","\r","struct VerificationResponse: Codable {\r","    let success: Bool\r","    let orderId: String?\r","    let error: String?\r","}\r",""],"metadata":{"steps":[]},"basename":"CheckoutView.swift","language":"swift"}]}],"steps":[{"id":"import-dependencies","heading":"Import the required dependencies"},{"id":"setup-view","heading":"Set up your SwiftUI view"},{"id":"fetch-session","heading":"Fetch the session data from your backend"},{"id":"configure-transaction","heading":"Configure the transaction data"},{"id":"initialize-dropin","heading":"Initialise Checkout Drop-in"},{"id":"setup-shopper","heading":"Provide shopper information (optional)"},{"id":"success-callback","heading":"Handle successful payments"},{"id":"error-callback","heading":"Handle payment errors"},{"id":"create-dropin","heading":"Create the Drop-in component"},{"id":"render-content","heading":"Render the Drop-in content"}],"inputs":{},"toggles":{}},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"initialise-drop-in-in-your-app","__idx":4},"children":["Initialise Drop-in in your app"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The following example shows a complete minimal SwiftUI integration using ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CheckoutDropInConfig"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["DropInTransactionData"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CheckoutDropIn(config:)"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["await create()"]},", and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["buildContent()"]},"."]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"import-dependencies","heading":"Import the required dependencies"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Import ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SwiftUI"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["PXPCheckoutSDK"]}," from the iOS SDK."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"setup-view","heading":"Set up your SwiftUI view"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Create a SwiftUI view that will host the Checkout Drop-in interface using the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["@StateObject"]}," property wrapper for the view model."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"fetch-session","heading":"Fetch the session data from your backend"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Call your backend endpoint to get the session data you created in the previous steps. Decode the response directly into a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SessionData"]}," object using ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["JSONDecoder().decode(SessionData.self, from:)"]},". The SDK's ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SessionData"]}," struct includes:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"swift","header":{"controls":{"copy":{}}},"source":"public struct SessionData: Codable {\n    public let sessionId: String\n    public let hmacKey: String\n    public let encryptionKey: String\n    public let sessionExpiry: String?\n    public let allowedFundingTypes: AllowedFundingType?\n    public let restrictions: Restrictions?\n}\n","lang":"swift"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["allowedFundingTypes"]}," controls which payment methods Drop-in can render (cards, PayPal, and Apple Pay when enabled in your session). ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["restrictions"]}," carries card-level filtering rules. Pass the entire decoded ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SessionData"]}," object into ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CheckoutDropInConfig(session:)"]}," — don't map only selected fields."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"configure-transaction","heading":"Configure the transaction data"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Specify the transaction details using ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["DropInTransactionData"]},". All of these fields are required:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"swift","header":{"controls":{"copy":{}}},"source":"DropInTransactionData(\n    amount: Decimal(string: \"99.99\") ?? 0,\n    currency: \"USD\",\n    entryType: .ecom,\n    intent: DropInTransactionIntentData(\n        card: .authorisation,\n        paypal: .authorisation\n    ),\n    merchantTransactionId: \"test-txn-123\",\n    merchantTransactionDate: { Date() }\n)\n","lang":"swift"},"children":[]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["amount"]},": the transaction amount as a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Decimal"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["currency"]},": a three-letter currency code (e.g., ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["\"USD\""]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["\"GBP\""]},")."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["entryType"]},": the entry type — use ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".ecom"]}," for PayPal"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["intent"]},": the payment intent for each method (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".authorisation"]}," or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".purchase"]},")."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["merchantTransactionId"]},": your unique transaction identifier"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["merchantTransactionDate"]},": closure returning the transaction date (typically ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["{ Date() }"]},")"]}]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"initialize-dropin","heading":"Initialise Checkout Drop-in"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Configure Drop-in with your environment, session data, transaction details, merchant shopper ID, and owner ID."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"setup-shopper","heading":"Provide shopper information (optional)"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Implement the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onGetShopper"]}," callback when you need card-on-file or shopper-scoped flows. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["merchantShopperId"]}," on ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CheckoutDropInConfig"]}," is always required — this callback returns the matching ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["TransactionShopper"]}," when Drop-in needs it (including at the start of ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["create()"]},")."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Callback signature:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"swift","header":{"controls":{"copy":{}}},"source":"onGetShopper: (() async -> TransactionShopper?)?\n","lang":"swift"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onGetShopper"]}," is asynchronous because shopper data may come from merchant storage or backend:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"swift","header":{"controls":{"copy":{}}},"source":"onGetShopper: { async in\n    TransactionShopper(id: \"shopper-123\")\n}\n","lang":"swift"},"children":[]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"success-callback","heading":"Handle successful payments"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Implement the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onSuccess"]}," callback to handle successful payments. Always verify payments on your backend before fulfilling orders — frontend callbacks can be manipulated."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Callback signature:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"swift","header":{"controls":{"copy":{}}},"source":"onSuccess: ((DropInSubmitResult) -> Void)?\n","lang":"swift"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onSuccess"]}," is synchronous and receives ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["DropInSubmitResult"]}," with transaction identifiers and the payment method:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"swift","header":{"controls":{"copy":{}}},"source":"onSuccess: { result in\n    let systemTransactionId = result.systemTransactionId\n    let merchantTransactionId = result.merchantTransactionId\n    let paymentMethod = result.paymentMethod\n    // paymentData is nil for card, PayPal, and Apple Pay;\n\n    // Verify the payment on your backend before fulfilling the order.\n}\n","lang":"swift"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If your app needs to update UI from a non-main context, wrap that update in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Task { @MainActor in ... }"]},"."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"error-callback","heading":"Handle payment errors"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Implement the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onError"]}," callback to handle payment failures and display appropriate error messages."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Callback signature:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"swift","header":{"controls":{"copy":{}}},"source":"onError: ((DropInPaymentMethod?, BaseSdkException) -> Void)?\n","lang":"swift"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onError"]}," is synchronous. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["paymentMethod"]}," can be ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["nil"]}," for initialisation or configuration errors before a payment method is selected:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"swift","header":{"controls":{"copy":{}}},"source":"onError: { paymentMethod, error in\n    print(\"Payment failed: \\(error.errorMessage)\")\n    Task { @MainActor in\n        self.errorMessage = error.errorMessage\n    }\n}\n","lang":"swift"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If the surrounding view model is already marked ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["@MainActor"]},", a simpler form works:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"swift","header":{"controls":{"copy":{}}},"source":"onError: { paymentMethod, error in\n    self.errorMessage = error.errorMessage\n}\n","lang":"swift"},"children":[]}]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"info"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For Apple Pay integrations that need dynamic shipping, payment method, or coupon handling, use the optional callbacks in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["DropInApplePayConfig"]},". See the ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/guides/checkout/drop-in/ios/implementation"},"children":["implementation guide"]}," and ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/guides/checkout/drop-in/ios/events"},"children":["events guide"]}," for full examples."]}]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"info"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["PayPal doesn't invoke ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onSubmit"]},"."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"create-dropin","heading":"Create the Drop-in component"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Call the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["create()"]}," method from an async task to initialise the Drop-in component."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"render-content","heading":"Render the Drop-in content"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Call the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["buildContent()"]}," method to display the payment interface in your SwiftUI view."]}]}]},{"$$mdtype":"Tag","name":"CodeWalkthrough","attributes":{"__idx":3,"filters":{"Backend":{"items":[{"label":"Node.js","value":"nodejs"},{"label":"Python","value":"python"}],"default":"nodejs"}},"filesets":[{"files":[{"path":"guides/checkout/drop-in/ios/_filesets/installation/webhook-handler.js","content":["// Node.js backend: Webhook handler for payment verification\r","const crypto = require('crypto');\r","const express = require('express');\r","const app = express();\r","\r","app.use(express.json());\r","\r","// [!code highlight:19]\r","// Step 1: Create the webhook endpoint\r","app.post('/webhooks/pxp', async (req, res) => {\r","  try {\r","    const events = req.body;\r","    \r","    // Step 2: Verify webhook signature\r","    const signature = req.headers['x-webhook-signature'];\r","    const expectedSignature = createWebhookSignature(JSON.stringify(events));\r","    \r","    if (signature !== expectedSignature) {\r","      console.error('Invalid webhook signature');\r","      return res.status(401).json({ error: 'Unauthorized' });\r","    }\r","    \r","    // [!code highlight:48]\r","    // Step 3: Process webhook events\r","    for (const event of events) {\r","      if (event.eventCategory === 'Transaction') {\r","        const transaction = event.eventData;\r","        \r","        console.log('Processing transaction:', transaction.systemTransactionId);\r","        \r","        // Step 4: Check payment state\r","        if (transaction.state !== 'Authorised' && transaction.state !== 'Captured') {\r","          console.log(`Skipping transaction in state: ${transaction.state}`);\r","          continue;\r","        }\r","        \r","        // Step 5: Prevent duplicate processing\r","        const existingTransaction = await db.transactions.findOne({\r","          systemTransactionId: transaction.systemTransactionId\r","        });\r","        \r","        if (existingTransaction) {\r","          console.log('Transaction already processed, skipping');\r","          continue;\r","        }\r","        \r","        // Step 6: Verify transaction details\r","        const order = await db.orders.findOne({\r","          merchantTransactionId: transaction.merchantTransactionId\r","        });\r","        \r","        if (!order) {\r","          console.error('Order not found:', transaction.merchantTransactionId);\r","          continue;\r","        }\r","        \r","        // Verify amount matches\r","        const transactionAmount = transaction.amounts?.transactionValue || transaction.amount || 0;\r","        if (Math.abs(transactionAmount - order.amount) > 0.01) {\r","          console.error('Amount mismatch:', {\r","            expected: order.amount,\r","            actual: transactionAmount\r","          });\r","          continue;\r","        }\r","        \r","        // Verify currency matches\r","        const transactionCurrency = transaction.amounts?.currencyCode || transaction.currency;\r","        if (transactionCurrency !== order.currency) {\r","          console.error('Currency mismatch');\r","          continue;\r","        }\r","        \r","        // [!code highlight:17]\r","        // Step 7: Fulfil the order\r","        // Mark transaction as processed\r","        await db.transactions.create({\r","          systemTransactionId: transaction.systemTransactionId,\r","          merchantTransactionId: transaction.merchantTransactionId,\r","          amount: transactionAmount,\r","          currency: transactionCurrency,\r","          state: transaction.state,\r","          paymentMethod: transaction.fundingData?.fundingType || 'Unknown',\r","          processedAt: new Date()\r","        });\r","        \r","        // Fulfil the order\r","        await fulfillOrder(order.id, transaction.systemTransactionId);\r","        \r","        console.log('Order fulfilled:', order.id);\r","      }\r","    }\r","    \r","    // [!code highlight:2]\r","    // Step 8: Respond to webhook\r","    res.json({ state: 'Success' });\r","    \r","  } catch (error) {\r","    console.error('Webhook processing error:', error);\r","    // Always return 200 to prevent retries\r","    res.json({ state: 'Success' });\r","  }\r","});\r","\r","function createWebhookSignature(body) {\r","  const secret = process.env.WEBHOOK_SECRET;\r","  return crypto\r","    .createHmac('sha256', secret)\r","    .update(body)\r","    .digest('hex');\r","}\r","\r","async function fulfillOrder(orderId, systemTransactionId) {\r","  // Your order fulfilment logic here\r","  // - Update order status\r","  // - Send confirmation email\r","  // - Trigger shipping\r","  // - Update inventory\r","  console.log(`Fulfilling order ${orderId} for transaction ${systemTransactionId}`);\r","}\r","\r","// Start server\r","const PORT = process.env.PORT || 3000;\r","app.listen(PORT, () => {\r","  console.log(`Webhook handler listening on port ${PORT}`);\r","});\r",""],"metadata":{"steps":[]},"basename":"webhook-handler.js","language":"javascript"}],"downloadAssociatedFiles":[{"path":"guides/checkout/drop-in/ios/_filesets/installation/webhook-handler.js","content":["// Node.js backend: Webhook handler for payment verification\r","const crypto = require('crypto');\r","const express = require('express');\r","const app = express();\r","\r","app.use(express.json());\r","\r","// [!code highlight:19]\r","// Step 1: Create the webhook endpoint\r","app.post('/webhooks/pxp', async (req, res) => {\r","  try {\r","    const events = req.body;\r","    \r","    // Step 2: Verify webhook signature\r","    const signature = req.headers['x-webhook-signature'];\r","    const expectedSignature = createWebhookSignature(JSON.stringify(events));\r","    \r","    if (signature !== expectedSignature) {\r","      console.error('Invalid webhook signature');\r","      return res.status(401).json({ error: 'Unauthorized' });\r","    }\r","    \r","    // [!code highlight:48]\r","    // Step 3: Process webhook events\r","    for (const event of events) {\r","      if (event.eventCategory === 'Transaction') {\r","        const transaction = event.eventData;\r","        \r","        console.log('Processing transaction:', transaction.systemTransactionId);\r","        \r","        // Step 4: Check payment state\r","        if (transaction.state !== 'Authorised' && transaction.state !== 'Captured') {\r","          console.log(`Skipping transaction in state: ${transaction.state}`);\r","          continue;\r","        }\r","        \r","        // Step 5: Prevent duplicate processing\r","        const existingTransaction = await db.transactions.findOne({\r","          systemTransactionId: transaction.systemTransactionId\r","        });\r","        \r","        if (existingTransaction) {\r","          console.log('Transaction already processed, skipping');\r","          continue;\r","        }\r","        \r","        // Step 6: Verify transaction details\r","        const order = await db.orders.findOne({\r","          merchantTransactionId: transaction.merchantTransactionId\r","        });\r","        \r","        if (!order) {\r","          console.error('Order not found:', transaction.merchantTransactionId);\r","          continue;\r","        }\r","        \r","        // Verify amount matches\r","        const transactionAmount = transaction.amounts?.transactionValue || transaction.amount || 0;\r","        if (Math.abs(transactionAmount - order.amount) > 0.01) {\r","          console.error('Amount mismatch:', {\r","            expected: order.amount,\r","            actual: transactionAmount\r","          });\r","          continue;\r","        }\r","        \r","        // Verify currency matches\r","        const transactionCurrency = transaction.amounts?.currencyCode || transaction.currency;\r","        if (transactionCurrency !== order.currency) {\r","          console.error('Currency mismatch');\r","          continue;\r","        }\r","        \r","        // [!code highlight:17]\r","        // Step 7: Fulfil the order\r","        // Mark transaction as processed\r","        await db.transactions.create({\r","          systemTransactionId: transaction.systemTransactionId,\r","          merchantTransactionId: transaction.merchantTransactionId,\r","          amount: transactionAmount,\r","          currency: transactionCurrency,\r","          state: transaction.state,\r","          paymentMethod: transaction.fundingData?.fundingType || 'Unknown',\r","          processedAt: new Date()\r","        });\r","        \r","        // Fulfil the order\r","        await fulfillOrder(order.id, transaction.systemTransactionId);\r","        \r","        console.log('Order fulfilled:', order.id);\r","      }\r","    }\r","    \r","    // [!code highlight:2]\r","    // Step 8: Respond to webhook\r","    res.json({ state: 'Success' });\r","    \r","  } catch (error) {\r","    console.error('Webhook processing error:', error);\r","    // Always return 200 to prevent retries\r","    res.json({ state: 'Success' });\r","  }\r","});\r","\r","function createWebhookSignature(body) {\r","  const secret = process.env.WEBHOOK_SECRET;\r","  return crypto\r","    .createHmac('sha256', secret)\r","    .update(body)\r","    .digest('hex');\r","}\r","\r","async function fulfillOrder(orderId, systemTransactionId) {\r","  // Your order fulfilment logic here\r","  // - Update order status\r","  // - Send confirmation email\r","  // - Trigger shipping\r","  // - Update inventory\r","  console.log(`Fulfilling order ${orderId} for transaction ${systemTransactionId}`);\r","}\r","\r","// Start server\r","const PORT = process.env.PORT || 3000;\r","app.listen(PORT, () => {\r","  console.log(`Webhook handler listening on port ${PORT}`);\r","});\r",""],"metadata":{"steps":[]},"basename":"webhook-handler.js","language":"javascript"}],"when":{"Backend":"nodejs"}},{"files":[{"path":"guides/checkout/drop-in/ios/_filesets/installation/webhook-handler.py","content":["# Python backend: Webhook handler for payment verification\r","import os\r","import hmac\r","import hashlib\r","import json\r","from flask import Flask, request, jsonify\r","from datetime import datetime\r","\r","app = Flask(__name__)\r","\r","# [!code highlight:14]\r","# Step 1: Create the webhook endpoint\r","@app.route('/webhooks/pxp', methods=['POST'])\r","def webhook_handler():\r","    \"\"\"Handle payment webhook notifications from Unity\"\"\"\r","    try:\r","        events = request.json\r","        \r","        # Step 2: Verify webhook signature\r","        signature = request.headers.get('X-Webhook-Signature')\r","        expected_signature = create_webhook_signature(json.dumps(events, separators=(',', ':')))\r","        \r","        if signature != expected_signature:\r","            print('Invalid webhook signature')\r","            return jsonify({'error': 'Unauthorized'}), 401\r","        \r","        # [!code highlight:49]\r","        # Step 3: Process webhook events\r","        for event in events:\r","            if event.get('eventCategory') == 'Transaction':\r","                transaction = event.get('eventData')\r","                \r","                print(f\"Processing transaction: {transaction.get('systemTransactionId')}\")\r","                \r","                # Step 4: Check payment state\r","                state = transaction.get('state')\r","                if state not in ['Authorised', 'Captured']:\r","                    print(f\"Skipping transaction in state: {state}\")\r","                    continue\r","                \r","                # Step 5: Prevent duplicate processing\r","                system_txn_id = transaction.get('systemTransactionId')\r","                existing_transaction = db.transactions.find_one({\r","                    'systemTransactionId': system_txn_id\r","                })\r","                \r","                if existing_transaction:\r","                    print('Transaction already processed, skipping')\r","                    continue\r","                \r","                # Step 6: Verify transaction details\r","                merchant_txn_id = transaction.get('merchantTransactionId')\r","                order = db.orders.find_one({\r","                    'merchantTransactionId': merchant_txn_id\r","                })\r","                \r","                if not order:\r","                    print(f\"Order not found: {merchant_txn_id}\")\r","                    continue\r","                \r","                # Verify amount matches\r","                amounts = transaction.get('amounts', {})\r","                transaction_amount = amounts.get('transactionValue') or transaction.get('amount', 0)\r","                \r","                if abs(transaction_amount - order['amount']) > 0.01:\r","                    print(f\"Amount mismatch - Expected: {order['amount']}, Actual: {transaction_amount}\")\r","                    continue\r","                \r","                # Verify currency matches\r","                transaction_currency = amounts.get('currencyCode') or transaction.get('currency')\r","                if transaction_currency != order['currency']:\r","                    print('Currency mismatch')\r","                    continue\r","                \r","                # [!code highlight:19]\r","                # Step 7: Fulfil the order\r","                # Mark transaction as processed\r","                funding_data = transaction.get('fundingData', {})\r","                db.transactions.insert_one({\r","                    'systemTransactionId': system_txn_id,\r","                    'merchantTransactionId': merchant_txn_id,\r","                    'amount': transaction_amount,\r","                    'currency': transaction_currency,\r","                    'state': state,\r","                    'paymentMethod': funding_data.get('fundingType', 'Unknown'),\r","                    'processedAt': datetime.utcnow()\r","                })\r","                \r","                # Fulfil the order\r","                fulfill_order(order['id'], system_txn_id)\r","                \r","                print(f\"Order fulfilled: {order['id']}\")\r","        \r","        # [!code highlight:2]\r","        # Step 8: Respond to webhook\r","        return jsonify({'state': 'Success'})\r","        \r","    except Exception as e:\r","        print(f'Webhook processing error: {e}')\r","        # Always return 200 to prevent retries\r","        return jsonify({'state': 'Success'})\r","\r","def create_webhook_signature(body):\r","    \"\"\"Generate webhook signature for verification\"\"\"\r","    secret = os.environ.get('WEBHOOK_SECRET')\r","    return hmac.new(\r","        secret.encode('utf-8'),\r","        body.encode('utf-8'),\r","        hashlib.sha256\r","    ).hexdigest()\r","\r","def fulfill_order(order_id, system_transaction_id):\r","    \"\"\"Fulfill order after payment verification\"\"\"\r","    # Your order fulfilment logic here:\r","    # - Update order status\r","    # - Send confirmation email\r","    # - Trigger shipping\r","    # - Update inventory\r","    print(f\"Fulfilling order {order_id} for transaction {system_transaction_id}\")\r","\r","if __name__ == '__main__':\r","    app.run(debug=True, port=3000)\r",""],"metadata":{"steps":[]},"basename":"webhook-handler.py","language":"python"}],"downloadAssociatedFiles":[{"path":"guides/checkout/drop-in/ios/_filesets/installation/webhook-handler.py","content":["# Python backend: Webhook handler for payment verification\r","import os\r","import hmac\r","import hashlib\r","import json\r","from flask import Flask, request, jsonify\r","from datetime import datetime\r","\r","app = Flask(__name__)\r","\r","# [!code highlight:14]\r","# Step 1: Create the webhook endpoint\r","@app.route('/webhooks/pxp', methods=['POST'])\r","def webhook_handler():\r","    \"\"\"Handle payment webhook notifications from Unity\"\"\"\r","    try:\r","        events = request.json\r","        \r","        # Step 2: Verify webhook signature\r","        signature = request.headers.get('X-Webhook-Signature')\r","        expected_signature = create_webhook_signature(json.dumps(events, separators=(',', ':')))\r","        \r","        if signature != expected_signature:\r","            print('Invalid webhook signature')\r","            return jsonify({'error': 'Unauthorized'}), 401\r","        \r","        # [!code highlight:49]\r","        # Step 3: Process webhook events\r","        for event in events:\r","            if event.get('eventCategory') == 'Transaction':\r","                transaction = event.get('eventData')\r","                \r","                print(f\"Processing transaction: {transaction.get('systemTransactionId')}\")\r","                \r","                # Step 4: Check payment state\r","                state = transaction.get('state')\r","                if state not in ['Authorised', 'Captured']:\r","                    print(f\"Skipping transaction in state: {state}\")\r","                    continue\r","                \r","                # Step 5: Prevent duplicate processing\r","                system_txn_id = transaction.get('systemTransactionId')\r","                existing_transaction = db.transactions.find_one({\r","                    'systemTransactionId': system_txn_id\r","                })\r","                \r","                if existing_transaction:\r","                    print('Transaction already processed, skipping')\r","                    continue\r","                \r","                # Step 6: Verify transaction details\r","                merchant_txn_id = transaction.get('merchantTransactionId')\r","                order = db.orders.find_one({\r","                    'merchantTransactionId': merchant_txn_id\r","                })\r","                \r","                if not order:\r","                    print(f\"Order not found: {merchant_txn_id}\")\r","                    continue\r","                \r","                # Verify amount matches\r","                amounts = transaction.get('amounts', {})\r","                transaction_amount = amounts.get('transactionValue') or transaction.get('amount', 0)\r","                \r","                if abs(transaction_amount - order['amount']) > 0.01:\r","                    print(f\"Amount mismatch - Expected: {order['amount']}, Actual: {transaction_amount}\")\r","                    continue\r","                \r","                # Verify currency matches\r","                transaction_currency = amounts.get('currencyCode') or transaction.get('currency')\r","                if transaction_currency != order['currency']:\r","                    print('Currency mismatch')\r","                    continue\r","                \r","                # [!code highlight:19]\r","                # Step 7: Fulfil the order\r","                # Mark transaction as processed\r","                funding_data = transaction.get('fundingData', {})\r","                db.transactions.insert_one({\r","                    'systemTransactionId': system_txn_id,\r","                    'merchantTransactionId': merchant_txn_id,\r","                    'amount': transaction_amount,\r","                    'currency': transaction_currency,\r","                    'state': state,\r","                    'paymentMethod': funding_data.get('fundingType', 'Unknown'),\r","                    'processedAt': datetime.utcnow()\r","                })\r","                \r","                # Fulfil the order\r","                fulfill_order(order['id'], system_txn_id)\r","                \r","                print(f\"Order fulfilled: {order['id']}\")\r","        \r","        # [!code highlight:2]\r","        # Step 8: Respond to webhook\r","        return jsonify({'state': 'Success'})\r","        \r","    except Exception as e:\r","        print(f'Webhook processing error: {e}')\r","        # Always return 200 to prevent retries\r","        return jsonify({'state': 'Success'})\r","\r","def create_webhook_signature(body):\r","    \"\"\"Generate webhook signature for verification\"\"\"\r","    secret = os.environ.get('WEBHOOK_SECRET')\r","    return hmac.new(\r","        secret.encode('utf-8'),\r","        body.encode('utf-8'),\r","        hashlib.sha256\r","    ).hexdigest()\r","\r","def fulfill_order(order_id, system_transaction_id):\r","    \"\"\"Fulfill order after payment verification\"\"\"\r","    # Your order fulfilment logic here:\r","    # - Update order status\r","    # - Send confirmation email\r","    # - Trigger shipping\r","    # - Update inventory\r","    print(f\"Fulfilling order {order_id} for transaction {system_transaction_id}\")\r","\r","if __name__ == '__main__':\r","    app.run(debug=True, port=3000)\r",""],"metadata":{"steps":[]},"basename":"webhook-handler.py","language":"python"}],"when":{"Backend":"python"}}],"steps":[{"id":"webhook-endpoint","heading":"Create the webhook endpoint"},{"id":"verify-signature","heading":"Verify webhook signature"},{"id":"process-events","heading":"Process webhook events"},{"id":"check-state","heading":"Check payment state"},{"id":"prevent-duplicates","heading":"Prevent duplicate processing"},{"id":"verify-details","heading":"Verify transaction details"},{"id":"fulfill-order","heading":"Fulfil the order"},{"id":"respond","heading":"Respond to webhook"}],"inputs":{},"toggles":{}},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"verify-payments","__idx":5},"children":["Verify payments"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["When a payment succeeds, the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onSuccess"]}," callback fires with transaction details. However, you must ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["always verify the payment on your backend"]}," before fulfilling orders."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Configure webhooks in the Unity Portal to receive real-time payment notifications on your backend."]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"webhook-endpoint","heading":"Create the webhook endpoint"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Set up an endpoint at ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["/webhooks/pxp"]}," to receive payment notifications from Unity."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"verify-signature","heading":"Verify webhook signature"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Verify the webhook authenticity using HMAC signature validation to prevent fraudulent requests."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"process-events","heading":"Process webhook events"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Loop through the events array and filter for Transaction events."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"check-state","heading":"Check payment state"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Verify the transaction state is ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Authorised"]}," or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Captured"]}," before processing."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"prevent-duplicates","heading":"Prevent duplicate processing"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Check if you've already processed this transaction using ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["systemTransactionId"]},"."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"verify-details","heading":"Verify transaction details"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Match the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["merchantTransactionId"]},", amount, and currency against your order records."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"fulfill-order","heading":"Fulfil the order"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If verification passes, fulfil the order and mark the transaction as processed."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"respond","heading":"Respond to webhook"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Always return ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["{ state: 'Success' }"]}," to acknowledge receipt, even if processing failed."]}]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"info"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["You can also verify payments using the Transactions API to query transaction status directly. See the ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/guides/checkout/drop-in/ios/implementation#step-6-handle-payment-callbacks"},"children":["Implementation guide"]}," for details."]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["That's it! You now have a working Checkout Drop-in integration."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"whats-next","__idx":6},"children":["What's next?"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Now that you have Drop-in running, here are the recommended next steps:"]},{"$$mdtype":"Tag","name":"ul","attributes":{"className":"code-walkthrough-list"},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Customise the look and feel in the ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"https://portal.pxp.io","target":"_blank"},"children":["Unity Portal"]}," (Checkout Drop-In site settings)."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"a","attributes":{"href":"/guides/checkout/drop-in/ios/implementation#step-6-handle-payment-callbacks"},"children":["Set up backend verification"]}," to verify payments before fulfilling orders."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"a","attributes":{"href":"/guides/checkout/drop-in/ios/events"},"children":["Add optional callbacks"]}," to enhance the user experience with validation and loading states."]}]}]},"headings":[{"value":"Quickstart","id":"quickstart","depth":1},{"value":"Pre-requisites","id":"pre-requisites","depth":2},{"value":"Add the SDK dependency","id":"add-the-sdk-dependency","depth":2},{"value":"Create a session on your backend","id":"create-a-session-on-your-backend","depth":2},{"value":"Initialise Drop-in in your app","id":"initialise-drop-in-in-your-app","depth":2},{"value":"Verify payments","id":"verify-payments","depth":2},{"value":"What's next?","id":"whats-next","depth":2}],"frontmatter":{"markdown":{"toc":{"hide":true}},"footer":{"hide":true},"seo":{"title":"Quickstart"}},"lastModified":"2026-07-14T09:12:22.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/guides/checkout/drop-in/ios/quickstart","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}