Receive Payments via Google Play Billing with the Digital Goods API and the Payment Request API

André Cipriani Bandarra
André Cipriani Bandarra

If your app is distributed through Google Play, and you want to sell digital goods or offer subscriptions, you must use Google Play Billing. Google Play Billing offers tools for managing your catalog, prices and subscriptions, useful reports, and a checkout flow powered by the Play Store that is already familiar to your users.

For apps built using Trusted Web Activities, and delivered through the Google Play Store, you can now use the Payment Request API and the Digital Goods API to integrate with Google Play Billing. It's available on Chrome 101 and above for Android and ChromeOS.

In this guide, you will learn how to add Google Play Billing support to your PWA and package it for distribution on the Google Play Store for both ChromeOS and Play Store.

You will use two web platform APIs to add Play Billing support to your PWA. The Digital Goods API is used to gather SKU information and check for purchases and entitlements from the Play Store. The Payment Request API is used to configure the Google Play Store as the payment method and to complete the purchase flow.

How to monetize applications on the Play Store

There are two ways your application can monetize with Google Play Billing on the Play Store:

  • In-app purchases allow selling both durable and consumable virtual goods, like additional features, or removing ads.
  • Subscriptions, offer your users ongoing access to content or services for a recurring fee, like news subscriptions or memberships.

Requirements

In order to setup Google Play Billing, you will need:

Update the Bubblewrap project

If you don't have Bubblewrap installed, you will need to install it. See the Quick Start Guide for details on how to get started. If you already have Bubblewrap, make sure to update to version 1.8.2 or above.

Bubblewrap also has the feature behind a flag. In order to enable it, you will need to modify the project configuration in the twa-manifest.json, located at the root of the project and enable both alphaDependencies and the playBilling feature:

  ...,
  "enableNotifications": true,
  "features": {
    "playBilling": {
      "enabled": true
    }
  },
  "alphaDependencies": {
    "enabled": true
  },
  ...

With the configuration file updated, run bubblewrap update to apply the configuration to the project, followed by bubblewrap build, to generate a new Android package and upload this package to the Play Store.

Feature detecting the Digital Goods API and Google Play Billing availability

The Digital Goods API is currently only supported by Chrome when the PWA is being executed inside a Trusted Web Activity, and it is possible to detect if it is available by checking for getDigitalGoodsService on the window object:

if ('getDigitalGoodsService' in window) {
 // Digital Goods API is supported!
}

The Digital Goods API may be available in any browser and support different stores. In order to check if a particular store backend is supported, you will need to invoke getDigitalGoodsService() passing the store ID as a parameter. The Google Play Store is identified by the string https://play.google.com/billing:

if ('getDigitalGoodsService' in window) {
  // Digital Goods API is supported!
  try {
    const service =
        await window.getDigitalGoodsService('https://play.google.com/billing');
    // Google Play Billing is supported!

  } catch (error) {
    // Google Play Billing is not available. Use another payment flow.
    return;
  }
}

Retrieve details for a SKU

The Digital Goods API provides getDetails(), which allows retrieving the information like the product title, description, and most importantly, the price, from the payments backend.

You can then use this information in your use interface and provide more details to the user:

const skuDetails = await service.getDetails(['shiny_sword', 'gem']);
for (item of skuDetails) {
  // Format the price according to the user locale.
  const localizedPrice = new Intl.NumberFormat(
      navigator.language,
      {style: 'currency', currency: item.price.currency}
    ).format(item.price.value);

  // Render the price to the UI.
  renderProductDetails(
        item.itemId, item.title, localizedPrice, item.description);
}

Build the purchase flow

The constructor for a PaymentRequest takes two parameters: a list of payment methods and a list of payment details.

When inside the Trusted Web Activity, you must use the Google Play billing payment method, by setting https://play.google.com/billing as the identifier, and adding the product SKU in as a data member:

async function makePurchase(service, sku) {
   // Define the preferred payment method and item ID
   const paymentMethods = [{
       supportedMethods: "https://play.google.com/billing",
       data: {
           sku: sku,
       }
   }];

   ...
}

Even though the payment details are required, the Play Billing will ignore those values and use the values set when creating the SKU in the Play Console, so they can be filled with bogus values:

const paymentDetails = {
    total: {
        label: `Total`,
        amount: {currency: `USD`, value: `0`}
    }
};

const request = new PaymentRequest(paymentMethods, paymentDetails);

Call the show() on the payment request object to start the payment flow. If the Promise succeeds that will may be payment was successful. If it fails, the user likely aborted the payment.

