从 Android 支付应用提供送货和联系信息

如何更新您的 Android 支付应用,以便通过 Web Payments API 提供送货地址和付款人的联系信息。

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

这就是 Payment Request API 支持请求送货地址和联系信息功能的原因。这提供了多项优势

  • 用户只需轻点几下即可选择正确的地址。
  • 地址始终以标准化格式返回。
  • 提交不正确地址的可能性更低。

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

在可能的情况下,Chrome 会将客户送货地址和联系信息的收集委托给调用的 Android 支付应用。委托减少了结账过程中的摩擦。

商家网站可以根据客户选择的送货地址和送货选项动态更新送货选项和总价。

送货选项和送货地址更改生效。了解它如何动态影响送货选项和总价。

要为已有的 Android 支付应用添加委托支持,请执行以下步骤

  1. 声明受支持的委托.
  2. 解析 PAY intent extras 以获取所需的支付选项.
  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 extras 以获取所需的支付选项

商家可以使用 paymentOptions 字典指定其他所需信息。Chrome 将通过将以下参数作为 Intent extras 传递给 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"。您的应用可以在其 UI 中使用此提示,在询问用户的地址或送货选项选择时。

shippingOptions

shippingOptions 是商家指定的送货选项的可 Parcel 化数组。此参数仅在 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 extras

  • 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
    • addressLineaddressLine 之外的所有键都具有字符串值。 addressLine 是字符串数组。
  • shippingOptionId - 用户选择的送货选项的标识符。当 paymentOptions.requestShipping 为 true 时,这应为非空字符串。

验证支付响应

如果从调用的支付应用收到的支付响应的 activity 结果设置为 RESULT_OK,则 Chrome 将检查其 extras 中所需的其他信息。如果验证失败,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"
Dev 版 "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 将检查是否有一个非空 shippingAddress 捆绑包,其中包含有效的 countryCode ,如果验证失败,则通过 callback.updateWith 发送带有以下错误消息的 updatePaymentDetails

'Payment app returned invalid shipping address in response.'

无效状态错误消息

如果 Chrome 在收到任何更改请求时遇到无效状态,它将使用经过编辑的 updatePaymentDetails 捆绑包调用 callback.updateWith。该捆绑包将仅包含 error 键,值为 "Invalid state"。无效状态的示例包括

  • 当 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()
        }
    }

updatePaymentDetailsPaymentRequestDetailsUpdate WebIDL 字典的捆绑包等效项(在编辑 modifiers 字段后),并包含以下可选键

  • total - 包含 currencyvalue 键的捆绑包,两个键都具有字符串值
  • shippingOptions - 送货选项的可 Parcel 化数组
  • error - 包含通用错误消息的字符串(例如,当 changeShippingOption 未提供有效的送货选项标识符时)
  • stringifiedPaymentMethodErrors - 表示支付方式验证错误的 JSON 字符串
  • addressErrors - 包含可选键的捆绑包,这些键与送货地址和字符串值相同。每个键表示与其送货地址的相应部分相关的验证错误。

缺少的键表示其值未更改。