Android Payment App Provider

A Collection of Interesting Ideas,

Issue Tracking:
GitHub
Inline In Spec
Editor:
(Google)

Abstract

This technical report describes how Android apps can be exposed by a user agent as payment apps for the web, via the Native App Payment Handler specification.

This technical report is non-normative.

1. Introduction

The [native-app-payment-handler] API defines an abstract functional model, the native payment app provider model, through which user agents can interact with native payment apps in order to treat them as native-app payment handlers that are accessible via the [payment-request] API.

This document describes one concrete implementation of that model, the Android payment app provider, which defines how a user agent running on the Android Operating System can interact with other applications on the same device that want to act as payment apps.

2. Android Payment App Provider

The Android Payment App Provider is a native payment app provider that defines how a user agent can access Android native payment apps. The necessary functionality is built on top of standard Android OS concepts for cross-application communication and collaboration:

An Android payment app is a native payment app representing an installed Android application capable of handling web payment requests. An Android payment app has the following properties:

2.1. Payment app AndroidManifest.xml

In order to be visible to the user agent, the Android payment app defines the following concepts in their AndroidManifest.xml file:

If an Android payment app specifies more than one activity or service that matches one of the above, the user agent behavior is undefined.

2.1.1. The PAY activity

The PAY activity will receive an intent from the user agent when the user agent invokes the native payment app to handle a PaymentRequest. It must be exported and have:

The "org.chromium.default_payment_method_name" tag must have an android:value attribute containing a valid url-based payment method identifier. This will be used for determining the authoritative payment method identifier.

The "org.chromium.payment_method_names" tag, if present, must have an android:resource attribute pointing at a <string-array> resource each of which must be a valid url-based payment method identifier. This will be used along with the authoritative payment method identifier for determining the claimed payment method identifiers.

The "org.chromium.payment_supported_delegations" tag, if present, must have an android:resource attribute pointing at a <string-array> resource listing the supported delegations of the payment app.

The chromium.org namespace used here should be replaced by a w3.org one, but Chromium currently ships with chromium.org so it would need to be updated and a deprecation process followed, etc.

<activity
     android:name=".PaymentActivity"
     android:exported="true">
  <intent-filter>
    <action android:name="org.chromium.intent.action.PAY" />
  </intent-filter>

  <meta-data
      android:name="org.chromium.default_payment_method_name"
      android:value="https://sampleapp.example/pay" />
  <meta-data
      android:name="org.chromium.payment_method_names"
      android:resource="@array/other_payment_method_names" />
  <meta-data
      android:name="org.chromium.payment_supported_delegations"
      android:resource="@array/supported_delegations" />
</activity>
<resources>
  <string-array name="other_payment_method_names">
    <item>https://anotherapp.example/payment</item>
  </string-array>
  <string-array name="supported_delegations">
    <item>shippingAddress</item>
    <item>payerName</item>
    <item>payerPhone</item>
    <item>payerEmail</item>
  </string-array>
</resources>

2.1.2. The IS_READY_TO_PAY service

The IS_READY_TO_PAY service receives calls from the user agent to determine if the app is able to handle a payment request (such as checking whether the user has an enrolled payment instrument ready to pay). If present, it must be exported and have an intent filter with an org.chromium.intent.action.IS_READY_TO_PAY action.

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

Communication with the service is defined by the following Android Interface Definition Language (AIDL) interfaces:

package org.chromium;

import org.chromium.IsReadyToPayServiceCallback;

// Implemented by the payment app.
interface IsReadyToPayService {
    oneway void isReadyToPay(
        IsReadyToPayServiceCallback callback, in Bundle parameters);
}
package org.chromium;

// Implemented by the user agent.
interface IsReadyToPayServiceCallback {
    oneway void handleIsReadyToPay(boolean isReadyToPay);
}

As with the intent actions and meta-data tags, the org.chromium package namespace used by these AIDL interfaces should eventually be standardized under a vendor-neutral namespace such as org.w3c.payments.

The user agent calls the isReadyToPay method with a callback on which it expects to receive the result of the readiness check, along with transaction metadata in the parameters bundle (when checking if the app is able to handle a payment request). The payment app then invokes handleIsReadyToPay asynchronously when it is ready. This asynchronous flow ensures that an unresponsive or slow payment application does not block or hang the user agent’s execution threads.

