Perform Apple Pay recurring, deferred, and automatic reload transactions for Web.
Apple Pay supports three types of recurring payment scenarios:
- Recurring payments: For subscription-based payments where the customer signs up and pays initially, and you then automatically charge them at specified intervals (monthly, yearly, etc.). Apple Pay handles the subscription lifecycle with proper customer consent.
- Deferred payments: For transactions where authorisation happens now, but payment is processed later (e.g., hotel bookings, pre-orders). Apple Pay provides secure payment promises.
- Automatic reload payments: For topping up stored value accounts when balance falls below a threshold (e.g., gift cards, transit cards). Apple Pay manages the reload triggers automatically.
All Apple Pay recurring payment types provide enhanced security, customer control, and transparent billing through Apple's ecosystem.
To set up a recurring Apple Pay payment, you need to include recurring payment configuration in your Apple Pay component setup.
// Configure subscription transaction data
const transactionData = {
amount: 9.99,
currency: 'USD',
entryType: 'Ecom',
intent: {
card: 'Purchase'
},
merchantTransactionId: 'sub-setup-123',
merchantTransactionDate: () => new Date().toISOString(),
shopper: {
email: 'customer@example.com',
firstName: 'John',
lastName: 'Doe'
}
};
// Create complete SDK configuration
const sdkConfig = {
environment: 'test',
session: {
sessionId: 'your-session-id',
allowedFundingTypes: {
wallets: {
applePay: {
merchantId: 'merchant.com.yourcompany'
}
}
}
},
transactionData, // ← This connects Step 1 to Step 2
// ... other SDK settings
};Next, you're going to use your sdkConfig to create the Apple Pay component with recurring payment configuration. When the customer authorises with Apple Pay, the recurring payments will start.
const applePayComponent = pxpSdk.create('apple-pay-button', {
merchantDisplayName: 'Subscription Service',
paymentDescription: 'Monthly Premium Subscription',
usingCss: false,
style: {
type: 'subscribe', // Use subscribe button type
buttonstyle: 'black',
height: '50px',
borderRadius: '8px'
},
paymentRequest: {
countryCode: 'US',
currencyCode: 'USD',
merchantCapabilities: ['supports3DS', 'supportsEMV'],
supportedNetworks: ['visa', 'masterCard', 'amex'],
total: {
label: 'First Month',
amount: '9.99'
},
// Recurring payment configuration
recurringPaymentRequest: {
paymentDescription: 'Monthly Premium Subscription',
regularBilling: {
label: 'Monthly Subscription',
amount: '9.99',
recurringPaymentIntervalUnit: 'month',
recurringPaymentIntervalCount: 1,
recurringPaymentStartDate: '2024-02-01T00:00:00Z',
recurringPaymentEndDate: '2025-01-31T23:59:59Z' // Optional end date
},
managementURL: 'https://yoursite.com/manage-subscription',
tokenNotificationURL: 'https://yoursite.com/webhook/subscription' // Optional
}
},
onPostAuthorisation: async (data) => {
console.log('Recurring Apple Pay subscription processing');
console.log('Merchant Transaction ID:', data.merchantTransactionId);
// Retrieve authorisation result from backend
const result = await getAuthorisationResultFromGateway(
data.merchantTransactionId,
data.systemTransactionId
);
if (result.status === 'Authorised') {
console.log('Recurring Apple Pay subscription started!');
console.log('Transaction ID:', result.transactionId);
// Store recurring payment information
storeRecurringPayment({
transactionId: result.transactionId,
merchantTransactionId: merchantTransactionId,
customerId: 'customer@example.com',
subscriptionType: 'monthly',
amount: 9.99,
currency: 'USD',
startDate: '2024-02-01',
endDate: '2025-01-31'
});
// Redirect to success page
window.location.href = '/subscription-success';
}
},
onError: (error) => {
console.error('Apple Pay subscription error:', error);
showError('Failed to start subscription. Please try again.');
}
});
applePayComponent.mount('apple-pay-container');For subscriptions with trial periods, you can configure both trial and regular billing:
const applePayConfig = {
merchantDisplayName: 'SaaS Platform',
paymentDescription: 'Annual Subscription with Trial',
paymentRequest: {
countryCode: 'US',
currencyCode: 'USD',
merchantCapabilities: ['supports3DS', 'supportsEMV'],
supportedNetworks: ['visa', 'masterCard', 'amex'],
total: {
label: 'Trial Period',
amount: '0.00' // Free trial
},
recurringPaymentRequest: {
paymentDescription: 'Annual SaaS Subscription',
regularBilling: {
label: 'Annual Subscription',
amount: '299.99',
recurringPaymentIntervalUnit: 'year',
recurringPaymentIntervalCount: 1,
recurringPaymentStartDate: '2024-02-01T00:00:00Z'
},
trialBilling: {
label: '14-Day Free Trial',
amount: '0.00',
recurringPaymentIntervalUnit: 'day',
recurringPaymentIntervalCount: 14,
recurringPaymentStartDate: '2024-01-15T00:00:00Z',
recurringPaymentEndDate: '2024-01-29T23:59:59Z'
},
managementURL: 'https://yoursite.com/manage-subscription',
tokenNotificationURL: 'https://yoursite.com/webhook/subscription'
}
}
};For deferred payments (e.g., hotel bookings or pre-orders), configure the payment to be charged at a future date:
const deferredApplePayConfig = {
merchantDisplayName: 'Travel Agency',
paymentDescription: 'Hotel Booking',
paymentRequest: {
countryCode: 'US',
currencyCode: 'USD',
merchantCapabilities: ['supports3DS', 'supportsEMV'],
supportedNetworks: ['visa', 'masterCard', 'amex'],
total: {
label: 'Hotel Booking',
amount: '200.00'
},
// Deferred payment configuration
deferredPaymentRequest: {
paymentDescription: 'Hotel Booking - Payment Due at Check-in',
deferredBilling: {
label: 'Hotel Payment',
amount: '200.00',
deferredPaymentDate: '2024-06-15T15:00:00Z' // Check-in date
},
managementURL: 'https://yoursite.com/manage-booking',
freeCancellationDate: '2024-06-10T23:59:59Z', // Optional cancellation deadline
tokenNotificationURL: 'https://yoursite.com/webhook/deferred-payment'
}
},
onPostAuthorisation: async (data) => {
console.log('Deferred Apple Pay payment processing');
console.log('Merchant transaction ID:', data.merchantTransactionId);
// Retrieve authorisation result from backend
const result = await getAuthorisationResultFromGateway(
data.merchantTransactionId,
data.systemTransactionId
);
if (result.status === 'Authorised') {
console.log('Deferred Apple Pay payment authorized!');
// Store deferred payment information
storeDeferredPayment({
authorizationId: result.transactionId,
merchantTransactionId: merchantTransactionId,
customerId: result.customerEmail,
bookingReference: 'HTL-' + Date.now(),
amount: 200.00,
currency: 'USD',
chargeDate: '2024-06-15T15:00:00Z',
cancellationDeadline: '2024-06-10T23:59:59Z'
});
// Send confirmation
window.location.href = '/booking-confirmed';
}
}
};When the deferred payment date arrives, you'll need to process the actual charge:
// This would typically run in a background job on the deferred payment date
async function processDeferredPayment(deferredPaymentId) {
try {
const deferredPayment = await getDeferredPayment(deferredPaymentId);
// Process the actual charge using the stored Apple Pay token
const chargeResult = await chargeDeferredApplePayment({
originalToken: deferredPayment.applePayToken,
amount: deferredPayment.amount,
currency: deferredPayment.currency,
merchantTransactionId: `deferred-${deferredPaymentId}`
});
if (chargeResult.success) {
console.log('Deferred payment charged successfully');
await updateDeferredPaymentStatus(deferredPaymentId, 'CHARGED');
await sendPaymentConfirmation(deferredPayment.customerId);
} else {
console.error('Deferred payment failed:', chargeResult.error);
await updateDeferredPaymentStatus(deferredPaymentId, 'FAILED');
await sendPaymentFailureNotification(deferredPayment.customerId);
}
} catch (error) {
console.error('Error processing deferred payment:', error);
}
}For automatic reload payments (e.g., gift cards, transit cards), configure the reload trigger and amount:
const autoReloadApplePayConfig = {
merchantDisplayName: 'Gift Card Store',
paymentDescription: 'Gift Card Purchase',
paymentRequest: {
countryCode: 'US',
currencyCode: 'USD',
merchantCapabilities: ['supports3DS', 'supportsEMV'],
supportedNetworks: ['visa', 'masterCard', 'amex'],
total: {
label: 'Gift Card',
amount: '50.00'
},
// Automatic reload configuration
automaticReloadPaymentRequest: {
paymentDescription: 'Automatic Reload for Gift Card',
automaticReloadBilling: {
label: 'Auto Reload',
amount: '25.00',
automaticReloadPaymentThresholdAmount: '10.00' // Reload when balance drops below $10
},
managementURL: 'https://yoursite.com/manage-gift-card',
tokenNotificationURL: 'https://yoursite.com/webhook/auto-reload'
}
},
onPostAuthorisation: async (data) => {
console.log('Apple Pay automatic reload processing');
console.log('Merchant transaction ID:', data.merchantTransactionId);
// Retrieve authorisation result from backend
const result = await getAuthorisationResultFromGateway(
data.merchantTransactionId,
data.systemTransactionId
);
if (result.status === 'Authorised') {
console.log('Apple Pay automatic reload set up!');
// Store automatic reload configuration
storeAutoReloadConfig({
transactionId: result.transactionId,
merchantTransactionId: merchantTransactionId,
customerId: result.customerEmail,
giftCardId: 'GC-' + Date.now(),
initialAmount: 50.00,
reloadAmount: 25.00,
thresholdAmount: 10.00,
currency: 'USD'
});
// Redirect to gift card details
window.location.href = '/gift-card-success';
}
}
};Monitor account balances and trigger automatic reloads when thresholds are reached:
// This would typically run as a scheduled job to check balances
async function checkAutoReloadTriggers() {
const autoReloadAccounts = await getActiveAutoReloadAccounts();
for (const account of autoReloadAccounts) {
const currentBalance = await getAccountBalance(account.giftCardId);
if (currentBalance <= account.thresholdAmount) {
try {
console.log(`Triggering auto-reload for account ${account.giftCardId}`);
// Process automatic reload using stored Apple Pay token
const reloadResult = await processAutoReload({
originalToken: account.applePayToken,
amount: account.reloadAmount,
currency: account.currency,
accountId: account.giftCardId,
merchantTransactionId: `reload-${account.giftCardId}-${Date.now()}`
});
if (reloadResult.success) {
// Update account balance
await updateAccountBalance(
account.giftCardId,
currentBalance + account.reloadAmount
);
// Send notification to customer
await sendAutoReloadNotification(account.customerId, {
amount: account.reloadAmount,
newBalance: currentBalance + account.reloadAmount,
transactionId: reloadResult.transactionId
});
console.log('Auto-reload completed successfully');
} else {
console.error('Auto-reload failed:', reloadResult.error);
await sendAutoReloadFailureNotification(account.customerId);
}
} catch (error) {
console.error('Error processing auto-reload:', error);
}
}
}
}
// Run every hour to check for reload triggers
setInterval(checkAutoReloadTriggers, 60 * 60 * 1000);You can combine multiple payment types in a single Apple Pay transaction:
const combinedApplePayConfig = {
merchantDisplayName: 'Premium Service',
paymentDescription: 'Premium Subscription with Auto-Reload',
paymentRequest: {
countryCode: 'US',
currencyCode: 'USD',
merchantCapabilities: ['supports3DS', 'supportsEMV'],
supportedNetworks: ['visa', 'masterCard', 'amex'],
total: {
label: 'First Payment',
amount: '29.99'
},
// Recurring payment for subscription
recurringPaymentRequest: {
paymentDescription: 'Monthly Premium Subscription',
regularBilling: {
label: 'Monthly Premium',
amount: '29.99',
recurringPaymentIntervalUnit: 'month',
recurringPaymentIntervalCount: 1,
recurringPaymentStartDate: '2024-01-01T00:00:00Z',
recurringPaymentEndDate: '2024-12-31T23:59:59Z'
},
managementURL: 'https://yoursite.com/manage-subscription',
tokenNotificationURL: 'https://yoursite.com/webhook/subscription'
},
// Automatic reload for credits
automaticReloadPaymentRequest: {
paymentDescription: 'Auto Reload Credits',
automaticReloadBilling: {
label: 'Credit Reload',
amount: '10.00',
automaticReloadPaymentThresholdAmount: '5.00'
},
managementURL: 'https://yoursite.com/manage-credits'
}
},
onPostAuthorisation: async (data) => {
console.log('Combined Apple Pay payment processing');
console.log('Merchant Transaction ID:', data.merchantTransactionId);
// Retrieve authorisation result from backend
const result = await getAuthorisationResultFromGateway(
data.merchantTransactionId,
data.systemTransactionId
);
if (result.status === 'Authorised') {
console.log('Combined Apple Pay payment configured!');
// Store both recurring and auto-reload configurations
storeCombinedPaymentConfig({
transactionId: result.transactionId,
merchantTransactionId: merchantTransactionId,
customerId: result.customerEmail,
subscription: {
amount: 29.99,
interval: 'monthly',
startDate: '2024-01-01',
endDate: '2024-12-31'
},
autoReload: {
reloadAmount: 10.00,
thresholdAmount: 5.00
}
});
}
}
};For recurring payments, you should also implement a consent component to clearly communicate the recurring nature of the payment:
// Create Apple Pay consent component
const applePayConsentComponent = pxpSdk.create('apple-pay-consent', {
label: 'I agree to store my Device Primary Account Number (DPAN) for recurring payments and authorise monthly charges of $9.99',
checkedColor: '#007AFF',
uncheckedColor: '#8E8E93',
size: 20.0,
checked: false
});
// Connect consent to Apple Pay button
const applePayConfig = {
// ... other configuration
applePayConsentComponent: applePayConsentComponent,
// Alternative: Use callback for consent
onGetConsent: () => {
return document.getElementById('recurring-consent-checkbox').checked;
}
};
applePayConsentComponent.mount('consent-container');Apple Pay requires management URLs for recurring payments where customers can:
- View upcoming charges.
- Modify subscription details.
- Cancel subscriptions.
- Update payment information.
// Example management page functionality
class SubscriptionManager {
async getSubscriptions(customerId) {
return await fetch(`/api/customers/${customerId}/subscriptions`);
}
async cancelSubscription(subscriptionId) {
// Cancel with Apple Pay
await this.notifyApplePayCancellation(subscriptionId);
// Cancel in your system
return await fetch(`/api/subscriptions/${subscriptionId}/cancel`, {
method: 'POST'
});
}
async updateSubscription(subscriptionId, updates) {
// Update subscription details
return await fetch(`/api/subscriptions/${subscriptionId}`, {
method: 'PATCH',
body: JSON.stringify(updates)
});
}
private async notifyApplePayCancellation(subscriptionId) {
// Implement Apple Pay notification for cancellation
// This ensures Apple Pay is aware of the cancellation
}
}Handle various failure scenarios for recurring payments:
const applePayConfig = {
onError: (error) => {
console.error('Apple Pay recurring payment error:', error);
if (error.message.includes('recurring')) {
showError('Unable to set up recurring payments. Please try again.');
} else if (error.message.includes('deferred')) {
showError('Unable to authorise future payment. Please try again.');
} else if (error.message.includes('reload')) {
showError('Unable to set up automatic reload. Please try again.');
} else {
showError('Payment setup failed. Please try again.');
}
},
onPostAuthorisation: async (data) => {
console.log('Merchant transaction ID:', data.merchantTransactionId);
// Retrieve authorization result from backend
const result = await getAuthorisationResultFromGateway(
data.merchantTransactionId,
data.systemTransactionId
);
if (result.status !== 'Authorised') {
// Handle specific recurring payment failures
switch (result.errorCode) {
case 'RECURRING_NOT_SUPPORTED':
showError('Recurring payments are not supported for this card.');
break;
case 'DEFERRED_NOT_SUPPORTED':
showError('Deferred payments are not supported for this card.');
break;
case 'AUTO_RELOAD_NOT_SUPPORTED':
showError('Automatic reload is not supported for this card.');
break;
case 'INSUFFICIENT_FUNDS':
showError('Insufficient funds for recurring payment setup.');
break;
default:
showError('Payment authorisation failed. Please try again.');
}
}
}
};Implement webhooks to handle Apple Pay notifications for recurring payments:
// Express.js webhook handler example
app.post('/webhook/apple-pay', express.json(), (req, res) => {
const { type, data } = req.body;
switch (type) {
case 'RECURRING_PAYMENT_CHARGED':
handleRecurringPaymentCharged(data);
break;
case 'RECURRING_PAYMENT_FAILED':
handleRecurringPaymentFailed(data);
break;
case 'SUBSCRIPTION_CANCELLED':
handleSubscriptionCancelled(data);
break;
case 'AUTO_RELOAD_TRIGGERED':
handleAutoReloadTriggered(data);
break;
case 'DEFERRED_PAYMENT_DUE':
handleDeferredPaymentDue(data);
break;
default:
console.log('Unknown webhook type:', type);
}
res.status(200).send('OK');
});
async function handleRecurringPaymentCharged(data) {
// Update subscription status
await updateSubscriptionStatus(data.subscriptionId, 'ACTIVE');
// Send receipt to customer
await sendRecurringPaymentReceipt(data.customerId, data);
// Update customer balance/access
await updateCustomerAccess(data.customerId, data.subscriptionType);
}
async function handleRecurringPaymentFailed(data) {
// Update subscription status
await updateSubscriptionStatus(data.subscriptionId, 'PAYMENT_FAILED');
// Send payment failure notification
await sendPaymentFailureNotification(data.customerId, {
reason: data.failureReason,
nextAttempt: data.nextRetryDate
});
// Implement grace period or suspend access
await suspendCustomerAccess(data.customerId, data.subscriptionType);
}