Skip to content

Implementation

Complete guide to integrating Checkout Drop-in into your application.

Overview

Checkout Drop-in provides a complete, pre-built payment interface that automatically handles multiple payment methods. It follows a simple three-step lifecycle:

  1. Initialise: Configure Drop-in with your session and transaction data.
  2. Mount: Render the payment interface to your page.
  3. Handle callbacks: Respond to payment success, errors, and other events.

Drop-in automatically detects available payment methods, renders the UI, and handles all payment flows.

Backend verification is mandatory. Always verify payments on your backend before fulfilling orders. Frontend callbacks can be manipulated by malicious users.

Before you start

Make sure you've activated the Checkout Drop-in service in the Unity Portal.

Step 1: Install the Web SDK library

Install the latest version of the Web SDK from the npm public registry. You'll need to have Node.js 22.x or higher.

npm i @pxpio/web-components-sdk

Checkout Drop-in is part of the main SDK package. Import it directly from @pxpio/web-components-sdk/src/checkoutDropIn/CheckoutDropIn.

Step 2: Get your API credentials

In order to initialise Checkout Drop-in, 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: Create a session on your backend

Checkout Drop-in requires a session from the PXP Sessions API. This must be done on your backend using HMAC authentication to keep your credentials secure.

Understanding HMAC authentication

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.

Create an HMAC signature

To create the HMAC signature, you need to prepare a string that includes five parts separated by colons:

  • Token ID: Your API token identifier (e.g., 9aac6071-38d0-4545-9d2f-15b936af6d7f)
  • Timestamp: The current time in Unix milliseconds (e.g., 1754701373)
  • Request ID: A unique GUID for this request (e.g., ce244054-b372-42c2-9102-f0d976db69f6)
  • Request path: The API endpoint path: api/v1/sessions
  • Request body: The complete JSON request body as a minified string

Example request body to minify:

{
  "merchant": "MERCHANT-1",
  "site": "SITE-1",
  "sessionTimeout": 120,
  "merchantTransactionId": "0ce72cfd-014d-4256-a006-a56601b2ffc4",
  "transactionMethod": {
    "intent": {
      "card": "Authorisation",
      "paypal": "Purchase"
    }
  },
  "amounts": {
    "currencyCode": "USD",
    "transactionValue": 25.00
  },
  "allowTransaction": true,
  "serviceType": "CheckoutDropIn"
}

When creating the HMAC signature, the request body must be minified (no whitespace or formatting). The formatted JSON above is for readability only.

Session request parameters

The session request accepts the following parameters:

ParameterDescription
merchant
string (≤ 20 characters)
required
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.
site
string (≤ 20 characters)
required
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.
merchantTransactionId
string (≤ 50 characters)
required
A unique identifier of your choice that represents this transaction.
sessionTimeout
number
required
The duration of the session, in minutes.
transactionMethod
object
required
Details about the transaction method, including the intent for each payment type.
transactionMethod.intent
object
required
The transaction intent for each payment method.
transactionMethod.intent.card
string
The intent for card, Apple Pay, or Google Pay transactions.

Possible values:
  • Authorisation
  • Purchase
  • Verification
  • EstimatedAuthorisation
transactionMethod.intent.paypal
string
The intent for PayPal transactions.

Possible values:
  • Authorisation
  • Purchase
amounts
object
required
Details about the transaction amount.
amounts.currencyCode
string (3 characters)
required
The currency code associated with the transaction, in ISO 4217 format. See Supported payment currencies.
amounts.transactionValue
number
required
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, USD 1 = USD 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).
allowTransaction
boolean
Whether or not to proceed with the transaction.

Prepare and send your request

The HMAC signature requires combining your token ID, timestamp, request ID, request path, and request body. Here's the format:

String to hash: {tokenId}:{timestamp}:{requestId}:{requestPath}:{requestBody}

For example:

9aac6071-38d0-4545-9d2f-15b936af6d7f:1754701373:ce244054-b372-42c2-9102-f0d976db69f6:api/v1/sessions:{"merchant":"MERCHANT-1","site":"SITE-1","sessionTimeout":120,"merchantTransactionId":"0ce72cfd-014d-4256-a006-a56601b2ffc4","transactionMethod":{"intent":{"card":"Authorisation","paypal":"Purchase"}},"amounts":{"currencyCode":"USD","transactionValue":25.00},"allowTransaction":true,"serviceType":"CheckoutDropIn"}

