Complete guide to integrating Checkout Drop-in into your application.
Checkout Drop-in provides a complete, pre-built payment interface that automatically handles multiple payment methods. It follows a simple three-step lifecycle:
- Initialise: Configure Drop-in with your session and transaction data.
- Mount: Render the payment interface to your page.
- 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.
Make sure you've activated the Checkout Drop-in service in the Unity Portal.
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-sdkCheckout Drop-in is part of the main SDK package. Import it directly from @pxpio/web-components-sdk/src/checkoutDropIn/CheckoutDropIn.
In order to initialise Checkout Drop-in, 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.
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.
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 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.
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, or Google Pay transactions. Possible values:
|
transactionMethod.intent.paypalstring | The intent for PayPal 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 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:
1DE2DFC390D7CD746A972140F26846AFA81CF85F5A0BAABA95DBC95301795EA6You 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: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.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"
}'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!
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}`);
}
});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 Drop-in. |
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, or Apple Pay transactions. Possible values:
|
transactionData.intent.paypalstring | The intent for PayPal 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-on-File functionality. Returns a Promise with shopper object containing id. |
onBeforeSubmitfunction | 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. |
onSubmitfunction | Callback fired when payment processing begins. Use this to show loading indicators. |
onSuccessfunction required | Callback fired when payment succeeds. |
onErrorfunction required | Callback fired when payment fails. Receives error details including code and message. |
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.
Drop-in requires three callbacks to handle payment lifecycle events.
Returns shopper information for Card-on-File functionality.
onGetShopper: () => Promise.resolve({ id: 'shopper-123' })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}`;
}
}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.
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.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 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>
);
}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.