How to update your Android payment app to provide shipping address and payer's contact information with Web Payments APIs.


![Sahel Sharify](https://web.dev/images/authors/sahel.jpg) Sahel Sharify [GitHub](https://github.com/sahel-sh)

<br />


Published: July 17, 2020, Last updated: October 16, 2026

<br />

Entering shipping address and contact information through a web form can be a
cumbersome experience for customers. It can cause errors and lower conversion
rate.

That's why the Payment Request API supports a feature to request shipping
address and contact information. This provides multiple benefits:

- Users can pick the right address with just a few taps.
- The address is always returned in [the standardized
  format](https://www.w3.org/TR/contact-picker/#infrastructure-physical-address).
- Submitting an incorrect address is less likely.

Browsers can defer the collection of shipping address and contact information to
a payment app to provide a unified payment experience. This functionality is
called *delegation*.

Whenever possible, Chrome delegates the collection of a customer's shipping
address and contact information to the invoked Android payment app. The
delegation reduces the friction during checkout.

The merchant website can dynamically update the shipping options and total price
depending on the customer's choice of the shipping address and the shipping
option.
Shipping option and shipping address change in action. See how it affects shipping options and total price dynamically.

> [!NOTE]
> **Note:** Learn how to implement an [Android payment app](https://web.dev/articles/android-payment-apps-developers-guide) in advance.

To add delegation support to an already existing Android payment app,
implement the following steps:

1. [Declare supported delegations](https://web.dev/articles/android-payment-apps-delegation#declare_supported_delegations).
2. [Parse `PAY` intent extras for required payment
   options](https://web.dev/articles/android-payment-apps-delegation#parse_pay_intent_extras_for_required_payment_options).
3. [Provide required information in payment
   response](https://web.dev/articles/android-payment-apps-delegation#provide_required_information_in_a_payment_response).
4. [Optional: Support dynamic flow](https://web.dev/articles/android-payment-apps-delegation#optional_support_dynamic_flow):
   1. [Notify the merchant about changes in the user selected payment method,
      shipping address, or shipping
      option](https://web.dev/articles/android-payment-apps-delegation#notify_the_merchant_about_changes_in_the_user_selected_payment_method_shipping_address_or_shipping_option).
   2. [Receive updated payment details from the merchant (for example, the
      adjusted total amount based on the selected shipping option's
      cost)](https://web.dev/articles/android-payment-apps-delegation#receive_updated_payment_details_from_the_merchant).

## Declare supported delegations

The browser needs to know the list of additional information that your payment
app can provide so it can delegate the collection of that information to your
app. Declare the supported delegations as a `<meta-data>` in your app's
[AndroidManifest.xml](https://web.dev/articles/android-payment-apps-developers-guide#androidmanifestxml_2).
Chrome reads supported delegations for Android payment apps only from
`AndroidManifest.xml`. The `payment.supported_delegations` field in the
[web app manifest](https://web.dev/articles/setting-up-a-payment-method#step_3_serve_a_web_app_manifest)
applies only to web-based payment apps.

    <activity
      android:name=".PaymentActivity"
      ...
      <meta-data
        android:name="org.chromium.payment_supported_delegations"
        android:resource="@array/chromium_payment_supported_delegations" />
    </activity>

`android:resource` must point to a `<string-array>` containing all or a subset of the following values:

- `payerName`
- `payerEmail`
- `payerPhone`
- `shippingAddress`

The following example can only provide a shipping address and the payer's email
address.

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
      <string-array name="chromium_payment_supported_delegations">
        <item>payerEmail</item>
        <item>shippingAddress</item>
      </string-array>
    </resources>

## Parse `PAY` intent extras for required payment options

The merchant can specify additional required information using the
[`paymentOptions`](https://www.w3.org/TR/payment-request/#paymentoptions-dictionary)
dictionary. Chrome will provide the list of required options that your app can
provide by passing the `paymentOptions`
[Intent extras](https://web.dev/articles/android-payment-apps-developers-guide#parameters_2)
to the `PAY` activity.

### `paymentOptions`

`paymentOptions` is the subset of merchant specified payment options for which
your app has declared delegation support.

### Kotlin

    val paymentOptions: Bundle? = extras.getBundle("paymentOptions")
    val requestPayerName: Boolean? = paymentOptions?.getBoolean("requestPayerName")
    val requestPayerPhone: Boolean? = paymentOptions?.getBoolean("requestPayerPhone")
    val requestPayerEmail: Boolean? = paymentOptions?.getBoolean("requestPayerEmail")
    val requestShipping: Boolean? = paymentOptions?.getBoolean("requestShipping")
    val shippingType: String? = paymentOptions?.getString("shippingType")

### Java

    Bundle paymentOptions = extras.getBundle("paymentOptions");
    if (paymentOptions != null) {
        Boolean requestPayerName = paymentOptions.getBoolean("requestPayerName");
        Boolean requestPayerPhone = paymentOptions.getBoolean("requestPayerPhone");
        Boolean requestPayerEmail = paymentOptions.getBoolean("requestPayerEmail");
        Boolean requestShipping = paymentOptions.getBoolean("requestShipping");
        String shippingType = paymentOptions.getString("shippingType");
    }

It can include the following parameters:

- `requestPayerName` - The boolean indicating whether or not the payer's name is required.
- `requestPayerPhone` - The boolean indicating whether or not the payer's phone is required.
- `requestPayerEmail` - The boolean indicating whether or not the payer's email is required.
- `requestShipping` - The boolean indicating whether or not shipping information is required.
- `shippingType` - The string showing the type of shipping. Shipping type can be `"shipping"`, `"delivery"`, or `"pickup"`. Your app can use this hint in its UI when asking for the user's address or choice of shipping options.

### `shippingOptions`

`shippingOptions` is the parcelable array of merchant specified shipping
options. This parameter will only exist when `paymentOptions.requestShipping ==
true`.

### Kotlin

    val shippingOptions: List<ShippingOption>? =
        extras.getParcelableArray("shippingOptions")?.mapNotNull {
            p -> from(p as Bundle)
        }

### Java

    Parcelable[] shippingOptions = extras.getParcelableArray("shippingOptions");
    for (Parcelable it : shippingOptions) {
      if (it != null && it instanceof Bundle) {
        Bundle shippingOption = (Bundle) it;
      }
    }

Each shipping option is a `Bundle` with the following keys.

- `id` - The shipping option identifier.
- `label` - The shipping option label shown to the user.
- `amount` - The shipping cost bundle containing `currency` and `value` keys with string values.
  - `currency` shows the currency of the shipping cost, as an [ISO4217](https://www.iso.org/iso-4217-currency-codes.html) well-formed 3-letter alphabet code
  - `value` shows the value of the shipping cost, as a [valid decimal monetary
    value](https://w3c.github.io/payment-request/#dfn-valid-decimal-monetary-value)
- `selected` - Whether or not the shipping option should be selected when the payment app displays the shipping options.

All keys other than the `selected` have string values. `selected` has a boolean
value.

### Kotlin

    val id: String = bundle.getString("id")
    val label: String = bundle.getString("label")
    val amount: Bundle = bundle.getBundle("amount")
    val selected: Boolean = bundle.getBoolean("selected", false)

### Java

    String id = bundle.getString("id");
    String label = bundle.getString("label");
    Bundle amount = bundle.getBundle("amount");
    Boolean selected = bundle.getBoolean("selected", false);

## Provide required information in a payment response

Your app should include the required additional information in its response to
the `PAY` activity.

To do so the following parameters must be specified as Intent extras:

- `payerName` - The payer's full name. This should be a non-empty string when `paymentOptions.requestPayerName` is true.
- `payerPhone` - The payer's phone number. This should be a non-empty string when `paymentOptions.requestPayerPhone` is true.
- `payerEmail` - The payer's email address. This should be a non-empty string when `paymentOptions.requestPayerEmail` is true.
- `shippingAddress` - The user-provided shipping address. This should be a non-empty bundle when `paymentOptions.requestShipping` is true. The bundle should have the following keys which represent different parts in a [physical
  address](https://www.w3.org/TR/contact-picker/#infrastructure-physical-address).
  - `countryCode`
  - `postalCode`
  - `sortingCode`
  - `region`
  - `city`
  - `dependentLocality`
  - `addressLine`
  - `organization`
  - `recipient`
  - `phone` All keys other than the `addressLine` have string values. The `addressLine` is an array of strings.
- `shippingOptionId` - The identifier of the user-selected shipping option. This should be a non-empty string when `paymentOptions.requestShipping` is true.

### Validate payment response

If the activity result of a payment response received from the invoked payment
app is set to `RESULT_OK`, then Chrome will check for required additional
information in its extras. If the validation fails, Chrome will return a rejected
promise from `request.show()` with one of the following developer-facing error
messages:

    'Payment app returned invalid response. Missing field "payerEmail".'
    'Payment app returned invalid response. Missing field "payerName".'
    'Payment app returned invalid response. Missing field "payerPhone".'
    'Payment app returned invalid shipping address in response.'
    '... is not a valid CLDR country code, should be 2 upper case letters [A-Z].'
    'Payment app returned invalid response. Missing field "shipping option".'

The following code sample is an example of a valid response:

### Kotlin

    fun Intent.populateRequestedPaymentOptions() {
        if (requestPayerName) {
            putExtra("payerName", "John Smith")
        }
        if (requestPayerPhone) {
            putExtra("payerPhone", "5555555555")
        }
        if (requestPayerEmail) {
            putExtra("payerEmail", "john.smith@gmail.com")
        }
        if (requestShipping) {
            val address: Bundle = Bundle()
            address.putString("countryCode", "CA")
            val addressLines: Array<String> =
                    arrayOf<String>("111 Richmond st. West")
            address.putStringArray("addressLines", addressLines)
            address.putString("region", "Ontario")
            address.putString("city", "Toronto")
            address.putString("postalCode", "M5H2G4")
            address.putString("recipient", "John Smith")
            address.putString("phone", "5555555555")
            putExtra("shippingAddress", address)
            putExtra("shippingOptionId", "standard")
        }
    }

### Java

    private Intent populateRequestedPaymentOptions() {
        Intent result = new Intent();
        if (requestPayerName) {
            result.putExtra("payerName", "John Smith");
        }
        if (requestPayerPhone) {
            result.putExtra("payerPhone", "5555555555");
        }
        if (requestPayerEmail) {
            result.putExtra("payerEmail", "john.smith@gmail.com");
        }
        if (requestShipping) {
            Bundle address = new Bundle();
            address.putString("countryCode", "CA");
            address.putString("postalCode", "M5H2G4");
            address.putString("region", "Ontario");
            address.putString("city", "Toronto");
            String[] addressLines = new String[] {"111 Richmond st. West"};
            address.putStringArray("addressLines", addressLines);
            address.putString("recipient", "John Smith");
            address.putString("phone", "5555555555");
            result.putExtra("shippingAddress", address);
            result.putExtra("shippingOptionId", "standard");
        }
        return result;
    }

## Optional: Support dynamic flow

Sometimes the total cost of a transaction increases, such as when the user
chooses the express shipping option, or when the list of available shipping
options or their prices changes when the user chooses an international shipping
address. When your app provides the user-selected shipping address or option, it
should be able to notify the merchant about any shipping address or option
changes and show the user the updated payment details (provided by the
merchant).

To notify the merchant about new changes, implement the
`IPaymentDetailsUpdateServiceCallback` interface and declare it in your
`AndroidManifest.xml` with `UPDATE_PAYMENT_DETAILS` intent filter.

Immediately after invoking the `PAY` intent, Chrome will connect to the
`UPDATE_PAYMENT_DETAILS` service (if it exists) in the same package as the
`PAY` intent and will call `setPaymentDetailsUpdateService(service)` to provide
your payment app with the `IPaymentDetailsUpdateService` end-point to notify
about changes in user's payment method, shipping option, or shipping address.

Use `packageManager.getPackagesForUid(Binder.getCallingUid())` when receiving
Inter-Process Communication (IPC) to validate that the app that invoked the
`PAY` intent has the same package name as the app that invoked the
`IPaymentDetailsUpdateServiceCallback` methods.

### AIDL

Create two AIDL files with the following content:

org/chromium/components/payments/IPaymentDetailsUpdateServiceCallback.aidl

    package org.chromium.components.payments;

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

    interface IPaymentDetailsUpdateServiceCallback {
        oneway void updateWith(in Bundle updatedPaymentDetails);

        oneway void paymentDetailsNotUpdated();

        oneway void setPaymentDetailsUpdateService(IPaymentDetailsUpdateService service);
    }

org/chromium/components/payments/IPaymentDetailsUpdateService.aidl

    package org.chromium.components.payments;

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

    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);
    }

### Service

Implement the `IPaymentDetailsUpdateServiceCallback` service.

### Kotlin

    class SampleUpdatePaymentDetailsCallbackService : Service() {
        private val binder = object : IPaymentDetailsUpdateServiceCallback.Stub() {
            override fun updateWith(updatedPaymentDetails: Bundle) {}

            override fun paymentDetailsNotUpdated() {}

            override fun setPaymentDetailsUpdateService(service: IPaymentDetailsUpdateService) {}
        }

        override fun onBind(intent: Intent?): IBinder? {
            return binder
        }
    }

### Java

    import org.chromium.components.payments.IPaymentDetailsUpdateServiceCallback;

    public class SampleUpdatePaymentDetailsCallbackService extends Service {
        private final IPaymentDetailsUpdateServiceCallback.Stub mBinder =
            new IPaymentDetailsUpdateServiceCallback.Stub() {
                @Override
                public void updateWith(Bundle updatedPaymentDetails) {}

                @Override
                public void paymentDetailsNotUpdated() {}

                @Override
                public void setPaymentDetailsUpdateService(IPaymentDetailsUpdateService service) {}
            };

        @Override
        public IBinder onBind(Intent intent) {
            return mBinder;
        }
    }

### `AndroidManifest.xml`

Expose the service for `IPaymentDetailsUpdateServiceCallback` in your
`AndroidManifest.xml`.

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

### Notify the merchant about changes in the user selected payment method, shipping address, or shipping option

### Kotlin

    try {
        if (isOptionChange) {
            service?.changeShippingOption(selectedOptionId, callback)
        } else if (isAddressChange) {
            service?.changeShippingAddress(selectedAddress, callback)
        } else {
            service?.changePaymentMethod(methodData, callback)
        }
    } catch (e: RemoteException) {
        // Handle the remote exception
    }

### Java

    if (service == null) {
      return;
    }

    try {
        if (isOptionChange) {
            service.changeShippingOption(selectedOptionId, callback);
        } else if (isAddressChange) {
            service.changeShippingAddress(selectedAddress, callback);
        } else {
            service.changePaymentMethod(methodData, callback);
        }
    } catch (RemoteException e) {
        // Handle the remote exception
    }

#### `changePaymentMethod`

Notifies the merchant about changes in the user-selected payment method. The
`paymentHandlerMethodData` bundle contains `methodName` and optional `details`
keys both with string values. Chrome will check for a non-empty bundle with a
non-empty `methodName` and send an `updatePaymentDetails` with one of the
following error messages using `callback.updateWith` if the validation fails.

    'Method data required.'
    'Method name required.'

#### `changeShippingOption`

Notifies the merchant about changes in the user-selected shipping option.
`shippingOptionId` should be the identifier of one of the merchant-specified
shipping options. Chrome will check for a non-empty `shippingOptionId` and send
an `updatePaymentDetails` with the following error message using
`callback.updateWith` if the validation fails.

    'Shipping option identifier required.'

#### `changeShippingAddress`

Notifies the merchant about changes in the user-provided shipping address. Chrome
will check for a non-empty `shippingAddress` bundle with a valid `countryCode`
and send an `updatePaymentDetails` with the following error message using
`callback.updateWith` if the validation fails.

    'Payment app returned invalid shipping address in response.'

#### Invalid state error message

If Chrome encounters an invalid state upon receiving any of the change requests
it will call `callback.updateWith` with a redacted `updatePaymentDetails`
bundle. The bundle will only contain the `error` key with `"Invalid state"`.
Examples of an invalid state are:

- When Chrome is still waiting for the merchant's response to a previous change (such as an ongoing change event).
- The payment-app-provided shipping option identifier does not belong to any of the merchant-specified shipping options.

### Receive updated payment details from the merchant

### Kotlin

    override fun updateWith(updatedPaymentDetails: Bundle) {}

    override fun paymentDetailsNotUpdated() {}

### Java

    @Override
    public void updateWith(Bundle updatedPaymentDetails) {}

    @Override
    public void paymentDetailsNotUpdated() {}

`updatedPaymentDetails` is the bundle equivalent to the
[`PaymentRequestDetailsUpdate`](https://w3c.github.io/payment-handler/#the-paymentrequestdetailsupdate)
[WebIDL](https://webidl.spec.whatwg.org/) dictionary and contains the following
optional keys:

- `total` - A bundle containing `currency` and `value` keys, both keys have string values
- `shippingOptions` - The parcelable array of [shipping
  options](https://web.dev/articles/android-payment-apps-delegation#shipping-options)
- `error` - A string containing a generic error message (e.g. when `changeShippingOption` does not provide a valid shipping option identifier)
- `stringifiedPaymentMethodErrors` - A JSON string representing validation errors for the payment method
- `addressErrors` - A bundle with optional keys identical to [shipping
  address](https://web.dev/articles/android-payment-apps-delegation#provide_required_information_in_a_payment_response) and string values. Each key represents a validation error related to its corresponding part of the shipping address.
- `modifiers` - A parcelable array of Bundles, each with a `total` and a `methodData` field, which are also Bundles.

An absent key means its value has not changed.