透過 Android 付款應用程式提供運送和聯絡資訊

如何更新 Android 付款應用程式,以便透過 Web Payments API 提供運送地址和付款者的聯絡資訊。

Sahel Sharify
Sahel Sharify

發布日期:2020 年 7 月 17 日,上次更新日期:2025 年 5 月 27 日

透過網路表單輸入運送地址和聯絡資訊,對消費者來說可能會很麻煩。這可能會導致錯誤並降低轉換率。

因此,Payment Request API 支援要求運送地址和聯絡資訊的功能。這麼做有以下好處:

  • 使用者只要輕觸幾下,就能選取正確的地址。
  • 系統一律會以標準格式傳回地址。
  • 提交錯誤地址的可能性較低。

瀏覽器可以將收貨地址和聯絡資訊的收集工作延後交由付款應用程式處理,以提供統一的付款體驗。這項功能稱為委派

在可行情況下,Chrome 會將收集客戶運送地址和聯絡資訊的作業,委派給叫用的 Android 付款應用程式。委派作業可減少結帳時的摩擦。

商家網站可根據消費者選擇的運送地址和運送選項,動態更新運送選項和總價。

運送選項和運送地址變更的實際情況。瞭解這項功能如何動態影響運送選項和總價。

如要在現有的 Android 付款應用程式中新增委派支援功能,請實作下列步驟:

  1. 宣告支援的委派作業
  2. 剖析 PAY 意圖額外資料,以便取得必要的付款選項
  3. 在付款回應中提供必要資訊
  4. [選用] 支援動態流程
    1. 通知商家使用者所選付款方式、運送地址或運送選項的變更
    2. 接收商家提供的更新付款詳細資料 (例如,根據所選運送選項的費用調整的總金額)

宣告支援的委派作業

瀏覽器需要知道付款應用程式可提供的其他資訊清單,才能將該資訊的收集工作委派給您的應用程式。請在應用程式的 AndroidManifest.xml 中,將支援的委派作業宣告為 <meta-data>

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

android:resource 必須指向包含下列值全部或部分的 <string-array>

  • payerName
  • payerEmail
  • payerPhone
  • shippingAddress

以下範例只能提供運送地址和付款者的電子郵件地址。

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

剖析 PAY 意圖額外項目,以便提供必要的付款選項

商家可以使用 paymentOptions 字典指定其他必要資訊。Chrome 會將 paymentOptions Intent 額外項目傳遞至 PAY 活動,並提供應用程式可提供的必要選項清單。

paymentOptions

paymentOptions 是商家指定付款方式的子集,您的應用程式已宣告委派支援。

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

可包含下列參數:

  • requestPayerName - 布林值,指出是否需要付款人姓名。
  • requestPayerPhone:布林值,指出是否需要付款者的手機。
  • requestPayerEmail:布林值,指出是否需要付款者的電子郵件地址。
  • requestShipping:布林值,指出是否需要運送資訊。
  • shippingType - 顯示運送類型的字串。運送類型可以是 "shipping""delivery""pickup"。應用程式在要求使用者提供地址或選擇運送選項時,可以在 UI 中使用這項提示。

shippingOptions

shippingOptions 是商家指定運送選項的可分割陣列。這個參數只會在 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;
  }
}

每個運送選項都是 Bundle,其中包含下列鍵。

  • id:運送選項 ID。
  • label:向使用者顯示的運送選項標籤。
  • amount:運費組合,其中包含 currencyvalue 鍵,以及字串值。
  • selected:付款應用程式顯示運送選項時,是否應選取運送選項。

除了 selected 以外,所有鍵都具有字串值。selected 有布林值。

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

在付款回應中提供必要資訊

應用程式應在對 PAY 活動的回應中,加入必要的額外資訊。

為此,您必須將下列參數指定為 Intent 額外資訊:

  • payerName:付款人全名。如果 paymentOptions.requestPayerName 為 true,則此值應為非空白字串。
  • payerPhone:付款者的電話號碼。如果 paymentOptions.requestPayerPhone 為 true,則此值應為非空白字串。
  • payerEmail:付款者的電子郵件地址。當 paymentOptions.requestPayerEmail 為 true 時,此值應為非空白字串。
  • shippingAddress - 使用者提供的運送地址。當 paymentOptions.requestShipping 為 true 時,此應為非空的 bundle。套件應包含以下鍵,代表實體位址中的不同部分。
    • countryCode
    • postalCode
    • sortingCode
    • region
    • city
    • dependentLocality
    • addressLine
    • organization
    • recipient
    • phone 除了 addressLine 以外,所有鍵都具有字串值。addressLine 是字串陣列。
  • shippingOptionId:使用者選取的運送選項 ID。如果 paymentOptions.requestShipping 為 true,則此值應為非空白字串。

驗證付款回應

如果從叫用的付款應用程式收到的付款回應活動結果設為 RESULT_OK,Chrome 會檢查其額外內容中是否有需要的額外資訊。如果驗證失敗,Chrome 會從 request.show() 傳回已拒絕的承諾,並顯示下列其中一個面向開發人員的錯誤訊息:

'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".'