2.1.3. The UPDATE_PAYMENT_DETAILS service

The UPDATE_PAYMENT_DETAILS service receives connections from the user agent when the payment app is invoked to facilitate two-way communication for dynamic updates (e.g. shipping address, shipping option, or payment method changes). If present, it must be exported and have an intent filter with an org.chromium.intent.action.UPDATE_PAYMENT_DETAILS action.

<service
    android:name=".PaymentDetailsUpdateService"
    android:exported="true">
  <intent-filter>
    <action android:name="org.chromium.intent.action.UPDATE_PAYMENT_DETAILS" />
  </intent-filter>
</service>

Communication with the service is defined by the following Android Interface Definition Language (AIDL) interfaces:

package org.chromium.components.payments;

import android.os.Bundle;
import org.chromium.components.payments.IPaymentDetailsUpdateServiceCallback;

// Implemented by the user agent.
interface IPaymentDetailsUpdateService {
    oneway void changePaymentMethod(in Bundle paymentHandlerMethodData,
            IPaymentDetailsUpdateServiceCallback callback);
    oneway void changeShippingOption(in String shippingOptionId,
            IPaymentDetailsUpdateServiceCallback callback);
    oneway void changeShippingAddress(in Bundle shippingAddress,
            IPaymentDetailsUpdateServiceCallback callback);
}
package org.chromium.components.payments;

import android.os.Bundle;
import org.chromium.components.payments.IPaymentDetailsUpdateService;

// Implemented by the payment app.
interface IPaymentDetailsUpdateServiceCallback {
    oneway void updateWith(in Bundle updatedPaymentDetails);
    oneway void paymentDetailsNotUpdated();
    oneway void setPaymentDetailsUpdateService(
            IPaymentDetailsUpdateService service);
}

As with the other intent actions and AIDL interfaces, the org.chromium.components.payments package namespace used here should eventually be standardized under a vendor-neutral namespace such as org.w3c.payments.

When the user agent invokes the payment app’s PAY activity, it concurrently binds to the app’s UPDATE_PAYMENT_DETAILS service (which implements IPaymentDetailsUpdateServiceCallback). Upon connecting, the user agent calls setPaymentDetailsUpdateService to hand the payment app a Binder proxy to the user agent’s own IPaymentDetailsUpdateService implementation.

When the user changes the payment method, shipping option, or shipping address within the payment app UI, the payment app calls the corresponding method on IPaymentDetailsUpdateService, passing the updated transaction parameters along with a callback. The user agent then notifies the web merchant and, upon receiving the merchant’s response, communicates the updated payment details back to the payment app via updateWith or paymentDetailsNotUpdated.

2.2. User agent app AndroidManifest.xml

In order to be able to interact with Android native payment apps, the user agent application declares its package visibility needs for the relevant intent filters in its own AndroidManifest.xml file, by defining a <queries> block that lists the following actions:

<queries>
  <intent>
    <action android:name="org.chromium.intent.action.PAY"/>
  </intent>
  <intent>
    <action android:name="org.chromium.intent.action.IS_READY_TO_PAY"/>
  </intent>
  <intent>
    <action android:name="org.chromium.intent.action.UPDATE_PAYMENT_DETAILS"/>
  </intent>
</queries>

2.3. Returning a list of native payment apps

In order to get the list of native payment apps, the user agent performs the following steps:

  1. Let payIntent be a new Intent object targeting the org.chromium.intent.action.PAY action.

  2. Let readyToPayIntent be a new Intent object targeting the org.chromium.intent.action.IS_READY_TO_PAY action.

  3. Let updateIntent be a new Intent object targeting the org.chromium.intent.action.UPDATE_PAYMENT_DETAILS action.

  4. Query the Android OS for the list of activities matching payIntent (e.g. via PackageManager.queryIntentActivities), resulting in a list of ResolveInfo objects, payActivities.

  5. Query the Android OS for services matching readyToPayIntent (e.g. via PackageManager.queryIntentServices), and let readyToPayMap be a mapping from package name to service class name.

  6. Query the Android OS for services matching updateIntent (e.g. via PackageManager.queryIntentServices), and let updateMap be a mapping from package name to service class name.

  7. Let apps be an empty list.

  8. For each ResolveInfo info in payActivities:

    1. Let packageName be info’s package name (info.activityInfo.packageName).

    2. Let activityName be info’s activity name (info.activityInfo.name).

    3. Let label be the application label retrieved from the package manager. If label is empty, continue to the next iteration of this loop.

    4. Let icon be the application icon retrieved from the package manager.

    5. Let app be a new Android payment app with package name packageName, pay activity name activityName, label label, and icon icon.

    6. If readyToPayMap contains an entry for packageName, set app’s is-ready-to-pay service name to that entry’s value.

    7. If updateMap contains an entry for packageName, set app’s update payment details service name to that entry’s value.

    8. Append app to apps.

  9. Return apps.

