Introducing the Payment Request API for Apple Pay

We’re pleased to announce that Safari 11.1 on macOS and Safari on iOS 11.3 now support the W3C Payment Request API for conducting Apple Pay transactions on the web.

We first introduced Apple Pay on the Web in macOS Sierra and iOS 10. Apple Pay brought novel ease-of-use, security, and privacy to online transactions, so we’ve been encouraged to see it widely adopted by merchants in the years since. However, we recognize that merchants need to support multiple payment methods, and doing so comes at the cost of increased complexity. Payment Request aims to reduce this complexity by supporting payment methods across multiple browsers using a standard API.

Today I’ll take you step-by-step through an Apple Pay transaction using Payment Request. If you’ve already worked with Apple Pay JS, this should look familiar to you. If you haven’t, this is a great place to get started!

Prerequisites

This post assumes that you have already registered a merchant identifier and set up your server environment to process Apple Pay transactions. You can find detailed instructions for doing this in the Apple Pay on the Web documentation.

Showing Apple Pay Buttons

Apple Pay transactions are always initiated by your customer, so you’ll need to display an Apple Pay button for them to tap or click. For a consistent visual appearance, WebKit provides built-in support for rendering Apple Pay buttons. Here’s how to display the most basic type of Apple Pay button:

<button style="-webkit-appearance: -apple-pay-button;"></button>

There are several other types of buttons available in various styles. Here’s a few that you might have seen while shopping in Safari:

Apple Pay Buttons

See Displaying Apple Pay Buttons for more information on the button types and styles available.

According to the Apple Pay on the Web Human Interface Guidelines, you should display Apple Pay buttons whenever your customer is using a supported device. To check if your customer’s device supports Apple Pay, call ApplePaySession.canMakePayments():

if (window.ApplePaySession && ApplePaySession.canMakePayments())
    // Show Apple Pay button.

If your customer taps or clicks an Apple Pay button, you should always present the Apple Pay payment sheet. If they haven’t yet enrolled a payment card you can accept in Apple Pay, Safari prompts them to do so before continuing with your transaction. Since these rules are specific to Apple Pay, Apple Pay JS’s ApplePaySession API is required for this step. The remainder of the transaction can be conducted using standard Payment Request API.

Constructing a PaymentRequest

When your customer taps or clicks an Apple Pay button, you initiate the transaction by constructing a new PaymentRequest:

var request = null;
if (window.PaymentRequest)
    request = new PaymentRequest(methods, details, options);
else
    // Consider using Apple Pay JS instead.

The PaymentRequest constructor takes three arguments: the payment methods you support, the details to show to your customer (like shipping options and total amount), and any options you require.

Payment Methods

Payment methods represent the means by which you can accept payments from your customer using Payment Request. You specify the payment methods you accept as a sequence of PaymentMethodData dictionaries, each of which contains an identifier (supportedMethods) and associated data.

To use Apple Pay with Payment Request, include it as a payment method. Apple Pay’s URL-based payment method identifier is "https://apple.com/apple-pay", and its associated data is an ApplePayRequest dictionary. Here’s what a PaymentMethodData dictionary for Apple Pay might look like:

const applePayMethod = {
    supportedMethods: "https://apple.com/apple-pay",
    data: {
        version: 3,
        merchantIdentifier: "merchant.com.example",
        merchantCapabilities: ["supports3DS", "supportsCredit", "supportsDebit"],
        supportedNetworks: ["amex", "discover", "masterCard", "visa"],
        countryCode: "US",
    },
};

Safari only supports the Apple Pay payment method, but other browsers might support additional payment methods. When you specify multiple payment methods in a request, the browser decides which to present to your customer based on device availability and user preference.

Payment Details

Payment details are represented by the PaymentDetailsInit dictionary. It contains your transaction’s total amount, display items, shipping options, and payment method-specific modifiers (more on modifiers below). Here’s what valid payment details might look like for a $20 item plus tax and two shipping options:

const paymentDetails = {
    total: {
        label: "My Merchant",
        amount: { value: "27.50", currency: "USD" },
    },
    displayItems: [{
        label: "Tax",
        amount: { value: "2.50", currency: "USD" },
    }, {
        label: "Ground Shipping",
        amount: { value: "5.00", currency: "USD" },
    }],
    shippingOptions: [{
        id: "ground",
        label: "Ground Shipping",
        amount: { value: "5.00", currency: "USD" },
        selected: true,
    }, {
        id: "express",
        label: "Express Shipping",
        amount: { value: "10.00", currency: "USD" },
    }],
};

