How to adapt your web-based payment app to Web Payments and provide a better user experience for customers.
Once a web-based payment app receives a payment request and initiates a payment transaction, the service worker will act as the hub for communication between the merchant and the payment app. This post explains how a payment app can pass information about the payment method, shipping address, or contact information to the merchant using a service worker.
Inform the merchant of a payment method change
Payment apps can support multiple payment instruments with different payment methods.
Customer | Payment Method | Payment Instrument |
---|---|---|
A | Credit Card Issuer 1 | ****1234 |
Credit Card Issuer 1 | ****4242 |
|
Bank X | ******123 |
|
B | Credit Card Issuer 2 | ****5678 |
Bank X | ******456 |
For example, in the above table, Customer A's web-based wallet has two credit
cards and one bank account registered. In this case, the app is handling three
payment instruments (****1234
, ****4242
, ******123
) and two payment
methods (Credit Card Issuer 1 and Bank X). On a payment transaction, the payment
app can let the customer pick one of the payment instruments and use it to pay
for the merchant.
The payment app can let the merchant know which payment method the customer picked before sending the full payment response. This is useful when the merchant wants to run a discount campaign for a specific payment method brand, for example.
With the Payment Handler API, the payment app can send a "payment method change"
event to the merchant via a service worker to notify the new payment method
identifier. The service worker should invoke
PaymentRequestEvent.changePaymentMethod()
with the new payment method
information.
Payment apps can pass a methodDetails
object as the optional second argument
for PaymentRequestEvent.changePaymentMethod()
. This object can contain
arbitrary payment method details required for the merchant to process the change
event.
[payment handler] service-worker.js
…
// Received a message from the frontend
self.addEventListener('message', async e => {
let details;
try {
switch (e.data.type) {
…
case 'PAYMENT_METHOD_CHANGED':
const newMethod = e.data.paymentMethod;
const newDetails = e.data.methodDetails;
// Redact or check that no sensitive information is passed in
// `newDetails`.
// Notify the merchant of the payment method change
details =
await payment_request_event.changePaymentMethod(newMethod, newDetails);
…
When the merchant receives a paymentmethodchange
event from the Payment
Request API, they can update the payment details and respond with a
PaymentDetailsUpdate
object.
[merchant]
request.addEventListener('paymentmethodchange', e => {
if (e.methodName === 'another-pay') {
// Apply $10 discount for example.
const discount = {
label: 'special discount',
amount: {
currency: 'USD',
// The value being string complies the spec
value: '-10.00'
}
};
let total = 0;
details.displayItems.push(discount);
for (let item of details.displayItems) {
total += parseFloat(item.amount.value);
}
// Convert the number back to string
details.total.amount.value = total.toString();
}
// Pass a promise to `updateWith()` and send updated payment details
e.updateWith(details);
});
When the merchant responds, the promise that
PaymentRequestEvent.changePaymentMethod()
returned will resolve with a
PaymentRequestDetailsUpdate
object.
[payment handler] service-worker.js
…
// Notify the merchant of the payment method change
details = await payment_request_event.changePaymentMethod(newMethod, newDetails);
// Provided the new payment details,
// send a message back to the frontend to update the UI
postMessage('UPDATE_REQUEST', details);
break;
…
Use the object to update the UI on the frontend. See Reflect the updated payment details.
Inform the merchant of a shipping address change
Payment apps can provide a customer's shipping address to the merchant as part of a payment transaction.
This is useful for merchants because they can delegate the address collection to payment apps. And, because the address data will be provided in the standard data format, the merchant can expect to receive shipping addresses in consistent structure.
Additionally, customers can register their address information with their preferred payment app and reuse it for different merchants.
Payment apps can provide a UI to edit a shipping address or to select pre-registered address information for the customer on a payment transaction. When a shipping address is determined temporarily, the payment app can let the merchant know of the redacted address information. This provides merchants with multiple benefits:
- A merchant can determine whether the customer meets the regional restriction to ship the item (for example, domestic only).
- A merchant can change the list of shipping options based on the region of the shipping address (For example, international regular or express).
- A merchant can apply the new shipping cost based on the address and update the total price.
With the Payment Handler API, the payment app can send a "shipping address
change" event to the merchant from the service worker to notify the new shipping
address. The service worker should invoke
PaymentRequestEvent.changeShippingAddress()
with the new address
object.
[payment handler] service-worker.js
...
// Received a message from the frontend
self.addEventListener('message', async e => {
let details;
try {
switch (e.data.type) {
…
case 'SHIPPING_ADDRESS_CHANGED':
const newAddress = e.data.shippingAddress;
details =
await payment_request_event.changeShippingAddress(newAddress);
…
The merchant will receive a shippingaddresschange
event from the Payment
Request API so they can respond with the updated PaymentDetailsUpdate
.
[merchant]
request.addEventListener('shippingaddresschange', e => {
// Read the updated shipping address and update the request.
const addr = request.shippingAddress;
const details = getPaymentDetailsFromShippingAddress(addr);
// `updateWith()` sends back updated payment details
e.updateWith(details);
});
When the merchant responds, the promise
PaymentRequestEvent.changeShippingAddress()
returned will resolve with a
PaymentRequestDetailsUpdate
object.
[payment handler] service-worker.js
…
// Notify the merchant of the shipping address change
details = await payment_request_event.changeShippingAddress(newAddress);
// Provided the new payment details,
// send a message back to the frontend to update the UI
postMessage('UPDATE_REQUEST', details);
break;
…
Use the object to update the UI on the frontend. See Reflect the updated payment details.
Inform the merchant of a shipping option change
Shipping options are delivery methods merchants use to ship purchased items to a customer. Typical shipping options include:
- Free shipping
- Express shipping
- International shipping
- Premium international shipping
Each comes with its own cost. Usually faster methods/options are more expensive.
Merchants using the Payment Request API can delegate this selection to a payment app. The payment app can use the information to construct a UI and let the customer pick a shipping option.
The list of shipping options specified in the merchant's Payment Request API is
propagated to the payment app's service worker as a property of
PaymentRequestEvent
.
[merchant]
const request = new PaymentRequest([{
supportedMethods: 'https://bobbucks.dev/pay',
data: { transactionId: '****' }
}], {
displayItems: [{
label: 'Anvil L/S Crew Neck - Grey M x1',
amount: { currency: 'USD', value: '22.15' }
}],
shippingOptions: [{
id: 'standard',
label: 'Standard',
amount: { value: '0.00', currency: 'USD' },
selected: true
}, {
id: 'express',
label: 'Express',
amount: { value: '5.00', currency: 'USD' }
}],
total: {
label: 'Total due',
amount: { currency: 'USD', value : '22.15' }
}
}, { requestShipping: true });
The payment app can let the merchant know which shipping option the customer picked. This is important for both the merchant and the customer because changing the shipping option changes the total price as well. The merchant needs to be informed of the latest price for the payment verification later and the customer also needs to be aware of the change.
With the Payment Handler API, the payment app can send a "shipping option
change" event to the merchant from the service worker. The service worker should
invoke
PaymentRequestEvent.changeShippingOption()
with the new shipping option ID.
[payment handler] service-worker.js
…
// Received a message from the frontend
self.addEventListener('message', async e => {
let details;
try {
switch (e.data.type) {
…
case 'SHIPPING_OPTION_CHANGED':
const newOption = e.data.shippingOptionId;
details =
await payment_request_event.changeShippingOption(newOption);
…
The merchant will receive a shippingoptionchange
event from the Payment
Request API. The merchant should use the information to update the total price
and then respond with the updated
PaymentDetailsUpdate
.
[merchant]
request.addEventListener('shippingoptionchange', e => {
// selected shipping option
const shippingOption = request.shippingOption;
const newTotal = {
currency: 'USD',
label: 'Total due',
value: calculateNewTotal(shippingOption),
};
// `updateWith()` sends back updated payment details
e.updateWith({ total: newTotal });
});
When the merchant responds, the promise that
PaymentRequestEvent.changeShippingOption()
returned will resolve with a
PaymentRequestDetailsUpdate
object.
[payment handler] service-worker.js
…
// Notify the merchant of the shipping option change
details = await payment_request_event.changeShippingOption(newOption);
// Provided the new payment details,
// send a message back to the frontend to update the UI
postMessage('UPDATE_REQUEST', details);
break;
…
Use the object to update the UI on the frontend. See Reflect the updated payment details.
Reflect the updated payment details
Once the merchant finishes updating the payment details, the promises returned
from .changePaymentMethod()
, .changeShippingAddress()
and
.changeShippingOption()
will resolve with a common
PaymentRequestDetailsUpdate
object. The payment handler can use the result to reflect updated total price
and shipping options to the UI.
Merchants may return errors for a few reasons:
- The payment method is not acceptable.
- The shipping address is outside of their supported regions.
- The shipping address contains invalid information.
- The shipping option is not selectable for the provided shipping address or some other reason.
Use the following properties to reflect the error status:
error
: Human readable error string. This is the best string to display to customers.shippingAddressErrors
:AddressErrors
object that contains in-detail error string per address property. This is useful if you want to open a form that lets the customer edit their address and you need to point them directly to the invalid fields.paymentMethodErrors
: Payment-method-specific error object. You can ask merchants to provide a structured error, but the Web Payments spec authors recommend keeping it a simple string.
Sample code
Most of sample codes you saw in this document were excerpts from the following working sample app:
https://paymenthandler-demo.glitch.me
[payment handler] service worker
[payment handler] frontend
To try it out:
- Go to https://paymentrequest-demo.glitch.me/.
- Go to the bottom of the page.
- Press Add a payment button.
- Enter
https://paymenthandler-demo.glitch.me
to the Payment Method Identifier field. - Press Pay button next to the field.