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

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

薩赫爾放大
Sahel Sharify

透過網路表單輸入運送地址和聯絡資訊,會對客戶造成困擾。可能會造成錯誤並降低轉換率。

因此,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/supported_delegations" />
</activity>

<resource> 必須是從以下有效值中選擇的字串清單:

[ "payerName", "payerEmail", "payerPhone", "shippingAddress" ]

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

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

剖析必要付款方式的 PAY 意圖額外項目

商家可以使用 paymentOptions 字典指定其他必要資訊。Chrome 會將下列參數做為意圖額外項目傳遞給 PAY 活動,以提供應用程式可提供的必要選項清單。

paymentOptions

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

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

當中可包含下列參數:

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

shippingOptions

shippingOptions 是商家指定運送選項的可封裝陣列。這個參數只會在 paymentOptions.requestShipping == true 時存在。

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

每個運送選項都是含有以下鍵的 Bundle

  • id:運送選項 ID。
  • label:向使用者顯示的運送選項標籤。
  • amount:包含 currencyvalue 鍵且採用字串值的運費套裝組合。
    • currency 會顯示運費幣別,格式為 3 個字母的 ISO4217 格式字母代碼
    • value 會顯示運費的值,顯示為有效的小額金額
  • selected - 當付款應用程式顯示運送選項時,是否要選取運送選項。

selected 以外的所有鍵都含有字串值。selected 包含布林值。

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)

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

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

如要這麼做,您必須將下列參數指定為意圖額外項目:

  • payerName - 付款人的全名。如果 paymentOptions.requestPayerName 為 true,這個字串應為非空白字串。
  • payerPhone - 付款人的電話號碼。如果 paymentOptions.requestPayerPhone 為 true,這個字串應為非空白字串。
  • payerEmail - 付款人的電子郵件地址。如果 paymentOptions.requestPayerEmail 為 true,這個字串應該是非空白字串。
  • shippingAddress:使用者提供的運送地址。當 paymentOptions.requestShipping 為 true 時,這應該是非空白套件。套件應包含下列鍵,代表實際位址內的不同部分。
    • city
    • countryCode
    • dependentLocality
    • organization
    • phone
    • postalCode
    • recipient
    • region
    • sortingCode
    • addressLine 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".'

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

fun Intent.populateRequestedPaymentOptions() {
    if (requestPayerName) {
        putExtra("payerName", "John Smith")
    }
    if (requestPayerPhone) {
        putExtra("payerPhone", "4169158200")
    }
    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", "4169158200")
        putExtra("shippingAddress", address)
        putExtra("shippingOptionId", "standard")
    }
}

選用:支援動態流程

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

AIDL

如要通知商家有新的變更,請使用 Chrome 的 AndroidManifest.xml 中宣告的 PaymentDetailsUpdateService 服務。如要使用這項服務,請建立兩個含有以下內容的 AIDL 檔案:

app/src/main/aidl/org/chromium/components/payments/IPaymentDetailsUpdateService

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

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

    oneway void paymentDetailsNotUpdated();
}

app/src/main/aidl/org/chromium/components/payments/IPaymentDetailsUpdateServiceCallback

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

通知商家使用者所選付款方式、運送地址或運送選項有所異動

private fun bind() {
    // The action is introduced in Chrome version 92, which supports the service in Chrome
    // and other browsers (e.g., WebLayer).
    val newIntent = Intent("org.chromium.intent.action.UPDATE_PAYMENT_DETAILS")
        .setPackage(callingBrowserPackage)
    if (packageManager.resolveService(newIntent, PackageManager.GET_RESOLVED_FILTER) == null) {
        // Fallback to Chrome-only approach.
        newIntent.setClassName(
            callingBrowserPackage,
            "org.chromium.components.payments.PaymentDetailsUpdateService")
        newIntent.action = IPaymentDetailsUpdateService::class.java.name
    }
    isBound = bindService(newIntent, connection, Context.BIND_AUTO_CREATE)
}

private val connection = object : ServiceConnection {
    override fun onServiceConnected(className: ComponentName, service: IBinder) {
        val service = IPaymentDetailsUpdateService.Stub.asInterface(service)
        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
        }
    }
}

用於服務啟動意圖的 callingPackageName 可能包含下列其中一個值,具體視啟動付款要求的瀏覽器而定。

Chrome 版本 套件名稱
穩定 "com.android.chrome"
Beta 版 "com.chrome.beta"
開發 "com.chrome.dev"
Canary 版 "com.chrome.canary"
Chromium "org.chromium.chrome"
Google 快速搜尋框 (WebLayer 嵌入程式) "com.google.android.googlequicksearchbox"

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。套件只會包含具有 "Invalid state"error 鍵。無效狀態的例子如下:

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

接收商家的最新付款資料

private fun unbind() {
    if (isBound) {
        unbindService(connection)
        isBound = false
    }
}

private val callback: IPaymentDetailsUpdateServiceCallback =
    object : IPaymentDetailsUpdateServiceCallback.Stub() {
        override fun paymentDetailsNotUpdated() {
            // Payment request details have not changed.
            unbind()
        }

        override fun updateWith(updatedPaymentDetails: Bundle) {
            newPaymentDetails = updatedPaymentDetails
            unbind()
        }
    }

updatePaymentDetails 相當於 PaymentRequestDetailsUpdate WebIDL 字典 (遮蓋 modifiers 欄位後) 的套件,且包含以下選用索引鍵:

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

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