Skip to content

Implementation

Complete guide to integrating card components into your application.

Overview

Card components provide flexible, secure payment forms for collecting card details. Every component follows a simple three-step lifecycle:

  1. Initialise: Configure the SDK with your session and transaction data.
  2. Create & mount: Build the component with your configuration and render it to your page.
  3. Handle callbacks: Respond to payment success, errors, and other events.

Components automatically handle card tokenisation, validation, 3D Secure authentication, and transaction processing.

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

Before you start

Components for Web is already available to all Unity customers. No additional activation required.

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

Card components are part of the main SDK package. Import PxpCheckout directly from @pxpio/web-components-sdk.

Step 2: Get your API credentials

In order to initialise Components for Web, 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

Components for Web 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 four parts concatenated together:

  • 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"
    }
  },
  "amounts": {
    "currencyCode": "USD",
    "transactionValue": 25.00
  },
  "allowTransaction": true
}

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, Google Pay, or Paze transactions.

Possible values:
  • Authorisation
  • Purchase
  • Verification
  • EstimatedAuthorisation
  • Payout
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 timestamp, request ID, request path, and request body. Here's the format:

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

For example:

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

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

1DE2DFC390D7CD746A972140F26846AFA81CF85F5A0BAABA95DBC95301795EA6

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

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

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

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

curl -i -X POST \
  'https://api-services.dev.pxp.io/api/v1/sessions' \
  -H 'Authorization: PXP-UST1 9aac6071-38d0-4545-9d2f-15b936af6d7f:1754701373: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"
    }
  },
  "amounts": {
    "currencyCode": "USD",
    "transactionValue": 25.00
  },
  "allowTransaction": true
}'

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": []
  }
}

Step 4: Initialise the SDK on your frontend

Import PxpCheckout from the SDK and initialise with your configuration.

import { PxpCheckout, IntentType } from '@pxpio/web-components-sdk';

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

// Initialise the SDK
const pxpSdk = PxpCheckout.initialize({
  environment: 'test',
  session: sessionData,
  ownerId: 'MERCHANT-1',
  ownerType: 'MerchantGroup',
  transactionData: {
    currency: 'USD',
    amount: 25,
    entryType: 'Ecom',
    intent: {
      card: IntentType.Authorisation
    },
    merchantTransactionId: crypto.randomUUID(),
    merchantTransactionDate: () => new Date().toISOString()
  },
  kountDisabled: false, // OPTIONAL: Set to true to disable Kount fraud detection
  onGetShopper: () => Promise.resolve({ 
    id: 'shopper-123',
    email: 'customer@example.com',
    firstName: 'John',
    lastName: 'Doe'
  })
});

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 Components for Web.
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, Apple Pay, or Paze transactions.

Possible values:
  • Authorisation
  • EstimatedAuthorisation
  • Purchase
  • Verification
  • Payout
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 tokenisation and Card-on-File functionality. Returns a Promise with shopper object containing id, email, firstName, and lastName.
onGetShippingAddress
function
Function to retrieve shipping address information. Returns a Promise with address object.
kountDisabled
boolean
Whether to disable the Kount fraud detection service. Defaults to false (fraud detection enabled).

Step 5: Create and configure a component

Use the SDK's create() method to build a component with your desired configuration:

const newCard = pxpSdk.create('new-card', {
  fields: {
    cardNumber: {
      required: true,
      placeholder: '1234 5678 9012 3456'
    },
    expiryDate: {
      required: true,
      placeholder: 'MM/YY'
    },
    cvc: {
      required: true,
      placeholder: '123'
    },
    holderName: {
      required: true,
      placeholder: 'John Doe'
    }
  },
  submit: {
    submitText: 'Pay $25.00',
    styles: {
      backgroundColor: '#4CAF50',
      color: 'white',
      padding: '15px',
      borderRadius: '6px',
      fontSize: '16px',
      fontWeight: 'bold',
      width: '100%'
    },
    onPostAuthorisation: async (data) => {
      // CRITICAL: Verify on backend before fulfilling order
      await verifyPaymentOnBackend(data);
    }
  }
});

The new-card component shown above is just one of many available components. See Pre-built components and Standalone components for a full list of available options.

Step 6: Mount the component to your page

Add a container element to your page where the component will be rendered:

<div id="new-card-container"></div>

Then call the mount() method to render the component:

newCard.mount('new-card-container');

At this point, the component will:

  • Render all configured input fields.
  • Apply validation rules automatically.
  • Handle card brand detection.
  • Display the submit button.
  • Process payments when submitted.

Step 7: Handle callbacks

Card components use callbacks to notify you of important events during the payment lifecycle.

onGetShopper callback

Returns shopper information for card tokenisation and Card-on-File functionality.

onGetShopper: () => Promise.resolve({ 
  id: 'shopper-123',
  email: 'customer@example.com',
  firstName: 'John',
  lastName: 'Doe',
  phoneNumber: '+1-555-0123'
})

onPostAuthorisation callback

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

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

onSubmitError callback

Handles payment failures and displays error messages to the user.