Use your token value (not token ID) as the secret key to create an HMAC SHA256 hash of this string. The result will be a base64-encoded signature like:

1DE2DFC390D7CD746A972140F26846AFA81CF85F5A0BAABA95DBC95301795EA6

You can now put together your Authorization header. It follows this format: {tokenId}:{timestamp}:{requestId}:{signature}. For example:

9aac6071-38d0-4545-9d2f-15b936af6d7f:1754701373:ce244054-b372-42c2-9102-f0d976db69f6: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 -i -X POST \
  'https://api-services.pxp.io/api/v1/sessions' \
  -H 'Authorization: 9aac6071-38d0-4545-9d2f-15b936af6d7f:1754701373:ce244054-b372-42c2-9102-f0d976db69f6:1DE2DFC390D7CD746A972140F26846AFA81CF85F5A0BAABA95DBC95301795EA6' \
  -H 'X-Request-Id: ce244054-b372-42c2-9102-f0d976db69f6' \
  -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",
  "transactionMethod": {
    "intent": {
      "card": "Authorisation",
      "paypal": "Purchase"
    }
  },
  "amounts": {
    "currencyCode": "USD",
    "transactionValue": 25.00
  },
  "allowTransaction": true,
  "serviceType": "CheckoutDropIn"
}'

Session response

If your request is successful, you'll receive a 200 response containing the session data:

{
  "sessionId": "c5f0799b-0839-43ce-abc5-5b462a98f250",
  "hmacKey": "904bc42395d4af634e2fd48ee8c2c7f52955a1da97a3aa3d82957ff12980a7bb",
  "encryptionKey": "20d175a669ad3f8c195c9c283fc86155",
  "sessionExpiry": "2025-05-19T13:39:20.3843454Z",
  "allowedFundingTypes": {
    "cardSchemes": [
      "Visa",
      "Diners",
      "Mastercard",
      "AmericanExpress"
    ],
    "cards": [],
    "wallets": {
      "paypal": {
        "allowedFundingOptions": [
          "venmo", 
          "paylater", 
          "paypal"
        ],
        "merchantId": "paypal-merchant-123"
      },
      "googlePay": {
        "merchantId": "BCR2DN4TWWPKJ45P",
        "merchantName": "Your Store Name",
        "gatewayMerchantId": "gateway-merchant-id"
      },
      "applePay": {
        "merchantId": "merchant.com.yourstore"
      }
    }
  }
}

Checkout Drop-in automatically detects available payment methods from the allowedFundingTypes in your session data. You don't need to manually configure which payment methods to show!

Step 4: Initialise Drop-in on your frontend

Import CheckoutDropIn and IntentType from the SDK and initialise with your configuration.

import CheckoutDropIn from '@pxpio/web-components-sdk/src/checkoutDropIn/CheckoutDropIn';
import IntentType from '@pxpio/web-components-sdk/src/basePxpCheckout/types/IntentType';
import PaymentMethod from '@pxpio/web-components-sdk/src/components/checkoutDropInComponents/types/PaymentMethod';
import { BaseSubmitResult } from '@pxpio/web-components-sdk/src/checkoutDropIn/types/BaseSubmitResult';
import BaseSdkException from '@pxpio/web-components-sdk/src/types/sdkExceptions/BaseSdkException';

// Get session data from your backend
const sessionData = await fetch('/api/create-session', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' }
}).then(response => response.json());

// Initialise Checkout Drop-in
const checkoutDropIn = CheckoutDropIn.initialize({
  environment: 'test',
  session: sessionData,
  ownerId: 'MERCHANT-1',
  ownerType: 'MerchantGroup',
  transactionData: {
    currency: 'USD',
    amount: 25.00,
    entryType: 'Ecom',
    intent: {
      card: IntentType.Authorisation,
      paypal: IntentType.Authorisation
    },
    merchantTransactionId: crypto.randomUUID(),
    merchantTransactionDate: () => new Date().toISOString()
  },
  onGetShopper: () => Promise.resolve({ id: 'shopper-123' }),
  onBeforeSubmit: async (paymentMethod: PaymentMethod) => {
    console.log('Payment method selected:', paymentMethod);
    return true; // Return true to proceed, false to cancel
  },
  onSubmit: (paymentMethod: PaymentMethod) => {
    console.log('Payment being processed:', paymentMethod);
  },
  onSuccess: async (result: BaseSubmitResult) => {
    // CRITICAL: Verify on backend before fulfilling order
    await verifyPaymentOnBackend(result);
  },
  onError: (error: BaseSdkException) => {
    console.error('Payment failed:', error);
    alert(`Payment failed: ${error.message}`);
  }
});