2.4. Authoritative payment method identifier

The authoritative payment method identifier for an Android application, app, is the android:value attribute of the org.chromium.default_payment_method_name <meta-data> tag in app’s AndroidManifest.xml file.

2.5. Claimed payment method identifiers

The set of claimed payment method identifiers for an Android application, app, is created as follows:

  1. Let claimedIdentifiers be a new empty set.

  2. Let authoritativeIdentifier be app’s authoritative payment method identifier.

  3. If authoritativeIdentifier is a valid url-based payment method identifier, add authoritativeIdentifier to claimedIdentifiers.

  4. Let otherMethodIdentifiers be the string array resource referenced by the org.chromium.payment_method_names <meta-data> tag in app’s AndroidManifest.xml file.

  5. If otherMethodIdentifiers is not null and is a string array:

    1. For each string identifier in otherMethodIdentifiers:

      1. If identifier is a valid url-based payment method identifier, add identifier to claimedIdentifiers.

  6. Return claimedIdentifiers.

NOTE: The claimed payment method identifiers automatically include the app’s authoritative payment method identifier, so payment applications do not need to duplicate their primary method URL inside org.chromium.payment_method_names.

2.6. Supported delegations

The set of supported delegations for an Android application, app, is created from the org.chromium.payment_supported_delegations <meta-data> tag in app’s PAY activity, as follows:

  1. Let delegations be an empty set.

  2. Let delegationNames be the string array resource referenced by the org.chromium.payment_supported_delegations <meta-data> tag in app’s PAY activity.

  3. If delegationNames is null or empty, return delegations.

  4. For each string name in delegationNames:

    1. If name is equal to "shippingAddress", add "shippingAddress" to delegations.

    2. Else if name is equal to "payerName", add "payerName" to delegations.

    3. Else if name is equal to "payerPhone", add "payerPhone" to delegations.

    4. Else if name is equal to "payerEmail", add "payerEmail" to delegations.

    5. Otherwise, ignore name.

  5. Return delegations.

In order to check if a given Android application, app, is a related application for a web app manifest, manifest, the user agent performs the following steps:

  1. Let packageName be the package name of app (e.g. obtained via PackageInfo.packageName).

  2. Let versionCode be the version code of app (e.g. obtained via PackageInfo.getLongVersionCode() or PackageInfo.versionCode).

  3. Let appFingerprints be a new empty set of strings.

  4. Let signatures be the cryptographic signing certificates for app obtained from the Android operating system (e.g. via SigningInfo.getSigningCertificateHistory() or PackageInfo.signatures).

  5. If signatures is null or empty, return false.

  6. For each signing certificate cert in signatures:

    1. Let digest be the result of computing the SHA-256 hash of cert’s raw DER-encoded byte array.

    2. Let hexString be the string produced by formatting each byte in digest as a two-digit lowercase hexadecimal number (e.g. "%02x" without any delimiter, such as "308201dd30820146020101300d06092a864886f70d010105050030...").

    3. Add hexString to appFingerprints.

  7. For each external application resource, relatedApplication, in the related applications of manifest:

    1. If relatedApplication’s platform is not equal to "play", go to the next iteration of this loop.

    2. If packageName is not equal to relatedApplication’s id, go to the next iteration of this loop.

    3. If relatedApplication specifies a minimum version:

      1. Let minVersion be the result of parsing relatedApplication’s minimum version as an integer.

      2. If versionCode is strictly less than minVersion, go to the next iteration of this loop.

    4. Let manifestFingerprints be a new empty set of strings.

    5. For each fingerprint object fp in relatedApplication’s fingerprints:

      1. If fp’s type is equal to "sha256_cert":

        1. Let normalizedFingerprint be fp’s value with any ASCII whitespace, colons (":"), or other delimiters removed, converted to ASCII lowercase.

        2. Add normalizedFingerprint to manifestFingerprints.

    6. If manifestFingerprints is empty, go to the next iteration of this loop.

    7. If appFingerprints is equal to manifestFingerprints (i.e. both sets contain the exact same fingerprint strings):

      1. Return true.

  8. Return false.

