Take a stroll down memory lane and celebrate #100CoolWebMoments since Chrome's first release.

Frictionless payment with Payment Request API


Welcome to "Frictionless payment with Payment Request API" codelab. In this codelab, you will learn how to implement Payment Request API onto an existing e-commerce website. Let's get started.


What you will need

  • USB cable to connect an Android device to your computer
  • Computer with terminal/shell access
  • Connection to the internet
  • Chrome for Android version 56 or greater
  • A text editor

Get set up

Clone the E-Commerce lab repository's "payment-request-api" branch with Git using the following command:

$ git clone -b payment-request-api https://github.com/google-developer-training/pwa-ecommerce-demo.git

The repository consists of a project folder, a solution folder and others.

  • The project folder is where you will build the app.
  • The solution folder is an example solution you will get by following this codelab.


Navigate into the cloned repo and open the project folder.

$ cd pwa-ecommerce-demo/project

Then run npm install in the command line at the project directory.

$ npm install

This command should create node_modules directory and install all required node modules to your project. It takes a while.

Run npm run serve in the same directory to build and run the server in dist. Any changes you make to JavaScript files in this codelab will trigger rebuilding the app as long as you keep the command running.

$ npm run serve

Open your browser and navigate to http://localhost:8080 to see the initial state of the app working.

At this point, you should be able to see the e-commerce app running. Within this app, you can add items to the cart and proceed to the checkout form. Play with it for a while to understand how the original website works.

17a65e050531bde8.png 59eab635dd82f90.png

From here, you will be implementing the Payment Request API.

The Payment Request API is not yet supported on desktop as of Chrome 58, so you will need an Android device with Chrome installed to test the code. Follow the instructions in the Access Local Servers article to set up port forwarding on your Android device. This lets you host the e-commerce app on your phone.

Create a PaymentRequest

Detect feature availability

First, let's add add a feature detection for the Payment Request API. And if it's available, let a user process payment with it.

