The Payment Handler API provides a standardized set of functionality for web applications to directly handle payments, rather than having to be redirected to a separate site for payment handling.
When a merchant website initiates payment via the Payment Request API, the Payment Handler API handles discovery of applicable payment apps, presenting them as choices to the user, opening a payment handler window once a choice has been made to allow the user to enter their payment details, and handling the payment transaction with the payment app.
Communication with payment apps (authorization, passing of payment credentials) is handled via Service Workers.
Concepts and usage
On a merchant website, a payment request is initiated by the construction of a new PaymentRequest
object:
const request = new PaymentRequest(
[
{
supportedMethods: "https://bobbucks.dev/pay",
},
],
{
total: {
label: "total",
amount: { value: "10", currency: "USD" },
},
},
);
The supportedMethods
property specifies a URL representing the payment method supported by the merchant. To use more than one payment method, you would specify them in an array of objects, like this:
const request = new PaymentRequest(
[
{
supportedMethods: "https://alicebucks.dev/pay",
},
{
supportedMethods: "https://bobbucks.dev/pay",
},
],
{
total: {
label: "total",
amount: { value: "10", currency: "USD" },
},
},
);
Making payment apps available
In supporting browsers, the process starts by requesting a payment method manifest file from each URL. A payment method manifest is typically called something like payment-manifest.json
(the exact name can be whatever you like), and should be structured like this:
{
"default_applications": ["https://bobbucks.dev/manifest.json"],
"supported_origins": ["https://alicepay.friendsofalice.example"]
}
Given a payment method identifier like https://bobbucks.dev/pay
, the browser:
- Starts loading
https://bobbucks.dev/pay
and checks its HTTP headers. - If a
Link
header is found with rel="payment-method-manifest"
, then it downloads the payment method manifest at that location instead (see Optionally route the browser to find the payment method manifest in another location for details). - Otherwise, parse the response body of
https://bobbucks.dev/pay
as the payment method manifest.
- Parses the downloaded content as JSON with
default_applications
and supported_origins
members.
These members have the following purposes:
-
default_applications
tells the browser where to find the default payment app that can use the BobBucks payment method if it doesn't already have one installed. -
supported_origins
tells the browser what other payment apps are permitted to handle the BobBucks payment if needed. If they are already installed on the device, they will be presented to the user as alternative payment options alongside the default application.
From the payment method manifest, the browser gets the URL of the default payment apps' web app manifest files, which can be called whatever you like, and look something like this:
{
"name": "Pay with BobBucks",
"short_name": "BobBucks",
"description": "This is an example of the Payment Handler API.",
"icons": [
{
"src": "images/manifest/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "images/manifest/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"serviceworker": {
"src": "service-worker.js",
"scope": "/",
"use_cache": false
},
"start_url": "/",
"display": "standalone",
"theme_color": "#3f51b5",
"background_color": "#3f51b5",
"related_applications": [
{
"platform": "play",
"id": "com.example.android.samplepay",
"min_version": "1",
"fingerprints": [
{
"type": "sha256_cert",
"value": "4C:FC:14:C6:97:DE:66:4E:66:97:50:C0:24:CE:5F:27:00:92:EE:F3:7F:18:B3:DA:77:66:84:CD:9D:E9:D2:CB"
}
]
}
]
}
When the PaymentRequest.show()
method is invoked by the merchant app in response to a user gesture, the browser uses the name
and icons
information found in each manifest to present the payment apps to the user in the browser-provided Payment Request UI.
- If there are multiple payment app options, a list of options is presented to the user for them to choose from. Selecting a payment app will start the payment flow, which causes the browser to Just-In-Time (JIT) install the web app if necessary, registering the service worker specified in the
serviceworker
member so it can handle the payment. - If there is only one payment app option, the
PaymentRequest.show()
method will start the payment flow with this payment app, JIT-installing it if necessary, as described above. This is an optimization to avoid presenting the user with a list that contains only one payment app choice.
Note: If prefer_related_applications
is set to true
in the payment app manifest, the browser will launch the platform-specific payment app specified in related_applications
to handle the payment (if it is available) instead of the web payment app.
See Serve a web app manifest for more details.
Checking whether the payment app is ready to pay with
The Payment Request API's PaymentRequest.canMakePayment()
method returns true
if a payment app is available on the customer's device, meaning that a payment app that supports the payment method is discovered, and that the platform-specific payment app is installed, or the web-based payment app is ready to be registered.
async function checkCanMakePayment() {
const canMakePayment = await request.canMakePayment();
if (!canMakePayment) {
}
}
The Payment Handler API adds an additional mechanism to prepare for handling a payment. The canmakepayment
event is fired on a payment app's service worker to check whether it is ready to handle a payment. Specifically, it is fired when the merchant website calls new PaymentRequest()
. The service worker can then use the CanMakePaymentEvent.respondWith()
method to respond appropriately:
self.addEventListener("canmakepayment", (e) => {
e.respondWith(
new Promise((resolve, reject) => {
someAppSpecificLogic()
.then((result) => {
resolve(result);
})
.catch((error) => {
reject(error);
});
}),
);
});
The promise returned by respondWith()
resolves with a boolean to signal that it is ready to handle a payment request (true
), or not (false
).
Handling the payment
After the PaymentRequest.show()
method is invoked, a paymentrequest
event is fired on the service worker of the payment app. This event is listened for inside the payment app's service worker to begin the next stage of the payment process.
let payment_request_event;
let resolver;
let client;
self.addEventListener("paymentrequest", async (e) => {
if (payment_request_event) {
resolver.reject();
}
payment_request_event = e;
});
When a paymentrequest
event is received, the payment app can open a payment handler window by calling PaymentRequestEvent.openWindow()
. The payment handler window will present the customers with a payment app interface where they can authenticate, choose shipping address and options, and authorize the payment.
When the payment has been handled, PaymentRequestEvent.respondWith()
is used to pass the payment result back to the merchant website.
See Receive a payment request event from the merchant for more details of this stage.
Managing payment app functionality
Once a payment app service worker is registered, you can use the service worker's PaymentManager
instance (accessed via ServiceWorkerRegistration.paymentManager
) to manage various aspects of the payment app's functionality.
For example:
navigator.serviceWorker.register("serviceworker.js").then((registration) => {
registration.paymentManager.userHint = "Card number should be 16 digits";
registration.paymentManager
.enableDelegations(["shippingAddress", "payerName"])
.then(() => {
});
});
-
PaymentManager.userHint
is used to provide a hint for the browser to display along with the payment app's name and icon in the Payment Handler UI. -
PaymentManager.enableDelegations()
is used to delegate responsibility for providing various parts of the required payment information to the payment app rather than collecting it from the browser (for example, via autofill).
Interfaces
CanMakePaymentEvent
-
The event object for the canmakepayment
event, fired on a payment app's service worker when it has been successfully registered to signal that it is ready to handle payments.
PaymentManager
-
Used to manage various aspects of payment app functionality. Accessed via the ServiceWorkerRegistration.paymentManager
property.
-
PaymentRequestEvent
Experimental
-
The event object for the paymentrequest
event, fired on a payment app's service worker when a payment flow has been initiated on the merchant website via the PaymentRequest.show()
method.
Extensions to other interfaces
-
canmakepayment
event -
Fired on a payment app's ServiceWorkerGlobalScope
when it has been successfully registered, to signal that it is ready to handle payments.
-
paymentrequest
event -
Fired on a payment app's ServiceWorkerGlobalScope
when a payment flow has been initiated on the merchant website via the PaymentRequest.show()
method.
ServiceWorkerRegistration.paymentManager
-
Returns a payment app's PaymentManager
instance, which is used to manage various payment app functionality.
Specifications
Browser compatibility
|
Desktop |
Mobile |
|
Chrome |
Edge |
Firefox |
Internet Explorer |
Opera |
Safari |
WebView Android |
Chrome Android |
Firefox for Android |
Opera Android |
Safari on IOS |
Samsung Internet |
PaymentRequestEvent |
70 |
79 |
No |
No |
57 |
No |
No |
70 |
No |
49 |
No |
10.0 |
Payment_Handler_API |
70 |
79 |
No |
No |
57 |
No |
No |
70 |
No |
49 |
No |
10.0 |
changePaymentMethod |
76 |
79 |
No |
No |
63 |
No |
No |
76 |
No |
54 |
No |
12.0 |
changeShippingAddress |
80 |
80 |
No |
No |
67 |
No |
No |
80 |
No |
57 |
No |
13.0 |
changeShippingOption |
80 |
80 |
No |
No |
67 |
No |
No |
80 |
No |
57 |
No |
13.0 |
instrumentKey |
70–111 |
79–111 |
No |
No |
57–97 |
No |
No |
70–111 |
No |
49 |
No |
10.0–22.0 |
methodData |
70 |
79 |
No |
No |
57 |
No |
No |
70 |
No |
49 |
No |
10.0 |
modifiers |
70 |
79 |
No |
No |
57 |
No |
No |
70 |
No |
49 |
No |
10.0 |
openWindow |
70 |
79 |
No |
No |
57 |
No |
No |
70 |
No |
49 |
No |
10.0 |
paymentOptions |
80 |
80 |
No |
No |
67 |
No |
No |
80 |
No |
57 |
No |
13.0 |
paymentRequestId |
70 |
79 |
No |
No |
57 |
No |
No |
70 |
No |
49 |
No |
10.0 |
paymentRequestOrigin |
70 |
79 |
No |
No |
57 |
No |
No |
70 |
No |
49 |
No |
10.0 |
respondWith |
70 |
79 |
No |
No |
57 |
No |
No |
70 |
No |
49 |
No |
10.0 |
shippingOptions |
80 |
80 |
No |
No |
67 |
No |
No |
80 |
No |
57 |
No |
13.0 |
topOrigin |
70 |
79 |
No |
No |
57 |
No |
No |
70 |
No |
49 |
No |
10.0 |
total |
70 |
79 |
No |
No |
57 |
No |
No |
70 |
No |
49 |
No |
10.0 |
See also