Customization with Apple Pay
The Apple Pay direct API integration will let you offer the payment method button to the customer for authorization. However, you can extend your code to customize the Apple Pay payment sheet for additional operations. For example, the customer need not register in the shop for express checkout and guest checkout options. Instead, they can fulfil their purchase using the data stored on their Apple devices. Add Apple Pay to your item or product pages, and enable quick checkout for your customers. This reduces the number of checkout steps and increases conversion rates on single-item purchases.
When you offer Apple Pay to your cart or product page, you can offer the customer the following improvements:
- Provide a business name after the word Pay on the same line as the total.
- A convenient view of all items in the cart includes additional charges, discounts, pending costs and add-on donations.
- Gathering of billing and shipping information from the payment sheet.
- Letting customers choose the shipping method in the payment sheet.
All these customizations are facilitated by using the order object in your payment intent request.
Further, you can set up Apple Pay buttons based on your business needs, desired button types, colours and display requirements. The button customizations are arranged by using the button object in your payment intent request.
Setup the business name
If you want to setup your merchant name or business name to be rendered in the payment sheet, you can use the order.merchantName property. Use the same business name people will see when they look for the charge on their bank or credit card statement. This provides reassurance that payment is going to the right place. For this, set up the transaction.dynamic_descriptor parameter during the booking when using the /payment API (or /authorize API).
Suppose the order.merchantName property is missing in the payment intent, Novalnet, by default, will show the domain name in which the Apple Pay processing takes place, Eg:- for this page, it is developer.novalnet.de.
Display the line items
You can set the order.lineItems property to show a detailed summary of the order line items in your payment intent request. As per the Apple recommendation, you can group all purchased items into one "subtotal" item and add additional items for shipping, tax, discounts, etc., as needed.
Provide the total separately, in the transaction.amount property. Line items aren't required in the API request but can't be empty if they're present.
{
lineItems: [
{
label: "Bag Subtotal",
type: "LINE_ITEM",
amount: 1500
},
{
label: "Free Shipping",
type: "LINE_ITEM",
amount: 0
},
{
label: "Estimated Tax",
type: "TAX",
amount: 300
}
]
}Collect the billing data of the customer
If you specify the order.billing.requiredFields property in your payment intent request, Apple will prompt the user to enter or choose their billing address in the payment sheet. Depending upon your customer account creation, you can customize the requiredFields property and require it only if it is essential. The entered billing data will then be available on the authorization response order.billing.contact property passed to the onProcessCompletion callback.
billing: {
requiredFields: ["postalAddress"]
}Process the shipping-related information
Unlike billing contact, gathering shipping information may trigger a few changes in the payment request and handling. For instance, the customer, after entering the shipping address in the payment sheet, the information needs to be validated if you could cover the delivery to those regions or could enforce a tax change which could change the total altogether. Owing to these requirements, processing the shipping information also might require including additional callbacks onShippingContactChange and onShippingMethodChange.
Collect the shipping data of the customer
In the same way, like gathering the billing information, you can also require shipping related data in your payment intent request. Apple will prompt the user to enter or choose their shipping address in the payment sheet. The entered shipping data will then be available to you on the authorization response order.shipping.contact passed to the onProcessCompletion callback.
shipping: {
requiredFields: ["postalAddress", "phone", "email"]
}The registered callback onShippingContactChange in the payment intent will be called when the customer selects a shipping contact in the payment sheet. The callback allows you to dynamically update shipping methods and transaction information based on the selected shipping address.
The callback function should have two arguments, the first to receive the entered shipping contact and the second to return the updated transaction information to the payment sheet. It is optional to correct the transaction information, depending on your shipping handling.
callbacks: {
"onShippingContactChange": function(shippingContact, newShippingContactResult) {
let transactionInfoToUpdate = {};
// There could be a situation where the shipping methods differ based on region
if (shippingContact.countryCode == "DE") {
transactionInfoToUpdate.methods = [{
identifier: "dhlshipping",
amount: 500,
detail: "The product will be delivered depends on the executive",
label: "DHL Shipping"
}, {
identifier: "freeshipping",
amount: 0,
detail: "Free shipping within Deutschland",
label: "Free Shipping"
}];
} else {
transactionInfoToUpdate.methods = [{
identifier: "expressshipping",
amount: 750,
detail: "The product will be dispatched in the same day",
label: "Express Shipping"
}];
}
// Recalculating the total gross based on the chosen shipping method
transactionInfoToUpdate.amount = transactionInfoToUpdate.methods[0].amount + transactionInformation.amount;
transactionInfoToUpdate.lineItems = [{
label: "Bag Subtotal",
type: "final",
amount: 1500
},
{
label: transactionInfoToUpdate.methods[0].label,
type: "final",
amount: transactionInfoToUpdate.methods[0].amount
},
{
label: "Estimated Tax",
type: "final",
amount: 300
}
];
newShippingContactResult(transactionInfoToUpdate);
}
}Setup the shipping methods offered
You can let your users choose/change by offering various shipping methods in the Apple Pay payment sheet. Make use of the order.shipping.methods property to pass on the shipping methods as shown in the sample code below.
{
methods: [
{
identifier: "freeshipping",
amount: 0,
detail: "Free shipping within Deutschland",
label: "Free Shipping"
},
{
identifier: "dhlshipping",
amount: 500,
detail: "The product will be delivered depends on the executive",
label: "DHL Shipping"
}
]
}The registered callback onShippingMethodChange in the payment intent will be called when the customer changes a shipping method in the payment sheet. The callback allows you to update transaction information based on the chosen shipping method dynamically.
The callback function should have two arguments, the first to receive the chosen shipping method and the second to return the updated transaction information to the payment sheet. It is optional to correct the transaction information, depending on your shipping handling.
The chosen shipping method will then be available to you on the authorization response order.shipping.method passed to the onProcessCompletion callback.
callbacks: {
"onShippingMethodChange": function(shippingMethod, newShippingMethodResult) {
// There could be a situation where the shipping method can alter total
let transactionInfoToUpdate = {};
// Recalculating the total gross based on the chosen shipping method
transactionInfoToUpdate.amount = shippingMethod.amount + transactionInformation.amount;
transactionInfoToUpdate.lineItems = [{
label: "Bag Subtotal",
type: "final",
amount: 1500
},
{
label: shippingMethod.label,
type: "final",
amount: shippingMethod.amount
},
{
label: "Estimated Tax",
type: "final",
amount: 300
}
];
newShippingMethodResult(transactionInfoToUpdate);
}
}Provisioning the coupon or discount information
Also, customers with a coupon code can use it in the Apple payment sheet, by which you can process the adjustments in the total amount accordingly.
In your payment intent request, if you specify the order.couponOptionRequired property, a field to enter the coupon code will appear in the payment sheet, allowing users to enter their coupon code. It is also possible to prefill the coupon code to some orders before displaying the payment sheet. For instance, if you want to apply a mandatory discount for all the users who opted to purchase a specific product, you can prefill the coupon/discount code in the payment sheet using the property order.couponCode.
Once the customer fills in the coupon code or if prefilled in the request, it can be validated using the callback, onCouponCodeChange. The registered callback onCouponCodeChange in the payment intent will be called when the customer enters/edits the coupon information in the payment sheet. The registered callback should have two arguments: one, to receive the entered coupon code and the second, to return the updated transaction information based on the coupon code entered, which includes validating the coupon code. The second argument is a callback function that lets you inform the payment sheet about handling the entered coupon code. You can use the property error of the callback function with values INVALID and EXPIRED for the invalid or expired coupon. If you want to customize the error text in the payment sheet, use the errorText property.
The coupon option will be available on the payment sheet based on the below specifications.
- - For iOS devices: Open Safari on your device with OS - iOS 15 or later.
- - For Mac device: Open Safari on your Mac with OS - macOS 12 or later.
callbacks: {
"onCouponCodeChange": function(couponCode, couponHandledResult) {
let couponInformationToUpdate = {};
// Validating the coupon code
if (couponCode == "INVALIDCOUPON") {
couponInformationToUpdate.error='INVALID';
} else if (couponCode == "EXPIREDCOUPON") {
couponInformationToUpdate.error='EXPIRED';
couponInformationToUpdate.errorText='The coupon code cannot be used anymore as the offer got expired';
} else if (couponCode == "FREESHIPPING") {
couponInformationToUpdate.methods = [{
identifier: "freeshipping",
amount: 0,
detail: "The product will be delivered for free using the discount period",
label: "Free Shipping"
};
// Recalculating the total gross based on the free shipping method
couponInformationToUpdate.amount = couponInformationToUpdate.methods[0].amount + couponInformationToUpdate.amount;
couponInformationToUpdate.lineItems = [{
label: "Bag Subtotal",
type: "final",
amount: 1500
},
{
label: couponInformationToUpdate.methods[0].label,
type: "final",
amount: couponInformationToUpdate.methods[0].amount
},
{
label: "Estimated Tax",
type: "final",
amount: 300
}
];
} else if (couponCode == "10_CENT_DISCOUNT") {
// Recalculating the total gross based on the coupon code used
couponInformationToUpdate.amount = couponInformationToUpdate.amount - 10;
couponInformationToUpdate.lineItems = [{
label: "Bag Subtotal",
type: "final",
amount: 1500
},
{
label: "10 Cent Discount Offer",
type: "final",
amount: -10
},
{
label: "Estimated Tax",
type: "final",
amount: 300
}
];
} else {
couponInformationToUpdate.error='INVALID';
couponInformationToUpdate.errorText='The coupon code entered seems to be invalid';
}
couponHandledResult(couponInformationToUpdate);
}
}Customize the payment button
Styling your payment button involves changing the button texts based on the customer locale to set up the colour, type, and dimensions. As a best practice, follow Apple's brand guidelines to customize the payment button accordingly.
Payment sheet customization request object and response sample
The entire customization request with the wallet and button property of the payment intent request is listed below. Beneath that, the example will contain a complete customization request for Apple Pay.
Each parameter is marked with attributes Mandatory, Conditional , Optional . Based on your necessity, you can use these parameters accordingly.
// Setting up the Payment Intent for authentication and payment button processing
var merchantInformation = {
countryCode: "DE",
};
var transactionInformation = {
amount: 1850,
currency: "EUR",
paymentMethod: "APPLEPAY",
environment: "SANDBOX"
};
var orderInformation = {
merchantName: "Test Development for Apple Pay",
lineItems: [{
label: "Bag Subtotal",
type: "final",
amount: 1500
},
{
label: "Free Shipping",
type: "final",
amount: 0
},
{
label: "Estimated Tax",
type: "final",
amount: 350
}
],
billing: {
requiredFields: ["postalAddress"]
},
shipping: {
type: "delivery",
requiredFields: ["postalAddress", "phone", "email"],
methods: [
{
identifier: "freeshipping",
amount: 0,
detail: "Free shipping within Deutschland",
label: "Free Shipping"
},
{
identifier: "dhlshipping",
amount: 500,
detail: "The product will be delivered depends on the executive",
label: "DHL Shipping"
}
]
},
couponOptionRequired:true
};
var buttonInformation = {
type: "plain",
style: "black",
locale: "en-US",
boxSizing: "border-box",
dimensions: {
height: 45,
width: 200,
paddingX: 12,
paddingY: 12,
cornerRadius: 10
}
};
var paymentIntent = {
clientKey: ###YOUR_CLIENT_KEY###,
paymentIntent: {
merchant: merchantInformation,
transaction: transactionInformation,
order: orderInformation,
button: buttonInformation,
callbacks: {
"onProcessCompletion": function(payLoad, bookingResult) {
// Handle response here and setup the bookingresult
if (payLoad.result && payLoad.result.status) {
// Only on success, we proceed further with the booking
if (payLoad.result.status == 'SUCCESS') {
// Sending the token and amount to Novalnet server for the booking
$.post('send_server_request.php', {token: payLoad.transaction.token, amount: payLoad.transaction.amount}).done(function(processedResult) {
var parsedResult = $.parseJSON(processedResult);
// "SUCCESS" or "FAILURE"
let result = {};
result.status = payLoad.result.status;
result.statusText = payLoad.result.status_text;
bookingResult(result);
});
} else {
// Upon failure, displaying the error text
if (payLoad.result.status_text) {
alert(payLoad.result.status_text);
}
}
}
},
"onShippingContactChange": function(shippingContact, newShippingContactResult) {
let transactionInfoToUpdate = {};
// There could be a situation where the shipping methods differ based on region
if (shippingContact.countryCode == "DE") {
transactionInfoToUpdate.methods = [{
identifier: "dhlshipping",
amount: 500,
detail: "The product will be delivered depends on the executive",
label: "DHL Shipping"
}, {
identifier: "freeshipping",
amount: 0,
detail: "Free shipping within Deutschland",
label: "Free Shipping"
}];
} else {
transactionInfoToUpdate.methods = [{
identifier: "expressshipping",
amount: 750,
detail: "The product will be dispatched in the same day",
label: "Express Shipping"
}];
}
// Recalculating the total gross based on the chosen shipping method
transactionInfoToUpdate.amount = transactionInfoToUpdate.methods[0].amount + transactionInformation.amount;
transactionInfoToUpdate.lineItems = [{
label: "Bag Subtotal",
type: "final",
amount: 1500
},
{
label: transactionInfoToUpdate.methods[0].label,
type: "final",
amount: transactionInfoToUpdate.methods[0].amount
},
{
label: "Estimated Tax",
type: "final",
amount: 350
}
];
newShippingContactResult(transactionInfoToUpdate);
},
"onShippingMethodChange": function(shippingMethod, newShippingMethodResult) {
// There could be a situation where the shipping method can alter total
let transactionInfoToUpdate = {};
// Recalculating the total gross based on the chosen shipping method
transactionInfoToUpdate.amount = shippingMethod.amount + transactionInformation.amount;
transactionInfoToUpdate.lineItems = [{
label: "Bag Subtotal",
type: "final",
amount: 1500
},
{
label: shippingMethod.label,
type: "final",
amount: shippingMethod.amount
},
{
label: "Estimated Tax",
type: "final",
amount: 350
}
];
newShippingMethodResult(transactionInfoToUpdate);
},
"onCouponCodeChange": function(couponCode, couponHandledResult) {
let couponInformationToUpdate = {};
// Validating the coupon code
if (couponCode == "INVALIDCOUPON") {
couponInformationToUpdate.error='INVALID';
} else if (couponCode == "EXPIREDCOUPON") {
couponInformationToUpdate.error='EXPIRED';
couponInformationToUpdate.errorText='The coupon code cannot be used anymore as the offer got expired';
} else if (couponCode == "FREESHIPPING") {
couponInformationToUpdate.methods = [{
identifier: "freeshipping",
amount: 0,
detail: "The product will be delivered for free using the discount period",
label: "Free Shipping"
};
// Recalculating the total gross based on the free shipping method
couponInformationToUpdate.amount = couponInformationToUpdate.methods[0].amount + couponInformationToUpdate.amount;
couponInformationToUpdate.lineItems = [{
label: "Bag Subtotal",
type: "final",
amount: 1500
},
{
label: couponInformationToUpdate.methods[0].label,
type: "final",
amount: couponInformationToUpdate.methods[0].amount
},
{
label: "Estimated Tax",
type: "final",
amount: 350
}
];
} else if (couponCode == "10_CENT_DISCOUNT") {
// Recalculating the total gross based on the coupon code used
couponInformationToUpdate.amount = couponInformationToUpdate.amount - 10;
couponInformationToUpdate.lineItems = [{
label: "Bag Subtotal",
type: "final",
amount: 1500
},
{
label: "10 Cent Discount Offer",
type: "final",
amount: -10
},
{
label: "Estimated Tax",
type: "final",
amount: 350
}
];
} else {
couponInformationToUpdate.error='INVALID';
couponInformationToUpdate.errorText='The coupon code entered seems to be invalid';
}
couponHandledResult(couponInformationToUpdate);
}
}
}
};
try {
// Loading the payment instances
var NovalnetPaymentInstance = NovalnetPayment();
var applepayNovalnetPaymentObj = NovalnetPaymentInstance.createPaymentObject();
// Setting up the payment intent in your object
applepayNovalnetPaymentObj.setPaymentIntent(paymentIntent);
// Checking for the payment method availability
applepayNovalnetPaymentObj.isPaymentMethodAvailable(function(displayApplePayButton) {
// Initiating the Payment Request for the Wallet Payment
applepayNovalnetPaymentObj.addPaymentButton("#applepay-container");
});
} catch (e) {
// Handling the errors from the payment intent setup
console.log(e.message);
}To try this demo, open this page in Safari.
(See Requirements.)