You can choose a default shipping option by setting the selected attribute to true as we did above for "Ground Shipping". The total amount must not be negative, and when using Apple Pay, all payment amounts in your request must use the same ISO 4217 currency code. It is up to you to ensure the correctness of your payment details; Safari does not do any currency calculations on your behalf.

What about modifiers?

You can optionally include a sequence of modifiers in your payment details. Modifiers update your transaction’s display items and total when criteria you specify for a given payment method are satisfied. In Apple Pay, you can use modifiers to adjust the price based on the type of payment card selected in the Apple Pay payment sheet. For instance, the following modifier applies a $1.00 discount when your customer selects a debit card in Apple Pay:

const debitModifier = {
    supportedMethods: "https://apple.com/apple-pay",
    data: { paymentMethodType: "debit" },
    total: {
        label: "My Merchant",
        amount: { value: "26.50", currency: "USD" },
    },
    additionalDisplayItems: [{
        label: "Debit Card Discount",
        amount: { value: "-1.00", currency: "USD" },
    }],
};

Modifiers provide some of the functionality present in the paymentmethodselected event from Apple Pay JS. See ApplePayModifier for more information.

Payment Options

Payment options are represented by the PaymentOptions dictionary. If you need to request your customer’s name, email address, or phone number – or request a certain type of shipping – you can do so here:

const paymentOptions = {
    requestPayerName: true,
    requestPayerEmail: true,
    requestPayerPhone: true,
    requestShipping: true,
    shippingType: "shipping",
};

If you set requestShipping to true, the shipping options you specified in Payment Details are presented in the payment sheet for your customer to choose between. You receive the requested information once your customer authorizes payment.

Exceptions

Safari might raise an exception when constructing a new PaymentRequest. Exceptions can occur for the following reasons:

  • The frame is not in a secure context.
  • The frame is a cross-origin subframe.
  • No payment methods were specified.
  • A payment method identifier is invalid.
  • Calling JSON.stringify() on the payment method data failed.
  • Invalid currency amounts were specified (e.g., negative total or multiple currencies).

canMakePayment() method

Once you’ve constructed a PaymentRequest, you can ask it if your customer will be able to authorize a transaction given the payment methods you can accept. You do this by calling the canMakePayment() method, which returns a promise that resolves to either true or false. In Safari, when Apple Pay is one of the payment methods, canMakePayment() resolves to true only if your customer has an active card enrolled in Apple Pay. This is the equivalent of how ApplePaySession.canMakePaymentsWithActiveCard() behaves in Apple Pay JS.

As we discussed in Showing Apple Pay Buttons, the Apple Pay Human Interface Guidelines require you to show an Apple Pay button whenever your customer is on supported hardware, whether or not they have an active card enrolled. Therefore, you should not hide Apple Pay buttons when canMakePayment() resolves to false. Always show an Apple Pay button if ApplePaySession.canMakePayments() returns true, and always present the Apple Pay payment sheet when your customer taps or clicks the button. Safari prompts your customer to enroll a payment card if they haven’t done so already before continuing with your transaction. For more information, see Human Interface Guidelines > Apple Pay on the Web.

show() method

When your customer taps or clicks an Apple Pay button, you should present the Apple Pay payment sheet. You do this by calling the show() method, which returns a promise that resolves to a PaymentResponse once your customer authorizes payment. The promise is rejected with an AbortError if your customer cancels the transaction.

You can optionally call show() with a promise for a PaymentDetailsUpdate. Sometimes you might still be in the process of calculating payment details when your customer taps or clicks the Apple Pay button. In this case, you can construct a new PaymentRequest with placeholders for details, then call show() with a promise to provide up-to-date details later. When you resolve this promise, Safari displays the updated details in the Apple Pay payment sheet.

Safari might reject the promise returned by show() with an exception. Exceptions can occur for the following reasons:

  • show() was not triggered by user activation (e.g., a tap or click).
  • The request has already been aborted.
  • An Apple Pay session is already active.
  • Payment method data is invalid (e.g., is missing required fields).

abort() method

If you need to abort the presented transaction, you can call the abort() method. When you do this, Safari dismisses the Apple Pay payment sheet and rejects the promise returned by show() with an AbortError. If the transaction has already been aborted, or show() has not yet been called, calling abort() throws an InvalidStateError.

Merchant Validation

Before Safari can present the Apple Pay payment sheet, you must acquire a payment session from Apple. This process is referred to as merchant validation.

Soon after you call show(), Safari dispatches the merchantvalidation event to your PaymentRequest object. The event defines a validationURL attribute representing the Apple URL your server contacts to receive a payment session. You must call the event’s complete() method with a promise that you resolve with this payment session once you receive it.