Configuration parameters

The initialize() method accepts the following configuration parameters:

ParameterDescription
environment
string
required
The environment type.

Possible values:
  • test: For development and testing.
  • production: For live transactions.
session
SessionData
required
Details about the checkout session returned from the Unity Sessions API. Includes sessionId, hmacKey, and allowedFundingTypes.
ownerId
string (≤ 20 characters)
required
Your unique merchant or merchant group identifier, as assigned by PXP. You can find it in the Unity Portal.
ownerType
string
required
Always set to 'MerchantGroup' for Drop-in.
transactionData
object
required
Details about the transaction.
transactionData.currency
string (3 characters)
required
The currency code associated with the transaction, in ISO 4217 format. See Supported payment currencies.
transactionData.amount
number
required
The transaction amount. Must match the currency's expected decimal places.
transactionData.entryType
string
required
The entry type.

Possible values:
  • Ecom: E-commerce transactions.
  • Moto: Mail order/telephone order transactions.
transactionData.intent
object
required
The transaction intents for each payment method. See supported transaction intents for details.
transactionData.intent.card
string
The intent for card, Google Pay, or Apple Pay transactions.

Possible values:
  • Authorisation
  • EstimatedAuthorisation
  • Purchase
  • Verification
transactionData.intent.paypal
string
The intent for PayPal transactions.

Possible values:
  • Authorisation
  • Purchase
transactionData.merchantTransactionId
string (≤ 50 characters)
required
A unique identifier for this transaction. Use a UUID or order ID.
transactionData.merchantTransactionDate
function
required
A function that returns the current date and time in ISO 8601 format.
onGetShopper
function
required
Function to retrieve shopper information. Required for Card-on-File functionality. Returns a Promise with shopper object containing id.
onBeforeSubmit
function
Callback fired when a payment method is selected and the user is about to submit payment. Receives payment method. Return true to proceed or false to cancel.
onSubmit
function
Callback fired when payment processing begins. Use this to show loading indicators.
onSuccess
function
required
Callback fired when payment succeeds.
onError
function
required
Callback fired when payment fails. Receives error details including code and message.

Step 5: Mount Drop-in to your page

Add a container element to your page where Drop-in will be rendered:

<div id="checkout-drop-in-container"></div>

Then call the create() method to render Drop-in:

checkoutDropIn.create('checkout-drop-in-container');

At this point, Drop-in will:

  • Detect available payment methods from your session.
  • Render a vertical accordion with all available payment methods.
  • Apply branding from the Unity Portal.
  • Show a "Secured by PXP" branded footer.

Step 6: Handle callbacks

Drop-in requires three callbacks to handle payment lifecycle events.

onGetShopper callback

Returns shopper information for Card-on-File functionality.

onGetShopper: () => Promise.resolve({ id: 'shopper-123' })

onSuccess callback

Fires when payment succeeds. CRITICAL: Always verify on your backend before fulfilling orders.

onSuccess: async (result: BaseSubmitResult) => {
  // Verify payment on backend
  const verified = await fetch('/api/verify-payment', {
    method: 'POST',
    body: JSON.stringify({
      systemTransactionId: result.systemTransactionId,
      merchantTransactionId: result.merchantTransactionId
    })
  }).then(r => r.json());
  
  if (verified.success) {
    window.location.href = `/success?orderId=${verified.orderId}`;
  }
}

onError callback

Fires when payment fails. Display appropriate error messages.

onError: (error: BaseSdkException) => {
  console.error('Payment failed:', error.code, error.message);
  alert(error.message || 'Payment failed. Please try again.');
}

Never trust frontend callbacks for order fulfillment. Always verify payments on your backend using webhooks or the Query Transaction API before fulfilling orders.