2.8. Determining if an Android native payment app can handle a PaymentRequest

In order to check if an Android application, app, is able to handle a payment request, request, the user agent performs the following steps:

  1. If the user agent is operating in a private browsing mode, return true immediately without binding to or querying the IS_READY_TO_PAY service.

  2. If app’s is-ready-to-pay service name is null or not present, return true.

    NOTE: The IS_READY_TO_PAY service is optional. If a payment app does not provide it, the user agent assumes the app is ready and delegates the payment decision to when the app is invoked via the PAY activity.

  3. Let parameters be a new Android Bundle object.

  4. Insert the Android package name of the user agent application into parameters with the key "packageName".

  5. Let topOrigin be request’s relevant global object’s associated Document’s origin, formatted without a scheme (e.g. "merchant.example"). Insert topOrigin into parameters with the key "topLevelOrigin".

  6. Let iframeOrigin be request’s relevant settings object’s origin, formatted without a scheme. Insert iframeOrigin with the key "paymentRequestOrigin".

  7. If the top-level document was loaded over a secure HTTPS connection and a server certificate chain is available, let certArray be a Parcelable array containing each certificate’s raw DER-encoded byte array inside an Android Bundle with key "certificate". Insert certArray into parameters with the key "topLevelCertificateChain".

  8. Let matchingMethodNames be a new empty list of strings.

  9. Let methodDataBundle be a new Android Bundle object.

  10. For each payment method identifier identifier supported by request:

    1. If app is allowed to handle the payment method identifier identifier:

      1. Append identifier to matchingMethodNames.

      2. Let dataString be the JSON serialization of the method-specific data associated with identifier in request, or "{}" if no data was supplied.

      3. Insert dataString into methodDataBundle with key identifier.

  11. Insert matchingMethodNames (as an ArrayList of strings) into parameters with the key "methodNames".

  12. Insert methodDataBundle into parameters with the key "methodData".

  13. Let serviceName be app’s is-ready-to-pay service name.

  14. Create a new Android Intent object targeting app’s package name, serviceName, and action org.chromium.intent.action.IS_READY_TO_PAY.

  15. Bind to the service via Context.bindService using the BIND_AUTO_CREATE flag.

  16. If binding fails or a user agent defined connection timeout expires before the service connects, unbind the service and return false.

  17. On service connection, call isReadyToPay(callback, parameters) on the bound IsReadyToPayService interface.

    NOTE: Passing merchant origin and method data in the isReadyToPay parameters bundle introduces potential tracking vectors. In private browsing modes, the user agent bypasses background queries entirely. Future iterations aim to minimize or remove identifying transaction metadata from background readiness checks.

  18. Wait asynchronously for the handleIsReadyToPay(isReadyToPay) method to be invoked on the callback, subject to a user agent defined query timeout.

    1. If handleIsReadyToPay is called with boolean isReadyToPay before the timeout, unbind the service and return isReadyToPay.

    2. If the query times out, the service disconnects, or an exception occurs, unbind the service and return false.

2.9. Invoking an Android native payment app

In order to invoke the native payment app, app, for a PaymentRequest request, the user agent performs the following steps:

  1. Let packageName be app’s package name.

  2. Let activityName be app’s pay activity name.

  3. Let extras be the result of creating an Android bundle from request for app.

  4. Create an Intent object, intent, whose:

    1. Class name is set to the combination of packageName and activityName, e.g. via Intent.setClassName.

    2. Action is set to org.chromium.intent.action.PAY.

    3. Extras are set to extras, e.g. via Intent.putExtras.

  5. If app declares an UPDATE_PAYMENT_DETAILS service in its AndroidManifest.xml, establish the update service connection concurrently.

  6. Send intent to the Android OS in such a way that the user agent will be notified on result, e.g. via Activity.startActivityForResult.

