通过 Android 付款应用提供送货和联系信息

如何更新 Android 付款应用,以使用 Web Payments API 提供送货地址和付款人联系信息。

萨赫尔·沙里伊 (Sahel Sharify)
Sahel Sharify

对于客户来说,通过网络表单输入送货地址和联系信息可能很麻烦。否则可能会导致错误并降低转化率。

因此,Payment Request API 支持请求送货地址和联系信息的功能。这样做有多种好处:

  • 用户只需点按几下即可选择正确的地址。
  • 地址始终以标准化格式返回。
  • 尽量避免提交错误的地址。

浏览器可以将送货地址和联系信息的收集工作推迟到付款应用,以提供统一的付款体验。此功能称为“委托”。

Chrome 会尽可能将客户送货地址和联系信息的收集委托给调用的 Android 付款应用。这种委托能够让结账过程更顺畅。

商家网站可以动态更新运费选项和总价,具体取决于客户选择的送货地址和运费选项。

运费选项和送货地址更改的实际操作查看它对运费选项和总价的动态有何影响。

如需为现有的 Android 付款应用添加委托支持,请按以下步骤操作:

  1. 声明支持的委托
  2. 解析了所需付款方式的 PAY intent extra
  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 intent extra

商家可以使用 paymentOptions 字典指定其他必需信息。Chrome 会将以下参数作为 Intent extra 传递给 PAY activity,从而提供您的应用能够提供的必需选项列表。

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"。在询问用户的地址或配送选项的选择时,您的应用可以在其界面中使用此提示。

shippingOptions

shippingOptions 是商家指定的运费选项的 parcelable 数组。此参数仅在 paymentOptions.requestShipping == true 时存在。

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

每个运费选项都是包含以下键的 Bundle

  • id - 送货方式标识符。
  • label - 向用户显示的配送选项标签。
  • amount - 运费软件包,其中包含具有字符串值的 currencyvalue 键。
  • 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 activity 的响应中添加所需的额外信息。

为此,必须将以下参数指定为 intent extra:

  • payerName - 付款人的全名。当 paymentOptions.requestPayerName 为 true 时,此字段应为非空字符串。
  • payerPhone - 付款人的电话号码。当 paymentOptions.requestPayerPhone 为 true 时,此字段应为非空字符串。
  • payerEmail - 付款人的电子邮件地址。当 paymentOptions.requestPayerEmail 为 true 时,它应为非空字符串。
  • shippingAddress - 用户提供的送货地址。当 paymentOptions.requestShipping 为 true 时,这应是一个非空 bundle。该软件包应包含以下键,分别表示一个物理地址中的不同部分。
    • city
    • countryCode
    • dependentLocality
    • organization
    • phone
    • postalCode
    • recipient
    • region
    • sortingCode
    • addressLineaddressLine 之外的所有键都具有字符串值。addressLine 是一个字符串数组。
  • shippingOptionId - 用户选择的运费选项的标识符。当 paymentOptions.requestShipping 为 true 时,此项应为非空字符串。

验证付款响应

如果从调用的付款应用收到的付款响应的活动结果被设为 RESULT_OK,Chrome 将在其 extra 中检查所需的其他信息。如果验证失败,Chrome 将从 request.show() 返回被拒绝的 Promise,并包含下列面向开发者的错误消息之一:

'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
        }
    }
}

用于服务的启动 intent 的 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 应该是商家指定的某个运费选项的标识符。如果验证失败,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。该 Bundle 将仅包含带有 "Invalid state"error 键。无效状态的示例如下:

  • Chrome 仍在等待商家对之前的更改(例如正在进行的更改事件)做出响应时。
  • 付款应用提供的运费选项标识符不属于商家指定的任何运费选项。

从商家接收更新后的付款信息

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 - 配送选项的 Parcelable 数组
  • error - 包含一般错误消息的字符串(例如,当 changeShippingOption 未提供有效的配送选项标识符时)
  • stringifiedPaymentMethodErrors - 一个 JSON 字符串,表示付款方式的验证错误
  • addressErrors - 一个软件包,其中包含与送货地址和字符串值完全相同的可选键。每个键表示一个与送货地址的相应部分相关的验证错误。

某个键不存在意味着其值没有更改。