Using the Payment Request API on Android

Overview

1200px-W3C®_Icon.svg.png

The W3C Payment Request API and Payment Handler API allows a web-based payment application to act as a payment method. This has been extended by Google in Chrome to allow native Android Apps to act in the same role.

A native Android app offers SRC, Issuers and Payment Service Providers another channel for consumers to make payments adding benefits and possibilities offered by native Android apps.

Google have created a demo custom payment handler (Bobpay) with complementary payment app: https://bobpay.xyz/. Full source code for both the web Payment app and the Android payment is available.

This post assumes familiarity with Payment Request API and Payment Handler API and does not cover these beyond how a native Android payment app integrates with them.

Implementation detail

A full developer guide has been created by Google: 

https://developers.google.com/web/fundamentals/payments/payment-apps-developer-guide/android-payment-apps

Briefly, an Android payment app can be included in a Custom Payment Handler manifest. This is achieved by listing the payment app id (it’s namespace, e.g. com.srcpay.app) and the fingerprint of the signed app:

"related_applications": [{     "platform": "play",     "id": "com.pay.app",     "min_version": "1",     "fingerprints": [{       "type": "sha256_cert",       "value": "92:5A:39:05:C5:B9:EA:BC:71:48:5F:F2"     }],     "url": "https://play.google.com/store/apps/details?id=com.pay.app"   }] }

These allow the app to be recognised as installed and correctly signed on the consumer device (and therefore safe to invoke). If the app is not installed, the Play Store url can also be included so that the user can be asked if they would like to download the app. Note also that more than one app can be listed in the manifest.

Once added in this way, a consumer using an Android device that has SRC Pay installed (or any other app included in the manifest) will see it listed as a payment option as part of a mobile web shopping experience (Chrome only).

payment_request1.png
payment_request2.png

You can see in the screenshots above the two native payment applications “SRC Payment” and “Partner Bank” as selectable payment methods.

Within the Android app a number of elements need to be included for the app to act as a payment handler.

Firstly, the app manifest must include an Activity that accepts the org.chromium.intent.action.PAY intent:

<activity android:name=".PaymentHandlerActivity">    <intent-filter>        <action android:name="org.chromium.intent.action.PAY" />    </intent-filter>    <meta-data android:name="org.chromium.default_payment_method_name"        android:value="http://example.com/pay" />    <meta-data android:name="org.chromium.payment_method_names"        android:resource="@array/method_names" /> </activity>

Default_payment_method_name is the url of the custom Payment Handler and payment_method_names is an array of supported methods.

<?xml version="1.0" encoding="utf-8"?> <resources>    <string-array name="method_names">        <item>http://example.com/pay</item>        <item>basic-card</item>    </string-array> </resources>

Here we list our custom payment handler and ‘basic-card’ as examples. A typical payment app provider may list only the default method. If the app can handle ‘basic-card’ it does not need to be listed in the web payment handler manifest. The app will be displayed in the payment sheet for any merchant including ‘basic-card’ as a payment method.

NB: Although the available documentation indicates it is possible list multiple payment handler urls, testing has revealed that only the default payment method is ever made available to the user. We suggested this is raised with Google.

A further addition can be made to the manifest. A service can be added called IS_READY_TO_PAY which the browser can check with the payment app before showing it as an option for payment (e.g. does the user actually have an account with payment cards available?).

<service    android:name=".IsReadyToPayServiceImpl"    android:enabled="true"    android:exported="true">    <intent-filter>        <action android:name="org.chromium.intent.action.IS_READY_TO_PAY" />    </intent-filter> </service>

In the activity which receives the PAY intent, data can be extracted from the calling merchant:

Intent callingIntent = getIntent(); Bundle extras = callingIntent.getExtras(); String details = extras.getString("details"); String data = extras.getString("data"); String total = extras.getString("total"); String merchantName = extras.getString("merchantName"); JSONObject json; JSONObject dataJSON; JSONObject totalJSON; try {    json = new JSONObject(details);    dataJSON = new JSONObject(data);    totalJSON = new JSONObject(total);    String lineItem = dataJSON.getString("item");    String currency = totalJSON.getString("currency");    String price = totalJSON.getString("value"); } catch (JSONException e) {    showError("Cannot parse the shopping cart details into JSON.");    return; }

The ‘data’ object can contain any custom data set by the merchant. In this activity one would expect the consumer to be able to select a payment card (following suitable authentication). To do so, custom data can be returned to the merchant using an intent scheme:

Intent result = new Intent(); Bundle extras = new Bundle(); extras.putString("methodName", "https://example.com/pay"); extras.putString("instrumentDetails", "{\"token\": \"1234\"}"); result.putExtras(extras); setResult(mError ? RESULT_CANCELED : RESULT_OK, result); finish();

Here we return the calling method and some custom data in ‘instrumentDetails’.

Returned data is handled by the merchant Payment Request API implementation:

request.show().then(response => {    response.complete('success').then(function() {   //Returned data contained in response.details    }); }).catch(exception => { });

The simple implementation detailed above demonstrates an end-to-end checkout flow: merchant site calls Payment Request API, consumer selects Android payment app, checkout data is sent through to the payment app and card data is returned back to the merchant.

Conclusion summary


An android app can successfully be integrated with Payment Request API. Such an app appears to offer the same set of capabilities (and restrictions) as a web Payment Handler but offers access to the enhanced security and UX possibilities a native app offers. Where users do not have an app installed there is a mechanism for encouraging installation via the Play Store. The web Payment Handler can always act as a fallback for users that do not wish to install an app for this function.

A number of observations have been made regarding how the app appears as part of the checkout flow:

  • The app is listed in the payment sheet using it’s app name and standard launcher icon.

  • The payment handler manifest prefer_related_applications=true means the user isn't shown the web handler if the app is installed as an option.

  • If related_applications in the handler manifest contains multiple apps then they are all listed as payment methods if installed.

  • With a single payment app installed and no configurable options in the Payment Request (e.g. shipping address), the app will launch immediately on invoking Payment Request show. (As per a web Payment Handler if no options are set).

  • The merchant website "title" is passed as merchantName in the bundle extras.

  • The apps can implement the optional IS_READY_TO_PAY intent to allow them to communicate that they are not ready. If not implemented it defaults to true and the API assumes the app is ready.

A number of issues were encountered in developing demonstration payment apps:

  • The payment handlers appeared to be one refresh behind the current live version. When invoked it shows the last version and updates in the background.

  • Sometimes both app and web handlers were not shown even if one app is installed if the web handler has been previously selected. In general, what is listed in the payment sheet seems a little volatile, but is likely dependent on previous user decisions. There may be issues that need to be ironed out in Chrome.

  • DisplayItems is not passed to the app, it receives an empty array. (This is consistent with the web app too, The Google Bobpay example suggests that it should work.

Previous
Previous

WebAuthn - a better alternative to passwords?

Next
Next

Manually re-signing iOS Apps