2.10. Communicating with an invoked Android native payment app

An invoked native payment app, app, can communicate dynamic transaction changes (such as shipping address, shipping option, or payment method changes) back to the web merchant via the UPDATE_PAYMENT_DETAILS service (see § 2.1.3 The UPDATE_PAYMENT_DETAILS service).

2.10.1. Establishing the update service connection

The user agent performs the following steps to establish the update service connection for an invoked native payment app app:

  1. Create a new Android Intent object targeting app’s package name, app’s UPDATE_PAYMENT_DETAILS service class name, and action org.chromium.intent.action.UPDATE_PAYMENT_DETAILS.

  2. Attempt to bind to the service via Context.bindService using the BIND_AUTO_CREATE flag.

    1. If binding fails, unbind the service and abort these steps.

      NOTE: The payment flow continues normally; the payment app will proceed without dynamic update capabilities.

  3. When the service connection is established:

    1. Obtain the IPaymentDetailsUpdateServiceCallback interface from the returned IBinder.

    2. Call setPaymentDetailsUpdateService on that interface, passing the user agent’s IPaymentDetailsUpdateService Binder instance.

  4. Maintain this service connection active for the duration of the payment interaction. When app’s PAY activity finishes or is dismissed, unbind and terminate the service connection.

    1. If the connection is lost or dies unexpectedly while the payment interaction is still active, the user agent may attempt to reconnect. If the connection cannot be restored, the user agent unbinds the service and continues the payment flow without dynamic update capabilities.

2.10.2. Authorizing service requests

To authorize a dynamic update service request from an application app:

  1. Let callerUid be the calling UID obtained via Binder.getCallingUid().

  2. Query the Android OS for the package names and cryptographic signing certificates associated with callerUid.

  3. If none of the packages matching callerUid have a package name equal to app’s package name, or if their signing certificates do not match app’s signing certificates, return false.

  4. Return true.

2.10.3. Handling payment method change

When changePaymentMethod is called on IPaymentDetailsUpdateService by app, with paymentHandlerMethodData and callback:

  1. If the user agent is unable to authorize a dynamic update service request from app, return.

  2. If paymentHandlerMethodData is null, or if the string extra "methodName" in paymentHandlerMethodData is null or empty:

    1. Call callback.updateWith with a Bundle containing an error message, and return.

  3. Let methodName be the string extra "methodName" in paymentHandlerMethodData.

  4. Let stringifiedDetails be the string extra "details" in paymentHandlerMethodData, or "{}" if absent.

  5. Let methodDetails be the result of parsing stringifiedDetails as a JSON object. If parsing throws an error, let methodDetails be null.

  6. Inform the user agent that the user has changed their payment method with app, methodName, and methodDetails.

2.10.4. Handling shipping option change

When changeShippingOption is called on IPaymentDetailsUpdateService by app with shippingOptionId and callback:

  1. If the user agent is unable to authorize a dynamic update service request from app, return.

  2. If shippingOptionId is null or empty:

    1. Call callback.updateWith with a Bundle containing an error message, and return.

  3. Inform the user agent that the user has changed their shipping option with app and shippingOptionId.

2.10.5. Handling shipping address change

When changeShippingAddress is called on IPaymentDetailsUpdateService by app with shippingAddressBundle and callback:

  1. If the user agent is unable to authorize a dynamic update service request from app, return.

  2. If shippingAddressBundle is null or empty:

    1. Call callback.updateWith with a Bundle containing an error message, and return.

  3. Let shippingAddress be the result of parsing a shipping address Android bundle from shippingAddressBundle.

  4. If shippingAddress is null:

    1. Call callback.updateWith with a Bundle containing an error message, and return.

  5. Inform the user agent that the user has changed their shipping address with app and shippingAddress.

2.10.6. Updating payment details