以下程式碼範例是有效回應的範例:

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) {
        presult.utExtra("payerPhone", "5555555555");
    }
    if (requestPayerEmail) {
        result.putExtra("payerEmail", "john.smith@gmail.com");
    }
    if (requestShipping) {
        Bundle address = new Bundle();
        address.putExtra("countryCode", "CA");
        address.putExtra("postalCode", "M5H2G4");
        address.putExtra("region", "Ontario");
        address.putExtra("city", "Toronto");
        String[] addressLines = new String[] {"111 Richmond st. West"};
        address.putExtra("addressLines", addressLines);
        address.putExtra("recipient", "John Smith");
        address.putExtra("phone", "5555555555");
        result.putExtra("shippingAddress", address);
        result.putExtra("shippingOptionId", "standard");
    }
    return result;
}

選用:支援動態流程

有時交易總費用會增加,例如使用者選擇快遞運送選項,或是使用者選擇國際運送地址時,可用的運送選項清單或價格會有所變動。當應用程式提供使用者選取的運送地址或選項時,應可通知商家任何運送地址或選項變更,並向使用者顯示更新的付款詳細資料 (由商家提供)。

如要通知商家新變更,請實作 IPaymentDetailsUpdateServiceCallback 介面,並在 AndroidManifest.xml 中使用 UPDATE_PAYMENT_DETAILS 意圖篩選器宣告該介面。

在叫用 PAY 意圖後,Chrome 會立即連線至與 PAY 意圖相同套件中的 UPDATE_PAYMENT_DETAILS 服務 (如果有),並呼叫 setPaymentDetailsUpdateService(service),為付款應用程式提供 IPaymentDetailsUpdateService 端點,以便通知使用者付款方式、運送選項或運送地址的變更。

接收進程間通訊 (IPC) 時,請使用 packageManager.getPackagesForUid(Binder.getCallingUid()) 驗證叫用 PAY 意圖的應用程式,其套件名稱是否與叫用 IPaymentDetailsUpdateServiceCallback 方法的應用程式相同。

AIDL

請建立兩個 AIDL 檔案,並加入以下內容:

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

服務

實作 IPaymentDetailsUpdateServiceCallback 服務。

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.paymsnts.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

AndroidManifest.xml 中公開 IPaymentDetailsUpdateServiceCallback 的服務。

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

通知商家使用者所選付款方式、運送地址或運送選項的變更

Kotlin

try {
    if (isOptionChange) {
        service?.changeShippingOption(selectedOptionId, callback)
    } else (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 (isAddressChange) {
        service.changeShippingAddress(selectedAddress, callback);
    } else {
        service.changePaymentMethod(methodData, callback);
    }
} catch (RemoteException e) {
    // Handle the remote exception
}

changePaymentMethod

通知商家使用者所選付款方式的變更。paymentHandlerMethodData 組合包含 methodName 和選用的 details 鍵,兩者都含有字串值。如果驗證失敗,Chrome 會檢查是否有非空的 methodName 和非空的套件,並透過 callback.updateWith 傳送含有下列錯誤訊息之一的 updatePaymentDetails

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

changeShippingOption

通知商家使用者所選運送選項的變更。shippingOptionId 應為商家指定的運送選項之一的 ID。如果驗證失敗,Chrome 會檢查是否有非空的 shippingOptionId,並透過 callback.updateWith 傳送 updatePaymentDetails,其中包含下列錯誤訊息。

'Shipping option identifier required.'

changeShippingAddress

通知商家使用者提供的運送地址有變更。Chrome 會檢查是否有使用有效 countryCode 的非空 shippingAddress 套件,如果驗證失敗,就會透過 callback.updateWith 傳送 updatePaymentDetails,並附上以下錯誤訊息。

'Payment app returned invalid shipping address in response.'

無效狀態錯誤訊息

如果 Chrome 在收到任何變更要求時遇到無效狀態,就會使用經過遮蓋的 updatePaymentDetails 套件呼叫 callback.updateWith。組合只會包含 error 索引鍵和 "Invalid state"。無效狀態的例子包括:

  • Chrome 仍在等待商家回應先前的變更 (例如進行中的變更事件)。
  • 付款應用程式提供的運送選項 ID 不屬於任何商家指定的運送選項。

接收商家提供的最新付款詳細資料

Kotlin

override fun updateWith(updatedPaymentDetails: Bundle) {}

override fun paymentDetailsNotUpdated() {}

Java

@Override
public void updateWith(Bundle updatedPaymentDetails) {}

@Override
public void paymentDetailsNotUpdated() {}

updatedPaymentDetails 是等同於 PaymentRequestDetailsUpdate WebIDL 字典的組合,並包含下列選用鍵:

  • total:包含 currencyvalue 鍵的組合,兩個鍵都具有字串值
  • shippingOptions運送選項的 parcelable 陣列
  • error:包含一般錯誤訊息的字串 (例如 changeShippingOption 未提供有效的運送選項 ID)
  • stringifiedPaymentMethodErrors:代表付款方式驗證錯誤的 JSON 字串
  • addressErrors:包含選用鍵的組合,與運送地址和字串值相同。每個鍵都代表與運送地址對應部分相關的驗證錯誤。
  • modifiers:可分割的 Bundle 陣列,每個 Bundle 都包含 totalmethodData 欄位,這些也是 Bundle。

缺少的鍵代表其值未變更。