# Apple Pay JS SDK Integration
## Current Integration Pattern
Merchant uses the Smart Payment Buttons integration pattern.
``` javascript
// Adding script for integrating standalone button.
<script src="https://www.paypal.com/sdk/js?client-id=YOUR_CLIENT_ID¤cy=USD&buyer-country=US&merchant-id=SUB_MERCHANT_ID&components=buttons"></script>
// Adding Apple Pay button container ID
<div id="applepay-btn"></div>
<script>
// Render Apple Pay button
paypal.Buttons({
fundingSource: paypal.FUNDING.APPLEPAY,
paymentRequest: {
applepay: {
requiredShippingContactFields: ["name", "phone", "email", "postalAddress"]
}
},
createOrder: () => {
return fetch("/api/orders", {
method: 'post'
// use the "body" param to optionally pass additional order information like
// product ids or amount.
})
.then((res) => res.json())
.then((orderData) => {
orderId = orderData.id; // needed later to complete capture
return orderData.id
})
},
onShippingChange: (data, actions) => {
// Patch shipping amounts, shipping address to order.
fetch(`/your-server/submit-patch/${data.orderID}`, {
method: "post",
})
.then((res) => res.json())
.then((data) => {
console.log(data);
})
.catch((err) => console.error(err));
},
onApprove: (data, actions) => {
//Submit approval to the server and authorize or capture the order.
fetch(`/your-server/submit-approval/${data.orderID}`, {
method: "post",
})
.then((res) => res.json())
.then((data) => {
console.log(data);
})
.catch((err) => console.error(err));
},
onError: (err) => {...}
}).render("#applepay-btn");
</script>
```
Wallets like ApplePay/GooglePay/MetaPay provide a lot of customization options for displaying line item labels, shipping method amounts, billing/shipping address, coupon codes, recurring payment agreement details and error messages on the payment sheet. They also provide specific callbacks for shipping method/address selection, coupon code selection, payment method selection, etc. In each of these callbacks, these wallets allow the merchant to update the payment information that is displayed to the buyer on the payment sheet. This makes sure that buyer will see the latest payment information before approving the payment.
In our current integration pattern, the order resource serves as the single source of data for both payment transaction data and also the experience customization data. The rich experience customization capabilities being introduced by these wallets is making it unscalable to manage the experience data via the orders resource.
Also the current approach of updating the payment sheet on buyer action introduces unnecessary latency.
1. Buyer action on payment sheet such as 'Buyer adds shipping address'.
2. Wallet SDK triggers PayPal handler.
3. PayPal JS SDK triggers Merchant handler.
4. Merchant updates order with PayPal backend (network latency).
5. PayPal JS SDK gets updated order data from PayPal backend (network latency).
6. PayPal calls Wallet SDK api to update payment sheet.
7. Wallet SDK updates payment sheet to display updated order details to buyer.
Steps 4,5 introduce unwanted latency for payment sheet updates on buyer actions. This can be an issue particularly in cases where buyer is on a low- bandwidth network.
## Proposed Integration Pattern
The proposed integration decouples the payment experience customization from the payment transaction. Merchant will use the ApplePay JS SDK directy to utlilize the full experience customization capabilities provided by ApplePay. PayPal JS SDK will provide utility apis to enable the direct ApplePay Web JS SDK integration for merchants. PayPal orders api will be used by the merchants to process the payment transaction.
PayPal JS SDK will provide:
1. `paypal.applePay.getConfiguration` api to check ApplePay eligibility for the merchant account and populate values for the 4 params - `countryCode,supportedNetworks,merchantCapabilities` - in the [ApplePayPaymentRequest](https://developer.apple.com/documentation/apple_pay_on_the_web/applepaypaymentrequest).
2. `paypal.applePay.validateMerchant` api to handle merchant validation during the [onvalidatemetchant](https://developer.apple.com/documentation/apple_pay_on_the_web/applepaysession/1778021-onvalidatemerchant) ApplePay Web JS SDK callback.
3. `paypal.applePay.confirmOrder` api to process ApplePay token during the [onpaymentauthorized](https://developer.apple.com/documentation/apple_pay_on_the_web/applepaysession/1778020-onpaymentauthorized) ApplePay Web JS SDK callback.
### One-time payment/purchase
```
<script src="https://www.paypal.com/sdk/js?client-id=YOUR_CLIENT_ID¤cy=USD&buyer-country=US&merchant-id=SUB_MERCHANT_ID&components=applepayweb"></script>
<script src="https://applepay.cdn-apple.com/jsapi/v1/apple-pay-sdk.js"></script>
<div id="applepay-btn"></div>
<script>
// Render Apple Pay button
document.onload = () => {
if (window.ApplePaySession && ApplePaySession.supportsVersion(4) && ApplePaySession.canMakePayments()) {
// This device supports version 4 of Apple Pay.
paypal.applePay.getConfiguration({}, function(err, applePayConfig) {
/* err object as below.
{
name: "INVALID_REQUEST",
fullDescription: "<error message descrition>",
paypalDebugId: "12189edbf9cba"
}
*/
// if applePayConfig.isApplePayEligible is 'false', Merchant/Partner need to make sure that they have correctly completed the ApplePay onboarding steps for Merchant/Partner and sub merchants.
if (!err && applePayConfig.isApplePayEligible) {
window.applePayConfig = applePayConfig;
// Display Apple Pay Button. Register on click handler 'onApplePayButtonClick'.
var applePayButton = document.getElementById("applepay-btn");
applePayButton.innerHTML = '<apple-pay-button buttonstyle="black" type="buy" locale="el-GR">';
applePayButton.addEventListener("click", onApplePayButtonClick);
} else {
// Do not display Apple Pay Button.
}
});
}
};
// Handle Apple Pay button click
function onApplePayButtonClick() {
var applePayPaymentRequest = {
"countryCode": window.applePayConfig.countryCode,
"merchantCapabilities": window.applePayConfig.merchantCapabilities,
"supportedNetworks": window.applePayConfig.supportedNetworks,
"currencyCode": "USD",
"requiredShippingContactFields": ["name", "phone", "email", "postalAddress],
"total": {
"label": "Demo",
"type": "final",
"amount": "1.99"
}
};
var session = new ApplePaySession(4, applePayPaymentRequest);
session.onvalidatemerchant = (event) => {
paypal.applePay.validateMerchant({
displayName: "Merchant Brand Name",
validationUrl: event.validationURL
}, (err, merchantSession) => {
if (err) {
/* err object as below.
{
name: "INVALID_REQUEST",
fullDescription: "<error message descrition>",
paypalDebugId: "12189edbf9cba"
}
OR
{
name: "UNPROCESSABLE_ENTITY"
fullDescription: "<error message descrition which contains NOT_ENABLED_FOR_APPLE_PAY - The 'client_id' and/or 'merchant-id' is not setup to be able to process apple pay>",
paypalDebugId: "12189edbf9cba"
}
NOT_ENABLED_FOR_APPLE_PAY - Merchant/Partner need to make sure that they have correctly completed the domain registration steps.
*/
// You may show an error to the user, e.g. 'Apple Pay failed to load.'
return;
}
session.completeMerchantValidation(merchantSession);
});
};
session.onshippingcontactselected = (event) => {
console.log('Your shipping contact is:', event.shippingContact);
// Update payment details.
var shippingContactUpdate = {...} // https://developer.apple.com/documentation/apple_pay_on_the_web/applepayshippingcontactupdate
session.completeShippingContactSelection(shippingContactUpdate); // Set shippingContactUpdate=null if there are no updates.
};
session.onshippingmethodselected = (event) => {
console.log('Your shipping method is:', event.shippingMethod);
// Update payment details.
var shippingMethodUpdate = {...} // https://developer.apple.com/documentation/apple_pay_on_the_web/applepayshippingmethodupdate
session.completeShippingMethodSelection(shippingMethodUpdate); // Set shippingMethodUpdate=null if there are no updates.
};
session.onpaymentauthorized = (event) => {
console.log('Your billing address is:', event.payment.billingContact);
console.log('Your shipping address is:', event.payment.shippingContact);
fetch("/api/orders", {
method: 'post'
body:
// use the "body" param to optionally pass additional order information like
// product ids or amount.
})
.then((res) => res.json())
.then((orderData) => {
var orderId = orderData.id;
paypal.applePay.confirmOrder({
orderId: orderId,
token: event.payment.token,
billingContact: {
givenName: "John"
familyName: "Doe",
emailAddress: "john.doe@example.com",
phoneNumber: "18882211161",
addressLines: [
"123 Townsend S",
"Floor 6"
],
locality: "San Francisco",
administrativeArea: "CA",
postalCode: "94107",
countryCode: "US"
} // event.payment.billingContact or provide billing address collected on merchant page.
}, (confirmError, confirmResult) => {
/*
confirmError object as below.
{
name: "INVALID_REQUEST",
fullDescription: "<error message descrition which contains INVALID_DECRYPTED_TOKEN - The decrypted token is not valid",
paypalDebugId: "12189edbf9cba"
}
OR
{
name: "INVALID_REQUEST",
fullDescription: "<error message descrition which contains billingContact/postalCode INVALID_STRING_MAX_LENGTH - The value of a field is too long.>",
paypalDebugId: "12189edbf9cba"
}
OR
{
name: "UNPROCESSABLE_ENTITY",
fullDescription: "<error message descrition which contains ORDER_ALREADY_AUTHORIZED- Order already captured. If 'intent=CAPTURE' only one capture per order is allowed.>",
paypalDebugId: "12189edbf9cba"
}
INVALID_DECRYPTED_TOKEN - Merchant needs to make sure that the token returned by ApplePay SDK is sent correctly to PayPal SDK. Also, merchant needs to make sure that amount approved by buyer on Apple payment sheet matches the amount in order resource.
*/
if (confirmError) {
console.error('Error confirming order with applepay token:', confirmError);
session.completePayment(ApplePaySession.STATUS_FAILURE);
return;
}
session.completePayment(ApplePaySession.STATUS_SUCCESS);
//Submit approval to the server and authorize or capture the order.
fetch(`/your-server/submit-approval/${data.orderID}`, {
method: "post",
})
.then((res) => res.json())
.then((data) => {
console.log(data);
})
.catch((err) => console.error(err));
});
});
};
// Open Apple sheet.
session.begin();
}
</script>
```
### Vault with purchase
```
<script src="https://www.paypal.com/sdk/js?client-id=YOUR_CLIENT_ID¤cy=USD&buyer-country=US&merchant-id=SUB_MERCHANT_ID&components=applepayweb"></script>
<script src="https://applepay.cdn-apple.com/jsapi/v1/apple-pay-sdk.js"></script>
<div id="applepay-btn"></div>
<script>
// Render Apple Pay button
document.onload = () => {
if (window.ApplePaySession && ApplePaySession.supportsVersion(4) && ApplePaySession.canMakePayments()) {
// This device supports version 4 of Apple Pay.
paypal.applePay.getConfiguration({}, function(err, applePayConfig) {
if (!err && applePayConfig.isApplePayEligible) {
window.applePayConfig = applePayConfig;
// Display Apple Pay Button. Register on click handler 'onApplePayButtonClick'.
var applePayButton = document.getElementById("applepay-btn");
applePayButton.innerHTML = '<apple-pay-button buttonstyle="black" type="buy" locale="el-GR">';
applePayButton.addEventListener("click", onApplePayButtonClick);
} else {
// Do not display Apple Pay Button.
}
});
}
};
// Handle Apple Pay button click
function onApplePayButtonClick() {
var applePayPaymentRequest = {
"countryCode": window.applePayConfig.countryCode,
"currencyCode": window.applePayConfig.currencyCode,
"merchantCapabilities": window.applePayConfig.merchantCapabilities,
"supportedNetworks": window.applePayConfig.supportedNetworks,
"lineItems": [
{
"label": "Recurring",
"amount": "4.99",
"paymentTiming": "recurring",
"recurringPaymentStartDate": "2022-08-03T07:56:44.582Z"
},
{
"label": "7 Day Trial",
"amount": "1.99",
"paymentTiming": "recurring",
"recurringPaymentEndDate": "2022-08-03T07:56:44.582Z"
}
],
"recurringPaymentRequest": {
"paymentDescription": "A description of the recurring payment to display to the user in the payment sheet.",
"regularBilling": {
"label": "Recurring",
"amount": "4.99",
"paymentTiming": "recurring",
"recurringPaymentStartDate": "2022-08-03T07:56:44.582Z"
},
"trialBilling": {
"label": "7 Day Trial",
"amount": "1.99",
"paymentTiming": "recurring",
"recurringPaymentEndDate": "2022-08-03T07:56:44.582Z"
}
},
"total": {
"label": "Demo",
"type": "final",
"amount": "1.99"
}
};
var session = new ApplePaySession(4, applePayPaymentRequest);
session.onvalidatemerchant = (event) => {
paypal.applePay.validateMerchant({
displayName: "Merchant Brand Name",
validationUrl: event.validationURL
}, (err, merchantSession) => {
if (err) {
// You should show an error to the user, e.g. 'Apple Pay failed to load.'
return;
}
session.completeMerchantValidation(merchantSession);
});
};
session.onshippingcontactselected = (event) => {
console.log('Your shipping contact is:', event.shippingContact);
// Update payment details.
var shippingContactUpdate = {...} // https://developer.apple.com/documentation/apple_pay_on_the_web/applepayshippingcontactupdate
session.completeShippingContactSelection(shippingContactUpdate); // Set shippingContactUpdate=null if there are no updates.
};
session.onshippingmethodselected = (event) => {
console.log('Your shipping method is:', event.shippingMethod);
// Update payment details.
var shippingMethodUpdate = {...} // https://developer.apple.com/documentation/apple_pay_on_the_web/applepayshippingmethodupdate
session.completeShippingMethodSelection(shippingMethodUpdate); // Set shippingMethodUpdate=null if there are no updates.
};
session.onpaymentauthorized = (event) => {
console.log('Your billing address is:', event.payment.billingContact);
console.log('Your shipping address is:', event.payment.shippingContact);
fetch("/api/orders", {
method: 'post'
body:
// use the "body" param to optionally pass additional order information like
// product ids or amount.
// set `payment_source.apple_pay.attributes.vault.store_in_vault=ON_SUCCESS` in the create order request.
// set `payment_source.apple_pay.stored_credential.payment_type=RECURRING/UNSCHEDULED` in the create order request.
})
.then((res) => res.json())
.then((orderData) => {
var orderId = orderData.id;
paypal.applePay.confirmOrder({
orderId: orderId,
token: event.payment.token,
billingContact: {
givenName: "John"
familyName: "Doe",
emailAddress: "john.doe@example.com",
phoneNumber: "18882211161",
addressLines: [
"123 Townsend S",
"Floor 6"
],
locality: "San Francisco",
administrativeArea: "CA",
postalCode: "94107",
countryCode: "US"
} // event.payment.billingContact or provide billing address collected on merchant page.
}, (confirmError, confirmResult) => {
if (confirmError) {
console.error('Error confirming order with applepay token:', confirmError);
session.completePayment(ApplePaySession.STATUS_FAILURE);
return;
}
session.completePayment(ApplePaySession.STATUS_SUCCESS);
//Submit approval to the server and authorize or capture the order.
fetch(`/your-server/submit-approval/${data.orderID}`, {
method: "post",
})
.then((res) => res.json()) // vault id or payment-token id will be present in `payment_source.apple_pay.vault.id` parameter in the response.
.then((data) => {
console.log(data);
})
.catch((err) => console.error(err));
});
});
};
// Open Apple sheet.
session.begin();
}
</script>
```
### Vault without purchase
```
<script src="https://www.paypal.com/sdk/js?client-id=YOUR_CLIENT_ID¤cy=USD&buyer-country=US&merchant-id=SUB_MERCHANT_ID&components=applepayweb"></script>
<script src="https://applepay.cdn-apple.com/jsapi/v1/apple-pay-sdk.js"></script>
<div id="applepay-btn"></div>
<script>
// Render Apple Pay button
document.onload = () => {
if (window.ApplePaySession && ApplePaySession.supportsVersion(4) && ApplePaySession.canMakePayments()) {
// This device supports version 4 of Apple Pay.
paypal.applePay.getConfiguration({}, function(err, applePayConfig) {
if (!err && applePayConfig.isApplePayEligible) {
window.applePayConfig = applePayConfig;
// Display Apple Pay Button. Register on click handler 'onApplePayButtonClick'.
var applePayButton = document.getElementById("applepay-btn");
applePayButton.innerHTML = '<apple-pay-button buttonstyle="black" type="buy" locale="el-GR">';
applePayButton.addEventListener("click", onApplePayButtonClick);
} else {
// Do not display Apple Pay Button.
}
});
}
};
// Handle Apple Pay button click
function onApplePayButtonClick() {
var applePayPaymentRequest = {
"countryCode": window.applePayConfig.countryCode,
"currencyCode": window.applePayConfig.currencyCode,
"merchantCapabilities": window.applePayConfig.merchantCapabilities,
"supportedNetworks": window.applePayConfig.supportedNetworks,
"lineItems": [
{
"label": "Recurring",
"amount": "4.99",
"paymentTiming": "recurring",
"recurringPaymentStartDate": "2022-08-03T07:56:44.582Z"
},
{
"label": "7 Day Trial",
"amount": "0.00",
"paymentTiming": "recurring",
"recurringPaymentEndDate": "2022-08-03T07:56:44.582Z"
}
],
"recurringPaymentRequest": {
"paymentDescription": "A description of the recurring payment to display to the user in the payment sheet.",
"regularBilling": {
"label": "Recurring",
"amount": "4.99",
"paymentTiming": "recurring",
"recurringPaymentStartDate": "2022-08-03T07:56:44.582Z"
},
"trialBilling": {
"label": "7 Day Trial",
"amount": "0.00",
"paymentTiming": "recurring",
"recurringPaymentEndDate": "2022-08-03T07:56:44.582Z"
}
},
"total": {
"label": "Demo",
"type": "final",
"amount": "0.00"
}
};
var session = new ApplePaySession(4, applePayPaymentRequest);
session.onvalidatemerchant = (event) => {
paypal.applePay.validateMerchant({
displayName: "Merchant Brand Name",
validationUrl: event.validationURL
}, (err, merchantSession) => {
if (err) {
// You should show an error to the user, e.g. 'Apple Pay failed to load.'
return;
}
session.completeMerchantValidation(merchantSession);
});
};
session.onshippingcontactselected = (event) => {
console.log('Your shipping contact is:', event.shippingContact);
// Update payment details.
var shippingContactUpdate = {...} // https://developer.apple.com/documentation/apple_pay_on_the_web/applepayshippingcontactupdate
session.completeShippingContactSelection(shippingContactUpdate); // Set shippingContactUpdate=null if there are no updates.
};
session.onshippingmethodselected = (event) => {
console.log('Your shipping method is:', event.shippingMethod);
// Update payment details.
var shippingMethodUpdate = {...} // https://developer.apple.com/documentation/apple_pay_on_the_web/applepayshippingmethodupdate
session.completeShippingMethodSelection(shippingMethodUpdate); // Set shippingMethodUpdate=null if there are no updates.
};
session.onpaymentauthorized = (event) => {
console.log('Your billing address is:', event.payment.billingContact);
console.log('Your shipping address is:', event.payment.shippingContact);
fetch("/api/vault/setup-token", {
method: 'post'
body:
// use the "body" param to optionally pass additional order information like
// product ids or amount.
// set `payment_source.apple_pay.stored_credential.payment_type=RECURRING/UNSCHEDULED` in the create setup-token request.
})
.then((res) => res.json())
.then((setupTokenData) => {
var setupTokenId = setupTokenData.id;
paypal.applePay.confirmSetupToken({
setupTokenId: setupTokenId,
token: event.payment.token,
billingContact: {
givenName: "John"
familyName: "Doe",
emailAddress: "john.doe@example.com",
phoneNumber: "18882211161",
addressLines: [
"123 Townsend S",
"Floor 6"
],
locality: "San Francisco",
administrativeArea: "CA",
postalCode: "94107",
countryCode: "US"
} // event.payment.billingContact or provide billing address collected on merchant page.
}, (confirmError, confirmResult) => {
if (confirmError) {
console.error('Error confirming setup token with applepay token:', confirmError);
session.completePayment(ApplePaySession.STATUS_FAILURE);
return;
}
session.completePayment(ApplePaySession.STATUS_SUCCESS);
//Submit approval to the server and create vault payment-token.
fetch(`/your-server/submit-approval/${setupTokenId}`, {
method: "post",
})
.then((res) => res.json()) // vault id or payment-token id will be present in `id` parameter in the response.
.then((data) => {
console.log(data);
})
.catch((err) => console.error(err));
});
});
};
// Open Apple sheet.
session.begin();
}
</script>
```