In order to update payment details in an invoked Android payment app, app, given a PaymentRequestDetailsUpdate dictionary, update:

  1. Let bundle be the result of creating an Android bundle from a PaymentRequestDetailsUpdate for update.

  2. Call callback.updateWith(bundle) on the active IPaymentDetailsUpdateServiceCallback.

2.10.7. Notifying payment details not updated

In order to notify payment details not updated for app:

  1. Call callback.paymentDetailsNotUpdated() on the active IPaymentDetailsUpdateServiceCallback.

2.10.8. Parsing a shipping address Android bundle

To parse a shipping address Android bundle from an Android Bundle, bundle:

  1. Let country be the string extra "countryCode" in bundle.

  2. If country is null, empty, or not a valid ISO 3166-1 alpha-2 country code, return null.

  3. Let address be a new ContactAddress object with the following fields:

    • country set to country.

    • addressLine set to the string array extra "addressLines" in bundle, or an empty list if absent.

    • region set to the string extra "region" in bundle, or "" if absent.

    • city set to the string extra "city" in bundle, or "" if absent.

    • dependentLocality set to the string extra "dependentLocality" in bundle, or "" if absent.

    • postalCode set to the string extra "postalCode" in bundle, or "" if absent.

    • sortingCode set to the string extra "sortingCode" in bundle, or "" if absent.

    • organization set to the string extra "organization" in bundle, or "" if absent.

    • recipient set to the string extra "recipient" in bundle, or "" if absent.

    • phone set to the string extra "phone" in bundle, or "" if absent.

  4. Return address.

2.10.9. Creating an Android bundle from a PaymentRequestDetailsUpdate

To create an Android bundle from a PaymentRequestDetailsUpdate from a PaymentRequestDetailsUpdate, update:

  1. Let bundle be a new Android Bundle object.

  2. If update["total"] exists:

    1. Let totalBundle be a new Android Bundle object containing:

      • "currency" set to update["total"]["currency"]

      • "value" set to update["total"]["value"]

    2. Insert totalBundle into bundle with key "total".

  3. If update["shippingOptions"] exists and is not empty:

    1. Let optionsArray be an empty Parcelable array.

    2. For each PaymentShippingOption option in update["shippingOptions"]:

      1. Let optBundle be a new Android Bundle object containing:

        • "id" set to option["id"]

        • "label" set to option["label"]

        • "amount" set to a Bundle containing "currency" and "value" from option["amount"]

        • "selected" set to option["selected"]

      2. Append optBundle to optionsArray.

    3. Insert optionsArray into bundle with key "shippingOptions".

  4. If update["error"] exists and is not empty:

    1. Insert update["error"] into bundle with key "error".

  5. If update["stringifiedPaymentMethodErrors"] exists and is not empty:

    1. Insert update["stringifiedPaymentMethodErrors"] into bundle with key "stringifiedPaymentMethodErrors".

  6. If update["shippingAddressErrors"] exists and is not null:

    1. Let addrErrorsBundle be a new Android Bundle object.

    2. For each key in update["shippingAddressErrors"] (such as "country", "addressLine", "city", "postalCode", etc.), insert the string error into addrErrorsBundle under that key.

    3. Insert addrErrorsBundle into bundle with key "addressErrors".

  7. Return bundle.

2.11. Indicating success of an invoked Android native payment app

In order for an invoked Android application, app, to indicate success back to the user agent, app performs the following steps:

  1. Create a new Android Intent object, intent.

  2. Insert the string identifier of the payment method selected by the user into intent with the key "methodName".

  3. Insert a JSON-stringified object containing the payment-method-specific transaction details into intent with the key "details".

  4. If app supports and was requested to provide payer information:

    1. If payer name collection was delegated, insert the payer’s name string into intent with the key "payerName".

    2. If payer email collection was delegated, insert the payer’s email string into intent with the key "payerEmail".

    3. If payer phone collection was delegated, insert the payer’s phone string into intent with the key "payerPhone".

  5. If app supported and was requested to provide shipping information:

    1. Insert the identifier of the selected shipping option into intent with the key "shippingOptionId".

    2. Let addressBundle be an Android Bundle representing the selected shipping address, populated with the address keys defined in parse a shipping address Android bundle. Insert addressBundle into intent with the key "shippingAddress".

  6. Call Activity.setResult with Activity.RESULT_OK and intent.

  7. Terminate app’s activity, e.g. via Activity.finish().