Replace "TODO PAY-3.1" in app/scripts/modules/app.js with the following code and remove the placeholder conditional of if (false) { to add PaymentRequest feature detection:


if (window.PaymentRequest) {


The feature detection is as simple as examining if window.PaymentRequest returns undefined or not.

Create a PaymentRequest

Create a Payment Request object using the PaymentRequest constructor.

Replace "TODO PAY-3.2" in app/scripts/modules/payment-api.js with the following code and initialize PaymentRequest:


let request = new PaymentRequest(supportedInstruments, details, paymentOptions);


The constructor takes three parameters.

supportedInstruments: The first argument is a required set of data about supported payment methods. This can include basic credit cards as well as payment processors like Android Pay. We'll use only basic credit cards in this codelab.

details: The second argument is required information about the transaction. This must include the information to display the total to the user (i.e., a label, currency, and value amount), but it can also include a breakdown of items in the transaction.

paymentOptions: The third argument is an optional parameter for things like shipping. This allows you to require additional information from the user, like payer name, phone, email, and shipping information.

Try it out

You should now be able to try the Payment Request API. If you are not running your server, npm run serve and try it using your Android device.

$ npm run serve

The PaymentRequest UI displays when you click Checkout.


Beware, this is only the first step and there is more work to be done. Let's continue...

Add payment methods

At this point, most of the arguments are empty arrays or objects. Let's configure the payment method (supportedInstruments) with proper values.

Replace "TODO PAY-4" and its following JSON block in scripts/modules/payment-api.js with this:


  supportedMethods: ['basic-card'],
  data: {
    supportedNetworks: ['visa', 'mastercard', 'amex',
      'jcb', 'diners', 'discover', 'mir', 'unionpay']


The first argument of the PaymentRequest constructor takes a list of supported payment methods as JSON objects.

supportedMethods takes a list of supported method names as an array. Supported methods can be basic-card or a URL representing a payment app. These are defined in the Payment Method Identifiers specification.

In the case of basic-card, supportedNetworks under data takes a list of supported credit card brands as defined at Card Network Identifiers Approved for use with Payment Request API. This will filter and show only the credit cards available for the user in the Payment Request UI.


Add payment details

Next, let's provide information about items a user is trying to purchase.

Define the details object

The second argument of the PaymentRequest constructor takes details about the purchase. It takes a list of items to display and the total price information.

This portion is already implemented in the buildPaymentDetails() function in app/scripts/modules/payment-api.js. You don't have to do anything at this time, but see what's happening here.


let details = {
  displayItems: displayItems,
  total: {
    label: 'Total due',
    amount: {currency: 'USD', value: String(total)}
  // TODO PAY-7.2 - allow shipping options
return details;


A required total parameter consists of a label, currency and total amount to be charged.

An optional displayItems parameter indicates how the final amount was calculated.

The displayItems parameter is not intended to be a line-item list, but is rather a summary of the order's major components: subtotal, discounts, tax, shipping costs, etc. Let's define it in the next section.

Define the display items

The displayItems variable should be defined based on items added to the cart.

Replace "TODO PAY-5" in app/scripts/modules/payment-api.js with the following code and remove the existing let displayItems = [];:


let displayItems = cart.cart.map(item => {
  return {
    label: `${item.sku}: ${item.quantity}x $${item.price}`,
    amount: {currency: 'USD', value: String(item.total)}


The payment UI should look like this. Try expanding "Order summary":


Notice that the display items are present in the "Order summary" row. We gave each item a label and amount. label is a display label information of the item. amount is an object that constructs price information for the item.

Complete the PaymentRequest

You've put minimum required options to run a Payment Request. Let's allow a user to complete the payment.

Replace "TODO PAY-6" and the existing return request.show(); in app/scripts/modules/payment-api.js with the following code:


    return request.show()
      .then(r => {
        // The UI will show a spinner to the user until
        // `request.complete()` is called.
        response = r;
        let data = r.toJSON();
        return data;
      .then(data => {
        return sendToServer(data);
      .then(() => {
        return response;
      .catch(e => {
        if (response) {
        } else if (e.code !== e.ABORT_ERR) {
          throw e;
        } else {
          return null;


The PaymentRequest interface is activated by calling its show() method. This method invokes a native UI that allows the user to examine the details of the purchase, add or change information, and pay. A Promise (indicated by its then() method and callback function) that resolves will be returned when the user accepts or rejects the payment request.

Calling toJSON() serializes the response object. You can then POST it to a server to process the payment. This portion differs depending on what payment processor / payment gateway you are using.

Once the server returns a response, call complete() to tell the user if processing the payment was successful or not by passing it success or fail.

Try out the app

Awesome! Now you have completed implementing the basic Payment Request API features in your app. If you are not running your server, npm run serve and try it using your Android device.

$ npm run serve

The PaymentRequest UI displays when you click Checkout.


822441ca3e9c7b30.png 26846a34210cd67c.png

Allow shipping options

So far you've learned how to integrate the Payment Request API when it doesn't involve shipping items. Moving forward you will learn how to collect shipping information and options from the user.

Define the shipping options

When you want to collect the user's address information in order to ship items, add requestShipping: true in the third property of the PaymentRequest constructor.

Replace "TODO PAY-7.1" in app/scripts/modules/payment-api.js with the following code:


requestShipping: true,

You also need to provide list of shipping options.

Replace "TODO PAY-7.2" in app/scripts/modules/payment-api.js with the following code:


shippingOptions: displayedShippingOptions

Luckily SHIPPING_OPTIONS is predefined in the app/scripts/modules/payment-api.js; you can parse it and construct the displayShippingOptions object from it.

Replace "TODO PAY-7.3" in app/scripts/modules/payment-api.js with the following code:


let displayedShippingOptions = [];
if (shippingOptions.length > 0) {
  let selectedOption = shippingOptions.find(option => {
    return option.id === shippingOptionId;
  displayedShippingOptions = shippingOptions.map(option => {
    return {
      id: option.id,
      label: option.label,
      amount: {currency: 'USD', value: String(option.price)},
      selected: option.id === shippingOptionId
  if (selectedOption) total += selectedOption.price;


id is a unique identifier of the shipping option item. label is a displayed label of the item. amount is an object that constructs price information for the item. selected is a boolean that indicates if the item is selected.


Notice that these changes add a section to the Payment Request UI, "Shipping". But beware, selecting shipping address will cause UI to freeze and timeout. To resolve this, you will need to handle shippingaddresschange event in the next section.

Add event handlers

What if a user specifies a shipping address that's outside of your target countries and not deliverable? How do you charge a user when the user changes a shipping option? The answer is to receive events upon the user's making changes and update with relevant information.

Add shippingaddresschange event listener

When the user changes a shipping address, you will receive the shippingaddresschange event.

Replace "TODO PAY-8.1" in app/scripts/modules/payment-api.js with the following code:


// When user selects a shipping address, add shipping options to match
request.addEventListener('shippingaddresschange', e => {
  e.updateWith((_ => {
    // Get the shipping options and select the least expensive
    shippingOptions = this.optionsForCountry(request.shippingAddress.country);
    selectedOption = shippingOptions[0].id;
    let details = this.buildPaymentDetails(cart, shippingOptions, selectedOption);
    return Promise.resolve(details);


Upon receiving the shippingaddresschange event, the request object's shippingAddress information is updated. By examining it, you can determine if

  • The item is deliverable.
  • Shipping cost needs to be added/updated.

This code looks into the country of the shipping address and provides free shipping and express shipping inside the US, and provides international shipping otherwise. Checkout optionsForCountry() function in app/scripts/modules/payment-api.js to see how the evaluation is done.


Note that passing an empty array to shippingOptions indicates that shipping is not available for this address. You can display an error message via shippingOption.error in that case.

Add shippingoptionchange event listener

When the user changes a shipping option, you will receive the shippingoptionchange event.

Replace "TODO PAY-8.2" in app/scripts/modules/payment-api.js with the following code:


// When user selects a shipping option, update cost, etc. to match
request.addEventListener('shippingoptionchange', e => {
  e.updateWith((_ => {
    selectedOption = request.shippingOption;
    let details = this.buildPaymentDetails(cart, shippingOptions, selectedOption);
    return Promise.resolve(details);


Upon receiving the shippingoptionchange event, the request object's shippingOption is updated. It indicates the id of the shipping options. Look for the price of the shipping option and update the display items so that the user knows the total cost is changed. Also change the shipping option's selected to true to indicate that user has chosen the item. Checkout buildPaymentDetails() function in app/scripts/modules/payment-api.js to see how it works.


Add payment options

In addition to the shipping address, there are options to collect payer's information such as email address, phone number, and name.

Replace "TODO PAY-9" in app/scripts/modules/payment-api.js with the following payment options:


requestPayerEmail: true,
requestPayerPhone: true,
requestPayerName: true



By adding requestPayerPhone: true, requestPayerEmail: true, requestPayerName: true to the third argument of the PaymentRequest constructor, you can request the payer's phone number, email address, and name.

Test it out

Phew! You have now completed implementing the Payment Request API with shipping option. Let's try it once again by running your server if it's stopped.

$ npm run serve


Try: add random items to the card, go to checkout, change shipping address and options, and finally make a payment.


You have added Payment integration to the e-commerce app. Congratulations!

To learn more about the Payment Request API, visit following links.




Android Pay

If you are interested in enabling Android Pay on top of the Payment Request API, learn how at Android Pay for the Web Codelab.

Found an issue, or have feedback?

Help us make our code labs better by submitting an issue today. And thanks!