Complete guide to integrating card components into your application.
Card components provide flexible, secure payment forms for collecting card details. Every component follows a simple three-step lifecycle:
- Initialise: Configure the SDK with your session and transaction data.
- Create & mount: Build the component with your configuration and render it to your page.
- 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.
Components for Web is already available to all Unity customers. No additional activation required.
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-sdkCard components are part of the main SDK package. Import PxpCheckout directly from @pxpio/web-components-sdk.
In order to initialise Components for Web, you'll need to send authenticated requests to the PXP API.
To get your credentials:
- In the Unity Portal, go to Merchant setup > Merchant groups.
- Select a merchant group.
- Click the Inbound calls tab.
- Copy the Client ID in the top-right corner.
- Click New token.
- Choose a number of days before token expiry. For example,
30. - Click Save to confirm. Your token is now created.
- 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.
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.
Our platform uses HMAC (Hash-based Message Authentication Code) with SHA256 for authentication to ensure secure communication and data integrity. This method involves creating a signature by hashing your request data with a secret key, which must then be included in the HTTP headers of your API request.
To create the HMAC signature, you need to prepare a string that includes four parts 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.
The session request accepts the following parameters:
| Parameter | Description |
|---|---|
merchantstring (≤ 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. |
sitestring (≤ 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. |
merchantTransactionIdstring (≤ 50 characters) required | A unique identifier of your choice that represents this transaction. |
sessionTimeoutnumber required | The duration of the session, in minutes. |
transactionMethodobject required | Details about the transaction method, including the intent for each payment type. |
transactionMethod.intentobject required | The transaction intent for each payment method. |
transactionMethod.intent.cardstring | The intent for card, Apple Pay, Google Pay, or Paze transactions. Possible values:
|
amountsobject required | Details about the transaction amount. |
amounts.currencyCodestring (3 characters) required | The currency code associated with the transaction, in ISO 4217 format. See Supported payment currencies. |
amounts.transactionValuenumber 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). |
allowTransactionboolean | Whether or not to proceed with the transaction. |
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:
1DE2DFC390D7CD746A972140F26846AFA81CF85F5A0BAABA95DBC95301795EA6You 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:1DE2DFC390D7CD746A972140F26846AFA81CF85F5A0BAABA95DBC95301795EA6Lastly, 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
}'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": []
}
}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'
})
});The initialize() method accepts the following configuration parameters:
| Parameter | Description |
|---|---|
environmentstring required | The environment type. Possible values:
|
sessionSessionData required | Details about the checkout session returned from the Unity Sessions API. Includes sessionId, hmacKey, and allowedFundingTypes. |
ownerIdstring (≤ 20 characters) required | Your unique merchant or merchant group identifier, as assigned by PXP. You can find it in the Unity Portal. |
ownerTypestring required | Always set to 'MerchantGroup' for Components for Web. |
transactionDataobject required | Details about the transaction. |
transactionData.currencystring (3 characters) required | The currency code associated with the transaction, in ISO 4217 format. See Supported payment currencies. |
transactionData.amountnumber required | The transaction amount. Must match the currency's expected decimal places. |
transactionData.entryTypestring required | The entry type. Possible values:
|
transactionData.intentobject required | The transaction intents for each payment method. See supported transaction intents for details. |
transactionData.intent.cardstring | The intent for card, Google Pay, Apple Pay, or Paze transactions. Possible values:
|
transactionData.merchantTransactionIdstring (≤ 50 characters) required | A unique identifier for this transaction. Use a UUID or order ID. |
transactionData.merchantTransactionDatefunction required | A function that returns the current date and time in ISO 8601 format. |
onGetShopperfunction 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. |
onGetShippingAddressfunction | Function to retrieve shipping address information. Returns a Promise with address object. |
kountDisabledboolean | Whether to disable the Kount fraud detection service. Defaults to false (fraud detection enabled). |
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.
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.
Card components use callbacks to notify you of important events during the payment lifecycle.
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'
})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}`;
}
}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.');
}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
}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.
Frontend callbacks can be manipulated by malicious users. You must verify all payments on your backend before fulfilling orders.
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' });
});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' });
});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.
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>
);
}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.