To retrieve the converted result details for an invoked Android payment app, app:

  1. Let resultIntent be the result Intent returned when app indicated success.

  2. Let paymentOptions be the requested PaymentOptions passed when app was invoked.

  3. If resultIntent is null or does not contain extras, return failure.

  4. Let extras be the Bundle of extras from resultIntent.

  5. Let details be the string extra "details" in extras.

  6. If details is null or empty, return failure.

  7. Let methodName be the string extra "methodName" in extras.

  8. If methodName is null or empty, return failure.

  9. Let response be a new NativePaymentHandlerResponse dictionary with:

    • response["methodName"] set to methodName

    • response["details"] set to details

  10. If paymentOptions was supplied:

    1. If paymentOptions["requestShipping"] is true:

      1. Let addressBundle be the Bundle extra "shippingAddress" in extras.

      2. If addressBundle is null or empty, return failure.

      3. Let shippingAddress be the result of parsing a shipping address Android bundle from addressBundle.

      4. If shippingAddress is null, return failure.

      5. Set response["shippingAddress"] to shippingAddress.

      6. Let shippingOptionId be the string extra "shippingOptionId" in extras.

      7. If shippingOptionId is null or empty, return failure.

      8. Set response["shippingOption"] to shippingOptionId.

    2. If paymentOptions["requestPayerName"] is true:

      1. Let payerName be the string extra "payerName" in extras.

      2. If payerName is null or empty, return failure.

      3. Set response["payerName"] to payerName.

    3. If paymentOptions["requestPayerEmail"] is true:

      1. Let payerEmail be the string extra "payerEmail" in extras.

      2. If payerEmail is null or empty, return failure.

      3. Set response["payerEmail"] to payerEmail.

    4. If paymentOptions["requestPayerPhone"] is true:

      1. Let payerPhone be the string extra "payerPhone" in extras.

      2. If payerPhone is null or empty, return failure.

      3. Set response["payerPhone"] to payerPhone.

  11. Return response.

2.12. Indicating user cancel from an invoked Android native payment app

In order for an invoked Android application, app, to indicate user cancellation back to the user agent, app performs the following steps:

  1. Call Activity.setResult with Activity.RESULT_CANCEL.

  2. Terminate app’s activity, e.g. via Activity.finish().

2.13. Indicating internal error from an invoked Android native payment app

In order for an invoked Android application, app, to indicate internal error back to the user agent, app performs the following steps:

  1. Let RESULT_INTERNAL_ERROR be equal to Activity.RESULT_FIRST_USER (integer value 1).

  2. Call Activity.setResult with RESULT_INTERNAL_ERROR.

  3. Terminate app’s activity, e.g. via Activity.finish().

2.14. Creating an Android bundle