If the promise succeeds, you will need to verify and acknowledge the purchase. In order to protect against fraud, this step must be implemented using your backend. Check out the Play Billing documentation to learn how to implement the verification in your backend. If you do not acknowledge the purchase, after three days, the user will receive a refund and Google Play will revoke the purchase.

...
const request = new PaymentRequest(paymentMethods, paymentDetails);
try {
    const paymentResponse = await request.show();
    const {purchaseToken} = paymentResponse.details;

    // Call backend to validate and acknowledge the purchase.
    if (await acknowledgePurchaseOnBackend(purchaseToken, sku)) {
        // Optional: tell the PaymentRequest API the validation was
        // successful. The user-agent may show a "payment successful"
        // message to the user.
        const paymentComplete = await paymentResponse.complete('success');
    } else {
        // Optional: tell the PaymentRequest API the validation failed. The
        // user agent may show a message to the user.
        const paymentComplete = await paymentResponse.complete('fail');
    }
} catch(e) {
    // The purchase failed, and we can handle the failure here. AbortError
    // usually means a user cancellation
}
...

Optionally, consume() may be called on a purchaseToken to mark the purchase as used up and allow it to be purchased again.

Putting everything together, a purchase method looks like the following:

async function makePurchase(service, sku) {
    // Define the preferred payment method and item ID
    const paymentMethods = [{
        supportedMethods: "https://play.google.com/billing",
        data: {
            sku: sku,
        }
    }];

    // The "total" member of the paymentDetails is required by the Payment
    // Request API, but is not used when using Google Play Billing. We can
    // set it up with bogus details.
    const paymentDetails = {
        total: {
            label: `Total`,
            amount: {currency: `USD`, value: `0`}
        }
    };

    const request = new PaymentRequest(paymentMethods, paymentDetails);
    try {
        const paymentResponse = await request.show();
        const {purchaseToken} = paymentResponse.details;

        // Call backend to validate and acknowledge the purchase.
        if (await acknowledgePurchaseOnBackend(purchaseToken, sku)) {
            // Optional: consume the purchase, allowing the user to purchase
            // the same item again.
            service.consume(purchaseToken);

            // Optional: tell the PaymentRequest API the validation was
            // successful. The user-agent may show a "payment successful"
            // message to the user.
            const paymentComplete =
                    await paymentResponse.complete('success');
        } else {
            // Optional: tell the PaymentRequest API the validation failed.
            // The user agent may show a message to the user.
            const paymentComplete = await paymentResponse.complete('fail');
        }
    } catch(e) {
        // The purchase failed, and we can handle the failure here.
        // AbortError usually means a user cancellation
    }
}

Check the status of existing purchases

The Digital Goods API allows you to check if the user has any existing entitlements (in-app purchases that haven't been consumed yet or on-going subscriptions) from previous purchases they've already made, whether on another device, from a previous install, redeemed from a promo code, or just the last time they opened the app.


const service =
     await window.getDigitalGoodsService('https://play.google.com/billing');
...
const existingPurchases = await service.listPurchases();
for (const p of existingPurchases) {
    // Update the UI with items the user is already entitled to.
    console.log(`Users has entitlement for ${p.itemId}`);
}

This is also a good time to check for purchases that were previously made but weren't acknowledged. It is recommended to acknowledge purchases as soon as possible to ensure your users' entitlements are properly reflected in your app.

const service =
     await window.getDigitalGoodsService("https://play.google.com/billing");
...
const existingPurchases = await service.listPurchases();
for (const p of existingPurchases) {
    await verifyOrAcknowledgePurchaseOnBackend(p.purchaseToken, p.itemId);

    // Update the UI with items the user is already entitled to.
    console.log(`Users has entitlement for ${p.itemId}`);
}

Test your integration

On a Development Android device

It is possible to enable the Digital Goods API on an development Android device for testing:

  • Ensure you are on Android 9 or greater with developer mode enabled.
  • Install Chrome 101 or newer.
  • Enable the following flags in Chrome by navigating to chrome://flags and searching for the flag by name:
    • #enable-debug-for-store-billing
  • Ensure that the site is hosted using a https protocol. Using http will cause the API to be undefined

On a ChromeOS device

The Digital Goods API will be available on ChromeOS stable starting with version 89. In the meantime, it is possible to test the Digital Goods API:

  • Install your app from the Play Store on the device.
  • Ensure that the site is hosted using a https protocol. Using http will cause the API to be undefined

With test users and QA teams

The Play Store provides affordances for testing, including user test accounts and test SKUs. Checkout the Google Play Billing test documentation for more information.

Where to go next?

As discussed in this document, the Play Billing API has client-side components, which are managed by the Digital Goods API, and server-side components.