Here is what a merchantvalidation event handler might look like:

request.onmerchantvalidation = function (event) {
    const sessionPromise = fetchPaymentSession(event.validationURL);
    event.complete(sessionPromise);
};

You can learn more about merchant validation from Requesting an Apple Pay Payment Session.

Shipping Events

Once you’ve received a merchant session, Safari presents the Apple Pay payment sheet to your customer. If you’ve requested shipping, your customer is able to select between your shipping options and provide a shipping address. When they make these selections in the payment sheet, Safari dispatches a shippingoptionchange or shippingaddresschange event to your PaymentRequest object.

shippingoptionchange

When the user selects a shipping option, Safari dispatches the shippingoptionchange event. In your event handler, you can determine the selected shipping option by checking the PaymentRequest‘s shippingOption attribute. To update the payment details based on the selected shipping option, call updateWith() on the event object with a promise that resolves to a PaymentDetailsUpdate.

When requesting shipping with Apple Pay, you must always listen for shippingoptionchange and call updateWith() with a promise that resolves within 30 seconds, otherwise, the transaction will time out.

shippingaddresschange

When your customer selects a shipping address, Safari dispatches the shippingaddresschange event. In your event handler, you can determine the selected shipping address by checking the PaymentRequest‘s shippingAddress attribute. To update the payment details based on the selected shipping address, call updateWith() on the event object with a promise that resolves to a PaymentDetailsUpdate. If you are unable to ship to the selected address, you can provide an error message in your PaymentDetailsUpdate that Safari displays to your customer.

When using Apple Pay, Safari might redact some details from the shipping address. For instance, in the United States, only city, state, 5-digit ZIP code, and country are provided. Safari provides the full, un-redacted shipping address once your customer authorizes payment.

When requesting shipping with Apple Pay, you must always listen for shippingaddresschange and call updateWith() with a promise that resolves within 30 seconds, otherwise the transaction will time out.

Handling Payment Authorization

When your customer authorizes payment, Safari resolves the promise you received from calling show() with a PaymentResponse. Depending on what you requested in your PaymentOptions, the response might contain the selected shipping option, shipping address, name, email, and phone number of your customer.

The response also contains the payment method identifier used to conduct the transaction (methodName), along with its associated details. When Apple Pay is the selected payment method, the associated details is an ApplePayPayment dictionary. ApplePayPayment contains the Apple Pay token you use to process the payment authorization. It also includes your customer’s billing and shipping contact information as ApplePayPaymentContacts if you required this in your ApplePayRequest.

When you have finished processing the payment authorization, you call the complete() method on PaymentResponse to indicate the result of your processing. You can call complete() with a status of "success" or "failure". At this point, the Apple Pay payment sheet is dismissed.

Demo

You now have all the pieces you need to conduct an Apple Pay transaction using Payment Request. Here’s what an Apple Pay session might look like using Payment Request:

async function applePayButtonClicked()
{
    // Consider falling back to Apple Pay JS if Payment Request is not available.
    if (!window.PaymentRequest)
        return;

    try {
        const request = new PaymentRequest([applePayMethod], paymentDetails, paymentOptions);

        request.onmerchantvalidation = function (event) {
            // Have your server fetch a payment session from event.validationURL.
            const sessionPromise = fetchPaymentSession(event.validationURL);
            event.complete(sessionPromise);
        };

        request.onshippingoptionchange = function (event) {
            // Compute new payment details based on the selected shipping option.
            const detailsUpdatePromise = computeDetails();
            event.updateWith(detailsUpdatePromise);
        };

        request.onshippingaddresschange = function (event) {
            // Compute new payment details based on the selected shipping address.
            const detailsUpdatePromise = computeDetails();
            event.updateWith(detailsUpdatePromise);
        };

        const response = await request.show();
        const status = processResponse(response);
        response.complete(status);
    } catch (e) {
        // Handle errors
    }
}

Let’s see how this works in a live demo. If you are viewing this post on a device capable of Apple Pay, you should see an Apple Pay button below. Feel free to click it! Don’t worry, no matter what you do in the payment sheet, your card won’t be charged anything.

Availability

The Payment Request API is available in Safari 11.1 on macOS Sierra and macOS High Sierra, Safari on iOS 11.3, and Safari Technology Preview.

More Information

Apple provides comprehensive documentation for Apple Pay on the Web. Here are a few links you might find useful:

Feedback

We’d love to hear your feedback! If you find a Payment Request bug, please report it at bugs.webkit.org. On Twitter, you can reach the WebKit team at @webkit, or our web technologies evangelist Jonathan Davis at @jonathandavis.