Enable secure, biometric-authenticated checkout on Safari and iOS devices.
Apple Pay is automatically included in your drop-in when enabled in your session and when the customer's device supports it. The drop-in handles all Apple Pay setup, button rendering, and payment processing automatically.
- Automatic detection: Only shows on Safari/iOS with Apple Pay configured
- Native experience: Uses device's native Apple Pay sheet with Face ID/Touch ID
- Zero configuration: Drop-in handles merchant IDs and tokenisation
- Unified callbacks: Same
onSuccess/onErroras other payment methods - Fastest checkout: Customers tap, authenticate, and complete their payment (typically under 3 seconds)
- The customer clicks "Apple Pay" in the accordion. A native payment sheet appears (iOS/macOS).
- The customer authenticates with Face ID, Touch ID, or passcode.
- The customer approves the payment. The payment sheet closes automatically.
- The payment is processed through Unity.
- Your
onSuccesscallback fires.
Configure Apple Pay-specific settings and callbacks for dynamic pricing updates.
The following properties are available for Apple Pay configuration:
| Property | Description |
|---|---|
shippingContactConfigurationApplePayShippingContactConfiguration | Configuration for collecting the shipping address. Should be used with billingContactConfiguration |
billingContactConfigurationApplePayBillingContactConfiguration | Configuration for collecting the billing address. Should be used with shippingContactConfiguration |
onShippingContactSelected(contact) => Promise<ApplePayShippingContactUpdate> | Called when a shipping contact is selected by the customer or when Apple Pay automatically applies the user's default shipping address |
onShippingMethodSelected(method) => Promise<ApplePayShippingMethodUpdate> | Called when a shipping method is selected by the customer |
onPaymentMethodSelected(paymentMethod) => Promise<ApplePayPaymentMethodUpdate> | Called when a payment method is selected by the customer or when Apple Pay automatically selects a default payment method (for example, the top card in the user's wallet) |
onCouponCodeChanged(couponCode) => Promise<ApplePayCouponCodeUpdate> | Called when coupon code is entered |
Callbacks may be invoked even when the user doesn't explicitly perform an action. This can occur when Apple Pay applies default selections.
Apple Pay allows you to collect contact information (shipping and billing addresses) and optionally pre-fill this information for the customer. For example, you can pre-fill a logged-in user's saved address from your database, or leave the fields empty to let Apple Pay use the customer's default address from their wallet.
The following properties are available for contact configuration:
| Property | Description |
|---|---|
requireShippingContactFieldsApplePayContactField[] | Array of required shipping fields that the customer must provide (e.g., ['email', 'phone', 'name', 'postalAddress']). Defaults to []. |
shippingContactApplePayPaymentContact | Pre-filled shipping contact information shown by default. The customer can modify these values. Defaults to undefined. |
requireBillingContactFieldsApplePayContactField[] | Array of required billing fields that the customer must provide (e.g., ['postalAddress', 'name']). Defaults to []. |
billingContactApplePayPaymentContact | Pre-filled billing contact information shown by default. The customer can modify these values. Defaults to undefined. |
email- Email addressphone- Phone numbername- Full namepostalAddress- Postal/shipping addressphoneticName- Phonetic name (for Japanese and Chinese)
When pre-filling contact information using shippingContact or billingContact, the following properties are available:
| Property | Description |
|---|---|
givenNamestring | First name (e.g., 'John') |
familyNamestring | Last name (e.g., 'Doe') |
emailAddressstring | Email address (e.g., 'john.doe@example.com') |
phoneNumberstring | Phone number with country code (e.g., '+1234567890') |
addressLinesstring[] | Street address lines (e.g., ['123 Main St', 'Apt 4B']) |
localitystring | City (e.g., 'New York') |
administrativeAreastring | State/Province code (e.g., 'NY', 'CA') |
postalCodestring | ZIP/Postal code (e.g., '10001') |
countrystring | Country name (e.g., 'United States') |
countryCodestring | ISO country code (e.g., 'US') |
phoneticGivenNamestring | Phonetic first name (for Japanese/Chinese names) |
phoneticFamilyNamestring | Phonetic last name (for Japanese/Chinese names) |
This example shows how to require specific fields and pre-fill contact information for a logged-in user:
applePay: {
// Shipping contact configuration
shippingContactConfiguration: {
// REQUIRED: Fields the customer must provide
requireShippingContactFields: ['email', 'phone', 'name', 'postalAddress'],
// OPTIONAL: Pre-fill shipping information (customer can modify)
shippingContact: {
givenName: 'John',
familyName: 'Doe',
emailAddress: 'john.doe@example.com',
phoneNumber: '+1234567890',
addressLines: ['123 Main St', 'Apt 4B'],
locality: 'New York',
administrativeArea: 'NY',
postalCode: '10001',
country: 'United States',
countryCode: 'US'
}
},
// Billing contact configuration
billingContactConfiguration: {
// REQUIRED: Fields the customer must provide for AVS verification
requireBillingContactFields: ['postalAddress', 'name'],
// OPTIONAL: Pre-fill billing information (customer can modify)
billingContact: {
givenName: 'John',
familyName: 'Doe',
addressLines: ['123 Main St'],
locality: 'New York',
administrativeArea: 'NY',
postalCode: '10001',
country: 'United States',
countryCode: 'US'
}
}
}- Required fields: When you specify
requireShippingContactFieldsorrequireBillingContactFields, Apple Pay enforces that the customer provides these fields before payment can be completed. - Pre-filled data: The
shippingContactandbillingContactobjects provide default values that appear in the payment sheet, saving the customer time. The customer can still modify these values. - Validation: Even if you pre-fill data, the customer can change it. Use the
onShippingContactSelectedcallback to validate the final address before completing payment.
The following table summarises how contact configuration properties work:
| Configuration | Purpose | Effect |
|---|---|---|
requireShippingContactFields: ['email', 'phone', 'name', 'postalAddress'] | Specify required fields | Customer must provide these fields to complete payment |
shippingContact: { givenName: 'John', ... } | Provide default values | These values appear pre-filled in the payment sheet (customer can modify) |
| Both together | Require fields + pre-fill | Customer sees pre-filled data but must ensure all required fields are complete |
Apple Pay inherits the following settings from methodConfig.global:
| Property | Description |
|---|---|
acceptedCardNetworksstring[] | Which card brands to accept through Apple Pay (falls back to session.allowedFundingTypes.cardSchemes) |
allowedCardFundingSourcestring[] | Which funding types to accept (Credit, Debit, Prepaid) |
transactionInfo.countryCodestring | Country code for the transaction (falls back to Unity Portal merchant country: siteConfig.merchantConfiguration.countryCode) |
transactionInfo.merchantDisplayNamestring | Merchant name shown in Apple Pay sheet (falls back to Unity Portal merchant name: siteConfig.merchantConfiguration.merchantName) |
transactionInfoobject | Other transaction display information (totalLabel, lineItems, etc.) |
shippingOptionsarray | Available shipping options |
onGetConsent(paymentMethod) => boolean | Controls whether to show consent checkbox for Apple Pay |
onCancel(paymentMethod, data) => void | Called when user cancels Apple Pay payment (dismisses payment sheet) |
This example demonstrates a full Apple Pay configuration with global settings, contact configuration, and all callback implementations:
methodConfig: {
global: {
// Apple Pay uses these settings
acceptedCardNetworks: ['Visa', 'Mastercard', 'American Express'],
allowedCardFundingSource: ['CREDIT', 'DEBIT'],
transactionInfo: {
countryCode: 'US',
merchantDisplayName: 'Demo Store',
totalLabel: 'Total Amount',
totalStatus: 'FINAL',
lineItems: [
{ label: 'Subtotal', amount: '95.00' },
{ label: 'Tax', amount: '5.00' }
]
},
shippingOptions: [
{ id: 'ground', label: 'Ground Shipping', amount: '5.00', description: 'Arrives in 5-7 days' },
{ id: 'express', label: 'Express Shipping', amount: '15.00', description: 'Arrives in 2-3 days' }
]
},
applePay: {
// Shipping contact configuration
shippingContactConfiguration: {
// Require specific shipping fields from the customer
requireShippingContactFields: ['email', 'phone', 'name', 'postalAddress']
},
// Billing contact configuration (optional)
billingContactConfiguration: {
// Require specific billing fields from the customer
requireBillingContactFields: ['postalAddress', 'name']
},
// Called when shipping contact is selected
onShippingContactSelected: async (contact) => {
console.log('Shipping contact selected:', contact);
// Update pricing based on shipping location
return {
newTotal: {
label: 'Total',
amount: '105.00',
type: 'final'
},
newLineItems: [
{ label: 'Subtotal', amount: '95.00', type: 'final' },
{ label: 'Shipping', amount: '10.00', type: 'final' }
]
};
},
// Called when shipping method is selected
onShippingMethodSelected: async (method) => {
console.log('Shipping method selected:', method);
// Update total based on shipping method
const shippingCost = method.identifier === 'express' ? 15.00 : 5.00;
const newTotal = 95.00 + shippingCost;
// Optional: Update session on backend for consistency
// await updateSessionOnBackend(sessionData.sessionId, {
// amounts: { transactionValue: newTotal, currencyCode: 'USD' }
// });
return {
newTotal: {
label: 'Total',
amount: newTotal.toFixed(2),
type: 'final'
},
newLineItems: [
{ label: 'Subtotal', amount: '95.00', type: 'final' },
{ label: 'Shipping', amount: shippingCost.toFixed(2), type: 'final' }
]
};
},
// Called when payment method (card type) is selected
onPaymentMethodSelected: async (paymentMethod) => {
console.log('Payment method selected:', paymentMethod);
// Add credit card processing fee for credit cards
const creditCardFee = paymentMethod.type === 'credit' ? 2.50 : 0;
return {
newTotal: {
label: 'Total',
amount: (100.00 + creditCardFee).toFixed(2),
type: 'final'
},
newLineItems: [
{ label: 'Subtotal', amount: '100.00', type: 'final' },
...(creditCardFee > 0 ? [{ label: 'Credit Card Fee', amount: creditCardFee.toFixed(2), type: 'final' }] : [])
]
};
},
// Called when coupon code is entered
onCouponCodeChanged: async (couponCode) => {
console.log('Coupon code entered:', couponCode);
// Validate coupon and apply discount
const coupons: Record<string, number> = {
'SAVE10': 10.00,
'SAVE20': 20.00,
'WELCOME5': 5.00
};
const discount = coupons[couponCode.toUpperCase()] || 0;
return {
newTotal: {
label: 'Total',
amount: (100.00 - discount).toFixed(2),
type: 'final'
},
newLineItems: [
{ label: 'Subtotal', amount: '100.00', type: 'final' },
...(discount > 0 ? [{ label: 'Discount', amount: (-discount).toFixed(2), type: 'final' }] : [])
]
};
}
}
}Apple Pay requires the following to function correctly:
- HTTPS: Your website must be served over HTTPS. Apple Pay doesn't work on HTTP connections.
- Domain verification: Your domain must be registered and verified in the Unity Portal.
- Merchant certificate: Apple Pay merchant certificate must be configured in the Unity Portal.
- Browser compatibility: Apple Pay is supported on Safari and Safari WebView. It works on:
- Safari on macOS
- Safari on iOS (iPhone 6 or later, iPad Air 2 or later)
- Safari WebView in native iOS apps
- Customer setup: The customer must have at least one card added to their Apple Wallet.
Apple Pay works through the standard implementation, with no Apple Pay-specific code needed for basic usage:
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 from backend (with Apple Pay enabled)
const sessionData = await fetch('/api/create-session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
}).then(response => response.json());
// Initialise Drop-in
const checkoutDropIn = CheckoutDropIn.initialize({
environment: 'test',
session: sessionData, // Must include applePay in allowedFundingTypes.wallets
ownerId: 'MERCHANT-1',
ownerType: 'MerchantGroup',
transactionData: {
currency: 'USD',
amount: 99.99,
entryType: 'Ecom',
intent: {
card: IntentType.Authorisation,
paypal: IntentType.Authorisation
},
merchantTransactionId: crypto.randomUUID(),
merchantTransactionDate: () => new Date().toISOString()
},
onGetShopper: () => Promise.resolve({ id: 'shopper-001' }),
onSuccess: async (result: BaseSubmitResult) => {
console.log('Apple Pay payment successful!');
console.log('System transaction ID:', result.systemTransactionId);
console.log('Payment method:', result.paymentMethod); // Will be "ApplePay"
// CRITICAL: Verify on backend
await verifyPaymentOnBackend(result);
},
onError: (error: BaseSdkException) => {
console.error('Apple Pay error:', error);
// Handle specific error scenarios
if (error.ErrorCode === 'SDK0615') {
// User cancelled Apple Pay - don't show error
console.log('User cancelled Apple Pay');
} else if (error.ErrorCode === 'SDK1119') {
// Apple Pay payment failed
alert('Apple Pay payment failed. Please try a different payment method.');
} else if (error.ErrorCode === 'SDK0601') {
// Apple Pay not available
alert('Apple Pay is not available on this device or browser.');
} else if (error.message.toLowerCase().includes('declined')) {
alert('Payment declined. Please try a different payment method.');
} else {
alert(`Payment failed: ${error.message}`);
}
}
});
// Mount Drop-in
checkoutDropIn.create('checkout-drop-in-container');Enable Apple Pay in your session request:
// BACKEND: Create session with Apple Pay enabled
const sessionRequest = {
merchant: "MERCHANT-1",
site: "SITE-1",
sessionTimeout: 120,
merchantTransactionId: crypto.randomUUID(),
transactionMethod: {
intent: {
card: "Authorisation"
}
},
amounts: {
currencyCode: "USD",
transactionValue: 99.99
},
allowedFundingTypes: {
card: true,
wallets: {
applePay: true // Enable Apple Pay
}
},
allowTransaction: true,
serviceType: "CheckoutDropIn"
};When an Apple Pay payment succeeds, your onSuccess callback receives the same standard result as other payment methods:
onSuccess: (result: BaseSubmitResult) => {
console.log('Payment Details:');
console.log('- System Transaction ID:', result.systemTransactionId);
console.log('- Merchant Transaction ID:', result.merchantTransactionId);
console.log('- Payment Method:', result.paymentMethod); // "ApplePay"
// Note: Amount, currency, and card details must be retrieved from backend
// Apple Pay tokenises cards - actual card details are not exposed
}Handle Apple Pay-specific errors:
import BaseSdkException from '@pxpio/web-components-sdk/src/types/sdkExceptions/BaseSdkException';
onError: (error: BaseSdkException) => {
// Use error.ErrorCode for SDK-defined errors
if (error.ErrorCode === 'SDK0615') {
// Apple Pay session was cancelled by the user
console.log('User cancelled Apple Pay');
// Don't show error - user intentionally closed payment sheet
} else if (error.ErrorCode === 'SDK1119') {
// Apple Pay payment failed
alert('Apple Pay payment failed. Please try a different payment method.');
} else if (error.ErrorCode === 'SDK0601') {
// Apple Pay not available
alert('Apple Pay is not available on this device or browser.');
} else if (error.message.toLowerCase().includes('declined')) {
alert('Payment declined. Please try a different payment method.');
} else if (error.message.toLowerCase().includes('not set up') ||
error.message.toLowerCase().includes('wallet')) {
alert('Apple Pay is not set up. Please add a card to Apple Wallet.');
} else if (error.message.toLowerCase().includes('restricted')) {
alert('Apple Pay is restricted on this device.');
} else {
alert(`Payment failed: ${error.message}`);
}
}Always verify Apple Pay payments on your backend to ensure payment success before fulfilling orders:
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';
onSuccess: async (result: BaseSubmitResult) => {
// Send to backend for verification
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 {
alert('Payment verification failed');
}
}Use the following backend code to verify Apple Pay transactions via the PXP API:
// BACKEND: Verify Apple Pay payment
app.post('/api/verify-payment', async (req, res) => {
const { systemTransactionId, merchantTransactionId } = req.body;
try {
// Query PXP API to get transaction details
const txnPath = `api/v1/transactions/${systemTransactionId}`;
const { authHeader, requestId } = createAuthHeader(
txnPath,
'',
process.env.PXP_TOKEN_ID,
process.env.PXP_TOKEN_VALUE
);
const transaction = await fetch(
`https://api-services.pxp.io/${txnPath}`,
{
headers: {
'X-Client-Id': process.env.PXP_CLIENT_ID,
'X-Request-Id': requestId,
'Authorization': authHeader
}
}
).then(r => r.json());
// Verify transaction state
if (transaction.state !== 'Authorised' && transaction.state !== 'Captured') {
return res.json({ success: false, error: 'Transaction not successful' });
}
// Verify merchant transaction ID matches
if (transaction.merchantTransactionId !== merchantTransactionId) {
return res.json({ success: false, error: 'Transaction ID mismatch' });
}
// Verify amount matches expected amount from your order records
const order = await getOrderByMerchantTransactionId(merchantTransactionId);
const txnAmount = transaction.amounts?.transactionValue || transaction.amount || 0;
if (Math.abs(txnAmount - order.amount) > 0.01) {
return res.json({ success: false, error: 'Amount mismatch' });
}
// Apple Pay payments show as Card funding type in PXP API
const fundingType = transaction.fundingData?.fundingType ||
transaction.fundingType ||
'Unknown';
if (fundingType !== 'Card') {
return res.json({ success: false, error: 'Invalid funding type' });
}
// Fulfill order
const orderId = await fulfillOrder(transaction);
return res.json({ success: true, orderId });
} catch (error) {
console.error('Verification error:', error);
return res.json({ success: false, error: 'Verification failed' });
}
});Drop-in supports Apple Pay recurring payments for subscription-based services and merchant-initiated transactions (MITs). Configure the recurring object in your transaction data to set up the initial payment, then use the stored gatewayTokenId for subsequent charges.
PXP doesn't provide an automatic payment scheduler. You must implement your own scheduling system to initiate subsequent recurring charges via the Transactions API.
Drop-in doesn't support Apple Pay's native recurring payment features (recurringPaymentRequest, deferredPaymentRequest, or automaticReloadPaymentRequest). For those features, use the Apple Pay button Ccmponent instead. Drop-in uses the standard recurring object approach, which works across all payment methods.
The recurring payment flow involves two phases:
Phase 1: Initial setup (via Drop-in)
- Configure the
recurringobject intransactionDatawith frequency and expiration. - The customer authenticates with Face ID/Touch ID and completes the Apple Pay payment.
- Drop-in automatically sets
processingModeltoMerchantInitiatedInitialRecurring. - The backend stores the
gatewayTokenIdfrom the transaction response.
When you include the recurring object in your transaction data, Drop-in automatically sets the processingModel to MerchantInitiatedInitialRecurring for the initial transaction.
Phase 2: Subsequent charges (via backend API)
- Your scheduling system triggers a charge based on the configured frequency.
- Your backend calls the Transactions API using the stored
gatewayTokenId. - Set
processingModeltoMerchantInitiatedSubsequentRecurringfor subsequent charges. - The payment processes without customer interaction.
Configure recurring payment frequency in your Drop-in initialisation:
import CheckoutDropIn from '@pxpio/web-components-sdk/src/checkoutDropIn/CheckoutDropIn';
import IntentType from '@pxpio/web-components-sdk/src/basePxpCheckout/types/IntentType';
import { BaseSubmitResult } from '@pxpio/web-components-sdk/src/checkoutDropIn/types/BaseSubmitResult';
import BaseSdkException from '@pxpio/web-components-sdk/src/types/sdkExceptions/BaseSdkException';
// Get session from backend (with Apple Pay enabled)
const response = await fetch('/api/create-session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
});
const result = await response.json();
const sessionData = result.data;
// Initialise Drop-in with Apple Pay recurring configuration
const checkoutDropIn = CheckoutDropIn.initialize({
environment: 'test',
session: sessionData,
ownerId: 'MERCHANT-1',
ownerType: 'MerchantGroup',
transactionData: {
currency: 'USD',
amount: 9.99,
entryType: 'Ecom',
intent: {
card: IntentType.Authorisation,
paypal: IntentType.Authorisation
},
merchantTransactionId: crypto.randomUUID(),
merchantTransactionDate: () => new Date().toISOString(),
// Recurring configuration for processing model
recurring: {
frequencyInDays: 30, // Bill every 30 days
frequencyExpiration: '2025-12-31' // Token expires on this date
}
},
onGetShopper: () => Promise.resolve({ id: 'customer-123' }),
methodConfig: {
global: {
transactionInfo: {
countryCode: 'US',
merchantDisplayName: 'Subscription Service',
totalLabel: 'Monthly Subscription',
totalStatus: 'FINAL',
lineItems: [
{ label: 'Premium Plan', amount: '9.99' }
]
}
},
applePay: {
// No specific recurring configuration needed for Drop-in
// Drop-in uses the standard recurring object above
}
},
onSuccess: async (result: BaseSubmitResult) => {
console.log('Apple Pay recurring payment setup completed');
// Verify and store gatewayTokenId on backend
const response = await fetch('/api/verify-and-setup-recurring', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
systemTransactionId: result.systemTransactionId,
merchantTransactionId: result.merchantTransactionId,
paymentMethod: 'ApplePay'
})
}).then(r => r.json());
if (response.success) {
window.location.href = `/subscription-success?subscriptionId=${response.subscriptionId}`;
}
},
onError: (error: BaseSdkException) => {
if (error.ErrorCode === 'SDK0615') {
console.log('User cancelled Apple Pay');
return;
}
console.error('Apple Pay subscription setup failed:', error);
alert(`Subscription setup failed: ${error.message}`);
}
});
checkoutDropIn.create('checkout-drop-in-container');After the initial payment, extract and store the gatewayTokenId:
// BACKEND: Verify Apple Pay payment and set up recurring subscription
app.post('/api/verify-and-setup-recurring', async (req, res) => {
const { systemTransactionId, merchantTransactionId, paymentMethod } = req.body;
try {
// Query PXP API to get transaction details
const txnPath = `api/v1/transactions/${systemTransactionId}`;
const { authHeader, requestId } = createAuthHeader(
txnPath,
'',
process.env.PXP_TOKEN_ID,
process.env.PXP_TOKEN_VALUE
);
const transaction = await fetch(
`https://api-services.pxp.io/${txnPath}`,
{
headers: {
'X-Client-Id': process.env.PXP_CLIENT_ID,
'X-Request-Id': requestId,
'Authorization': authHeader
}
}
).then(r => r.json());
// Verify transaction is successful
if (transaction.state !== 'Authorised' && transaction.state !== 'Captured') {
return res.json({ success: false, error: 'Transaction not successful' });
}
// Store gatewayTokenId for recurring charges
if (transaction.fundingData?.gatewayTokenId) {
const subscription = await database.subscriptions.insert({
shopperId: req.user.shopperId,
gatewayTokenId: transaction.fundingData.gatewayTokenId,
paymentMethod: paymentMethod,
amount: transaction.amounts?.transactionValue || 0,
currency: transaction.amounts?.currencyCode || 'USD',
frequencyInDays: 30,
nextChargeDate: calculateNextChargeDate(30),
status: 'active',
createdAt: new Date()
});
return res.json({ success: true, subscriptionId: subscription.id });
}
return res.json({ success: false, error: 'No gateway token found' });
} catch (error) {
console.error('Setup error:', error);
return res.json({ success: false, error: 'Setup failed' });
}
});Use the Transactions API to charge subsequent payments:
// BACKEND: Charge subsequent recurring payment
async function chargeRecurringPayment(subscription) {
const requestBody = {
merchant: "MERCHANT-1",
site: "SITE-1",
merchantTransactionId: `recurring-${Date.now()}`,
merchantTransactionDate: new Date().toISOString(),
transactionMethod: {
intent: "Purchase",
entryType: "Ecom",
fundingType: "Card"
},
fundingData: {
card: {
gatewayTokenId: subscription.gatewayTokenId
}
},
amounts: {
transaction: subscription.amount,
currencyCode: subscription.currency
},
recurring: {
processingModel: "MerchantInitiatedSubsequentRecurring"
}
};
const path = 'api/v1/transactions';
const { authHeader, requestId } = createAuthHeader(
path,
JSON.stringify(requestBody),
process.env.PXP_TOKEN_ID,
process.env.PXP_TOKEN_VALUE
);
const response = await fetch(
`https://api-services.pxp.io/${path}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Client-Id': process.env.PXP_CLIENT_ID,
'X-Request-Id': requestId,
'Authorization': authHeader
},
body: JSON.stringify(requestBody)
}
);
const result = await response.json();
if (result.state === 'Captured' || result.state === 'Authorised') {
await database.subscriptions.update(subscription.id, {
lastChargeDate: new Date(),
nextChargeDate: calculateNextChargeDate(subscription.frequencyInDays),
lastTransactionId: result.systemTransactionId
});
console.log('Recurring charge successful:', result.systemTransactionId);
return { success: true, transactionId: result.systemTransactionId };
} else {
console.error('Recurring charge failed:', result);
return { success: false, error: result.errorReason };
}
}