onSubmitError: (error) => {
  console.error('Payment failed:', error);
  alert('Payment failed. Please check your card details and try again.');
}

3D Secure callbacks (required if using 3DS)

If you want to use 3DS authentication, implement these callbacks. If you want to skip 3DS, omit these callbacks.

Your backend must set authentication = true via the Modify session API if you include these callbacks, or false if you omit them. Mismatched decisions will cause error CAD4005: Authentication decision is required but not provided.

onPreInitiateAuthentication: () => {
  return {
    providerId: 'pxpfinancial',
    timeout: 12
  };
},
onPostInitiateAuthentication: (data) => {
  console.log('3DS authentication initiated:', data);
},
onPreAuthentication: async () => {
  return {
    merchantCountryNumericCode: '100',
    merchantLegalName: 'YourMerchantName',
    challengeWindowSize: 1,
    requestorChallengeIndicator: '02'
  };
},
onPostAuthentication: (data) => {
  console.log('3DS authentication completed:', data);
  // Optional: Retrieve authentication details from backend
  // and update session decision if needed
}

onPreAuthorisation callback

Provides additional transaction data before authorisation. Most merchants return an empty object to proceed.

onPreAuthorisation: (data) => {
  return {}; // Return empty object to proceed
  // Or optionally include AVS/risk screening data
}

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 handle field-level events? See the Events guide for all available callbacks.

Step 8: 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.dev.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 the new-card component integration in a React component:

import { useEffect, useState } from 'react';
import { PxpCheckout, IntentType } from '@pxpio/web-components-sdk';

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/sessions', {
        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"
            }
          },
          amounts: {
            currencyCode: "USD",
            transactionValue: 25
          },
          allowTransaction: true
        })
      }).then(r => r.json());

      // 2. Initialise SDK
      const pxpSdk = PxpCheckout.initialize({
        environment: 'production',
        session: sessionData,
        ownerId: 'MERCHANT-1',
        ownerType: 'MerchantGroup',
        transactionData: {
          currency: 'USD',
          amount: 25,
          entryType: 'Ecom',
          intent: {
            card: IntentType.Authorisation
          },
          merchantTransactionId: crypto.randomUUID(),
          merchantTransactionDate: () => new Date().toISOString()
        },
        kountDisabled: false, // OPTIONAL: Set to true to disable Kount fraud detection
        onGetShopper: () => Promise.resolve({ 
          id: 'shopper-123',
          email: 'customer@example.com',
          firstName: 'John',
          lastName: 'Doe'
        })
      });

      // 3. Create component
      const newCard = pxpSdk.create('new-card', {
        fields: {
          cardNumber: {
            required: true,
            placeholder: '1234 5678 9012 3456',
            onCardBrandDetected: (event) => {
              console.log('Card brand detected:', event.detail.cardBrand.brand);
            }
          },
          expiryDate: {
            required: true,
            placeholder: 'MM/YY'
          },
          cvc: {
            required: true,
            placeholder: '123'
          },
          holderName: {
            required: true,
            placeholder: 'John Doe'
          }
        },
        submit: {
          submitText: 'Pay $25.00',
          styles: {
            backgroundColor: '#4CAF50',
            border: 'none',
            padding: '15px',
            borderRadius: '6px',
            color: 'white',
            fontSize: '16px',
            fontWeight: 'bold',
            cursor: 'pointer',
            width: '100%'
          },
          onPostAuthorisation: async (data) => {
            console.log('Payment successful:', data.systemTransactionId);
            
            // Verify on backend
            try {
              const verified = await fetch('/api/verify-payment', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                  systemTransactionId: data.systemTransactionId,
                  merchantTransactionId: data.merchantTransactionId
                })
              }).then(r => r.json());
              
              if (verified.success) {
                globalThis.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);
            }
          },
          onSubmitError: (error) => {
            console.error('Payment failed:', error);
            setError('Payment failed. Please check your card details and try again.');
            setIsLoading(false);
          },
          // 3DS callbacks (only include if backend set authentication = true)
          onPreInitiateAuthentication: () => {
            return {
              providerId: 'pxpfinancial',
              timeout: 12
            };
          },
          onPostInitiateAuthentication: (data) => {
            console.log('3DS authentication initiated:', data);
          },
          onPreAuthentication: async () => {
            return {
              merchantCountryNumericCode: '100',
              merchantLegalName: 'TestMerchant_10',
              challengeWindowSize: 1,
              requestorChallengeIndicator: '02'
            };
          },
          onPostAuthentication: (data) => {
            console.log('3DS authentication completed:', data);
          },
          onPreAuthorisation: (data) => {
            setIsLoading(true);
            setError(null);
            return {};
          }
        }
      });

      // 4. Mount component
      newCard.mount('new-card-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="new-card-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 card components, here are some recommended next steps:

  • Customisation: Learn how to customise the appearance and behavior of components.
  • Events: Explore all available callbacks and event handling.
  • 3D Secure: Enable 3DS for enhanced security and liability shift.
  • Configure webhooks: Set up server-side webhook handling for reliable payment verification.