To create an Android bundle from a PaymentRequest, request, for an application app:

  1. Let bundle be a new Android Bundle object.

  2. Insert request.id into bundle with the key "paymentRequestId".

  3. Insert the title of request’s relevant global object’s associated Document into bundle with the key "merchantName".

  4. Let totalItem be request.[[details]].total.

  5. Let serializedTotal be a JSON-serialized object containing "currency" set to totalItem.amount.currency and "value" set to totalItem.amount.value. Insert serializedTotal into bundle with the key "total".

  6. If request.[[details]].modifiers exists and is not empty:

    1. Let modifiersList be an empty JSON array.

    2. For each PaymentDetailsModifier modifier in request.[[details]].modifiers:

      1. Let modObj be a new JSON object.

      2. If modifier.total exists, set modObj["total"] to a JSON object with "currency" and "value" from modifier.total.amount.

      3. Set modObj["supportedMethods"] to a JSON array containing the single string modifier.supportedMethods.

      4. Set modObj["data"] to the JSON-serialized string of modifier.data if present, or "{}" otherwise.

      5. Append modObj to modifiersList.

    3. Insert the JSON-stringified serialization of modifiersList into bundle with the key "modifiers".

  7. Let options be request.[[options]].

  8. If options exists:

    1. Let optionsBundle be a new Android Bundle object containing:

    2. If options.shippingType exists and is not null:

      • Insert options.shippingType into optionsBundle with the key "shippingType".

    3. Insert optionsBundle into bundle with the key "paymentOptions".

  9. If options exists and options.requestShipping is true:

    1. If request.[[details]].shippingOptions exists and is not empty:

      1. Let optionsArray be an empty Parcelable array.

      2. For each PaymentShippingOption option in request.[[details]].shippingOptions:

        1. Let optBundle be a new Android Bundle object containing:

          • "id" set to option.id

          • "label" set to option.label

          • "amount" set to a Bundle containing "currency" and "value" from option.amount

          • "selected" set to option.selected

        2. Append optBundle to optionsArray.

      3. Insert optionsArray into bundle with the key "shippingOptions".

  10. Let topOrigin be request’s relevant global object’s associated Document’s origin, formatted without a scheme (e.g. "merchant.example"). Insert topOrigin into bundle with the key "topLevelOrigin".

  11. Let iframeOrigin be request’s relevant settings object’s origin, formatted without a scheme. Insert iframeOrigin into bundle with the key "paymentRequestOrigin".

  12. If the top-level document was loaded over a secure HTTPS connection and a server certificate chain is available, let certArray be a Parcelable array containing each certificate’s raw DER-encoded byte array inside an Android Bundle with key "certificate". Insert certArray into bundle with the key "topLevelCertificateChain".

  13. Let matchingMethodNames be a new empty list of strings.

  14. Let methodDataBundle be a new Android Bundle object.

  15. For each payment method identifier identifier supported by request:

    1. If app is allowed to handle the payment method identifier identifier:

      1. Append identifier to matchingMethodNames.

      2. Let dataString be the JSON serialization of the method-specific data associated with identifier in request, or "{}" if no data was supplied.

      3. Insert dataString into methodDataBundle with key identifier.

  16. Insert matchingMethodNames (as an ArrayList of strings) into bundle with the key "methodNames".

  17. Insert methodDataBundle into bundle with the key "methodData".

  18. Return bundle.

Conformance

Conformance requirements are expressed with a combination of descriptive assertions and RFC 2119 terminology. The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in the normative parts of this document are to be interpreted as described in RFC 2119. However, for readability, these words do not appear in all uppercase letters in this specification.

All of the text of this specification is normative except sections explicitly marked as non-normative, examples, and notes. [RFC2119]

Examples in this specification are introduced with the words “for example” or are set apart from the normative text with class="example", like this:

This is an example of an informative example.

Informative notes begin with the word “Note” and are set apart from the normative text with class="note", like this:

Note, this is an informative note.

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119

Non-Normative References

[APPMANIFEST]
Marcos Caceres; Daniel Murphy; Christian Liebel. Web Application Manifest. URL: https://w3c.github.io/manifest/
[CONTACT-PICKER]
Peter Beverloo. Contact Picker API. URL: https://w3c.github.io/contact-picker/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[MANIFEST-INCUBATIONS]
Manifest Incubations. Draft Community Group Report. URL: https://wicg.github.io/manifest-incubations/
[NATIVE-APP-PAYMENT-HANDLER]
Native-app Payment Handler. URL: https://stephenmcgruer.github.io/native-app-payment-handler/spec.html
[PAYMENT-METHOD-ID]
Marcos Caceres. Payment Method Identifiers. URL: https://w3c.github.io/payment-method-id/
[PAYMENT-REQUEST]
Marcos Caceres; Ian Jacobs; Stephen McGruer. Payment Request API. URL: https://w3c.github.io/payment-request/
[WEB-BASED-PAYMENT-HANDLER]
Ian Jacobs; Jinho Bang; Stephen McGruer. Web-based Payment Handler API. URL: https://w3c.github.io/web-based-payment-handler/

Issues Index

The chromium.org namespace used here should be replaced by a w3.org one, but Chromium currently ships with chromium.org so it would need to be updated and a deprecation process followed, etc.
As with the intent actions and meta-data tags, the org.chromium package namespace used by these AIDL interfaces should eventually be standardized under a vendor-neutral namespace such as org.w3c.payments.
As with the other intent actions and AIDL interfaces, the org.chromium.components.payments package namespace used here should eventually be standardized under a vendor-neutral namespace such as org.w3c.payments.