Want to add validation before payment or show loading states? See the Events guide for optional callbacks like onBeforeSubmit and onSubmit.

Step 7: Backend verification (CRITICAL)

Frontend callbacks can be manipulated by malicious users. You must verify all payments on your backend before fulfilling orders.

Primary method: Webhooks

Unity sends real-time webhook notifications to your backend when transactions complete:

// Your webhook endpoint (configured in the Unity Portal)
app.post('/webhooks/pxp', async (req, res) => {
  const events = req.body;
  
  // Verify webhook authenticity using HMAC
  if (!verifyWebhookSignature(req)) {
    return res.status(401).json({ error: 'Unauthorised' });
  }
  
  for (const event of events) {
    if (event.eventCategory === 'Transaction') {
      const txn = event.eventData;
      
      // Verify transaction details
      if (txn.state === 'Authorised' || txn.state === 'Captured') {
        // Check idempotency (prevent duplicate processing)
        if (!await hasProcessedTransaction(txn.systemTransactionId)) {
          // Verify amount and merchant transaction ID
          const transactionAmount = txn.amounts?.transactionValue || txn.amount;
          if (transactionAmount === expectedAmount &&
              txn.merchantTransactionId === expectedMerchantTxnId) {
            // Payment verified - fulfill order
            await fulfillOrder(txn.merchantTransactionId);
            await markTransactionProcessed(txn.systemTransactionId);
          }
        }
      }
    }
  }
  
  // Always return success response
  res.json({ state: 'Success' });
});

Optional fallback: Query the Transactions API

If you need instant feedback before the webhook arrives, you can query PXP's Transactions API:

// Your verification endpoint
app.post('/api/verify-payment', async (req, res) => {
  const { systemTransactionId, amount, merchantTransactionId } = req.body;
  
  // Check if webhook already processed this
  const existingTxn = await getTransactionFromDB(systemTransactionId);
  if (existingTxn) {
    return res.json({ success: true, orderId: existingTxn.orderId });
  }
  
  // Query PXP API as fallback (requires HMAC authentication)
  const response = await fetch(
    `https://api-services.pxp.io/api/v1/transactions?systemTransactionId=${systemTransactionId}&fundingType=Card`,
    {
      headers: {
        'X-Client-Id': process.env.PXP_CLIENT_ID,
        'X-Request-Id': crypto.randomUUID(),
        'Authorization': createAuthHeader('/api/v1/transactions', '', process.env.PXP_TOKEN_ID, process.env.PXP_TOKEN_VALUE),
        'Content-Type': 'application/json'
      }
    }
  );
  
  const transaction = await response.json();
  
  // Verify transaction details
  const transactionAmount = transaction.amounts?.transactionValue || transaction.amount;
  if (transaction.state === 'Authorised' &&
      Math.abs(transactionAmount - amount) < 0.01 &&
      transaction.merchantTransactionId === merchantTransactionId) {
    // Payment verified - fulfill order
    const order = await fulfillOrder(merchantTransactionId);
    return res.json({ success: true, orderId: order.id });
  }
  
  res.json({ success: false, error: 'Payment verification failed' });
});

Error handling

All errors return a consistent structure with ErrorCode and message properties. Common scenarios include card declined, insufficient funds, expired card, CVV failure, and 3DS authentication failure. Display the error message to users and allow them to retry or use a different payment method.

Complete example

Here's a complete example showing Drop-in integration in a React component:

import { useEffect, useState } from 'react';
import CheckoutDropIn from '@pxpio/web-components-sdk/src/checkoutDropIn/CheckoutDropIn';
import IntentType from '@pxpio/web-components-sdk/src/basePxpCheckout/types/IntentType';
import PaymentMethod from '@pxpio/web-components-sdk/src/components/checkoutDropInComponents/types/PaymentMethod';
import { BaseSubmitResult } from '@pxpio/web-components-sdk/src/checkoutDropIn/types/BaseSubmitResult';
import BaseSdkException from '@pxpio/web-components-sdk/src/types/sdkExceptions/BaseSdkException';

export default function CheckoutPage() {
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    initializeCheckout();
  }, []);

  async function initializeCheckout() {
    try {
      // 1. Get session from backend
      const sessionData = await fetch('/api/create-session', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          merchant: "MERCHANT-1",
          site: "SITE-1",
          sessionTimeout: 120,
          merchantTransactionId: crypto.randomUUID(),
          transactionMethod: {
            intent: {
              card: "Authorisation",
              paypal: "Authorisation"
            }
          },
          amounts: {
            currencyCode: "USD",
            transactionValue: 25.00
          },
          allowTransaction: true,
          serviceType: "CheckoutDropIn"
        })
      }).then(r => r.json());

      // 2. Initialise Drop-in
      const checkoutDropIn = CheckoutDropIn.initialize({
        environment: 'production',
        session: sessionData,
        ownerId: 'MERCHANT-1',
        ownerType: 'MerchantGroup',
        transactionData: {
          currency: 'USD',
          amount: 25.00,
          entryType: 'Ecom',
          intent: {
            card: IntentType.Authorisation,
            paypal: IntentType.Authorisation
          },
          merchantTransactionId: crypto.randomUUID(),
          merchantTransactionDate: () => new Date().toISOString()
        },
        onGetShopper: () => Promise.resolve({ id: 'shopper-123' }),
        onBeforeSubmit: async (paymentMethod: PaymentMethod) => {
          console.log('Payment method selected:', paymentMethod);
          setError(null);
          return true;
        },
        onSubmit: (paymentMethod: PaymentMethod) => {
          console.log('Processing payment...');
          setIsLoading(true);
        },
        onSuccess: async (result: BaseSubmitResult) => {
          console.log('Payment successful:', result.systemTransactionId);
          
          // Verify on backend
          try {
            const verified = await fetch('/api/verify-payment', {
              method: 'POST',
              headers: { 'Content-Type': 'application/json' },
              body: JSON.stringify({
                systemTransactionId: result.systemTransactionId,
                merchantTransactionId: result.merchantTransactionId
              })
            }).then(r => r.json());
            
            if (verified.success) {
              window.location.href = `/success?orderId=${verified.orderId}`;
            } else {
              setError('Payment verification failed. Please contact support.');
              setIsLoading(false);
            }
          } catch (err) {
            setError('Failed to verify payment. Please contact support.');
            setIsLoading(false);
          }
        },
        onError: (error: BaseSdkException) => {
          console.error('Payment failed:', error);
          
          // Handle errors based on message content
          let userMessage = error.message || 'Payment failed. Please try again.';
          
          if (error.message.includes('declined')) {
            userMessage = 'Your card was declined. Please try a different card.';
          } else if (error.message.includes('insufficient')) {
            userMessage = 'Insufficient funds. Please use a different payment method.';
          } else if (error.message.includes('expired')) {
            userMessage = 'This card has expired. Please use a different card.';
          } else if (error.message.includes('CVV') || error.message.includes('security')) {
            userMessage = 'Invalid security code. Please check your CVV.';
          } else if (error.code === 'SDK1114' || error.message.includes('authentication')) {
            userMessage = '3D Secure authentication failed. Please try again.';
          }
          
          setError(userMessage);
          setIsLoading(false);
        }
      });

      // 3. Mount Drop-in
      checkoutDropIn.create('checkout-drop-in-container');
      
    } catch (err) {
      console.error('Failed to initialise checkout:', err);
      setError('Failed to load payment form. Please refresh the page.');
    }
  }

  return (
    <div className="checkout-page">
      <h1>Complete Your Purchase</h1>
      
      <div className="order-summary">
        <h2>Order Summary</h2>
        <p>Product: Premium Subscription</p>
        <p>Amount: $25.00 USD</p>
      </div>
      
      {error && (
        <div className="error-message" role="alert">
          {error}
        </div>
      )}
      
      {isLoading && (
        <div className="loading-overlay">
          Processing payment...
        </div>
      )}
      
      <div id="checkout-drop-in-container"></div>
      
      <p className="security-notice">
        Your payment information is securely processed by PXP. 
        We never store your full card details.
      </p>
    </div>
  );
}

What's next?

Now that you've integrated Checkout Drop-in, here are some recommended next steps:

  • Configuration: Customise branding, colours, and styling in the Unity Portal.
  • Events: Learn about all available callbacks and event handling.
  • Testing: Use test cards and sandbox environment to test your integration.
  • Configure webhooks: Set up server-side webhook handling for reliable payment verification.