Android 付款应用开发者指南

了解如何调整您的 Android 付款应用,使其支持网络付款,并为客户提供更好的用户体验。

Payment Request API 将 提供了基于浏览器的内置界面,用户可以在其中输入所需付款 更轻松地获取信息。API 还可以调用平台专用付款 。

浏览器支持

  • Chrome:60。 <ph type="x-smartling-placeholder">
  • Edge:15。 <ph type="x-smartling-placeholder">
  • Firefox:背后有旗帜。
  • Safari:11.1. <ph type="x-smartling-placeholder">

来源

<ph type="x-smartling-placeholder">
</ph>
针对具体平台且使用 Web Payments 的 Google Pay 应用的结账流程。

与仅使用 Android Intent 相比,Web Payments 可实现更好的集成 安全性和用户体验方面:

  • 付款应用作为模态在商家网站环境中启动。
  • 实现是对现有付款应用的补充,使您能够: 充分利用您的用户群
  • 系统会检查付款应用的签名,以防止 旁加载
  • 付款应用可以支持多种付款方式。
  • 任何付款方式(例如加密货币、银行转账等)都可以 集成Android 设备上的付款应用甚至可以集成 需要访问设备上的硬件芯片。

在 Android 付款应用中实现 Web Payments 需要执行四个步骤:

  1. 让商家发现您的付款应用。
  2. 告知商家客户是否已注册付款方式(例如赠金) 卡)。
  3. 让客户付款。
  4. 验证调用方的签名证书。

如需了解在线付款的实际运作情况,请参阅 android-web-payment 演示。

第 1 步:让商家发现您的付款应用

商家要想使用您的付款应用,需要使用付款 请求 API 和 使用付款方式指定您支持的付款方式 标识符

如果您有付款应用独有的付款方式标识符, 可以设置自己的付款方式 清单,让浏览器可以 。

第 2 步:告知商家客户是否有可以付款的已注册付款方式

商家可以调用 hasEnrolledInstrument()查询客户 能够付款。您可以 实现 IS_READY_TO_PAY 作为 Android 服务来回答此查询。

AndroidManifest.xml

使用具有该操作的 intent 过滤器声明您的服务 org.chromium.intent.action.IS_READY_TO_PAY

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

IS_READY_TO_PAY 服务是可选的。如果 中没有此类 intent 处理程序, 付款应用,那么网络浏览器会假定该应用可以随时 付款。

AIDL

IS_READY_TO_PAY 服务的 API 是使用 AIDL 定义的。创建两个 AIDL 文件的内容:

app/src/main/aidl/org/chromium/IsReadyToPayServiceCallback.aidl

package org.chromium;
interface IsReadyToPayServiceCallback {
    oneway void handleIsReadyToPay(boolean isReadyToPay);
}

app/src/main/aidl/org/chromium/IsReadyToPayService.aidl

package org.chromium;
import org.chromium.IsReadyToPayServiceCallback;

interface IsReadyToPayService {
    oneway void isReadyToPay(IsReadyToPayServiceCallback callback);
}

实施 IsReadyToPayService

以下代码展示了最简单的 IsReadyToPayService 实现 示例:

class SampleIsReadyToPayService : Service() {
  private val binder = object : IsReadyToPayService.Stub() {
    override fun isReadyToPay(callback: IsReadyToPayServiceCallback?) {
      callback?.handleIsReadyToPay(true)
    }
  }

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

响应

服务可以通过 handleIsReadyToPay(Boolean) 方法发送其响应。

callback?.handleIsReadyToPay(true)

权限

您可以使用 Binder.getCallingUid() 检查来电者的身份。请注意, 必须在 isReadyToPay 方法中执行此操作,而不是在 onBind 方法中执行。

override fun isReadyToPay(callback: IsReadyToPayServiceCallback?) {
  try {
    val callingPackage = packageManager.getNameForUid(Binder.getCallingUid())
    // …

请参阅验证调用方的签名证书,了解如何操作 以验证发出调用的软件包是否具有正确的签名。

第 3 步:让客户付款

商家调用 show()启动付款 应用 以便客户进行付款通过 Android 调用付款应用 intent PAY,并在 intent 参数中包含交易信息。

付款应用会返回 methodNamedetails,它们是付款应用 对于浏览器来说是不透明的浏览器将 details 字符串转换为商家的 JavaScript 对象,但 除此之外,不强制执行任何有效性。浏览器不会修改 details;该参数的值会直接传递给商家。

AndroidManifest.xml

具有 PAY intent 过滤器的 activity 应该有一个 <meta-data> 标记 用于标识 应用

要支持多种付款方式,请添加 <meta-data> 标记并 <string-array> 资源。

<activity
  android:name=".PaymentActivity"
  android:theme="@style/Theme.SamplePay.Dialog">
  <intent-filter>
    <action android:name="org.chromium.intent.action.PAY" />
  </intent-filter>

  <meta-data
    android:name="org.chromium.default_payment_method_name"
    android:value="https://bobbucks.dev/pay" />
  <meta-data
    android:name="org.chromium.payment_method_names"
    android:resource="@array/method_names" />
</activity>

resource 必须是字符串列表,其中每个字符串都必须是有效的 采用 HTTPS 架构的绝对网址,如下所示。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="method_names">
        <item>https://alicepay.com/put/optional/path/here</item>
        <item>https://charliepay.com/put/optional/path/here</item>
    </string-array>
</resources>

参数

以下参数会作为 intent extra 传递给 activity:

  • methodNames
  • methodData
  • topLevelOrigin
  • topLevelCertificateChain
  • paymentRequestOrigin
  • total
  • modifiers
  • paymentRequestId
val extras: Bundle? = intent?.extras

methodNames

使用的方法的名称。这些元素是 methodData字典。以下是付款应用支持的付款方式。

val methodNames: List<String>? = extras.getStringArrayList("methodNames")

methodData

每个 methodNamesmethodData

val methodData: Bundle? = extras.getBundle("methodData")

merchantName

商家结账页的 <title> HTML 标记的内容( 浏览的顶级浏览上下文)。

val merchantName: String? = extras.getString("merchantName")

topLevelOrigin

不带 scheme 的商家来源( 顶级浏览上下文)。例如,https://mystore.com/checkout 是 以 mystore.com 的形式传递。

val topLevelOrigin: String? = extras.getString("topLevelOrigin")

topLevelCertificateChain

商家的证书链(顶级证书链) 浏览上下文)。对于 localhost 和磁盘上的文件为 null,它们都是安全的 没有 SSL 证书的上下文。每个 Parcelable 都是一个包含 certificate 键和一个字节数组值。

val topLevelCertificateChain: Array<Parcelable>? =
    extras.getParcelableArray("topLevelCertificateChain")
val list: List<ByteArray>? = topLevelCertificateChain?.mapNotNull { p ->
  (p as Bundle).getByteArray("certificate")
}

paymentRequestOrigin

在 JavaScript 中调用 new PaymentRequest(methodData, details, options) 构造函数的 iframe 浏览上下文的无传输来源。如果 构造函数,那么此构造函数的值将从顶级上下文中调用, 参数等于 topLevelOrigin 参数的值。

val paymentRequestOrigin: String? = extras.getString("paymentRequestOrigin")

total

表示交易总金额的 JSON 字符串。

val total: String? = extras.getString("total")

以下是该字符串的示例内容:

{"currency":"USD","value":"25.00"}

modifiers

JSON.stringify(details.modifiers) 的输出,其中 details.modifiers 仅包含 supportedMethodstotal

paymentRequestId

PaymentRequest.id字段,其中“push-payment”应用应该与 交易状态商家网站将使用此字段查询 &quot;push-payment&quot;带外交易状态的应用。

val paymentRequestId: String? = extras.getString("paymentRequestId")

响应

activity 可以使用 RESULT_OK 通过 setResult 发回其响应。

setResult(Activity.RESULT_OK, Intent().apply {
  putExtra("methodName", "https://bobbucks.dev/pay")
  putExtra("details", "{\"token\": \"put-some-data-here\"}")
})
finish()

您必须将两个参数指定为 intent extra:

  • methodName:所用方法的名称。
  • details:JSON 字符串,包含商家 以便完成交易如果成功为 true,则 details 必须为 以使 JSON.parse(details) 能够成功。

如果交易未在以下期限内完成,您可以传递 RESULT_CANCELED 例如,如果用户未能为付款应用输入正确的 PIN 码 在付款应用中访问自己的账号。浏览器可能会让用户选择 付款应用。

setResult(RESULT_CANCELED)
finish()

如果从调用的付款收到付款响应的活动结果 应用设置为 RESULT_OK,那么 Chrome 会检查 methodName 是否不为空, details。如果验证失败,Chrome 会返回“已拒绝” promise 中包含下列面向开发者的错误之一(由 request.show() 提供) 消息:

'Payment app returned invalid response. Missing field "details".'
'Payment app returned invalid response. Missing field "methodName".'

权限

activity 可以使用其 getCallingPackage() 方法检查调用方。

val caller: String? = callingPackage

最后一步是验证调用方的签名证书, 调用的软件包具有正确的签名。

第 4 步:验证调用方的签名证书

您可以使用 Binder.getCallingUid() 检查调用方的软件包名称: IS_READY_TO_PAY,以及在PAY中有 Activity.getCallingPackage() 的用户。为此, 验证调用方是否是您所希望的浏览器,您应该 请检查其签名证书,并确保它与正确的 值。

如果您以 API 级别 28 及更高级别为目标平台,并且与浏览器集成 因此您可以使用 PackageManager.hasSigningCertificate()

val packageName: String =  // The caller's package name
val certificate: ByteArray =  // The correct signing certificate.
val verified = packageManager.hasSigningCertificate(
  callingPackage,
  certificate,
  PackageManager.CERT_INPUT_SHA256
)

对于单个证书,首选 PackageManager.hasSigningCertificate() 因为它可正确处理证书轮替。(Chrome 具有 单个签名证书。)具有多个签名证书的应用无法 旋转它们。

如果您需要支持更低的 API 级别 27 及更低级别,或者需要处理 有多个签名证书的浏览器,您可以使用 PackageManager.GET_SIGNATURES

val packageName: String =  // The caller's package name
val certificates: Set<ByteArray> =  // The correct set of signing certificates

val packageInfo = getPackageInfo(packageName, PackageManager.GET_SIGNATURES)
val sha256 = MessageDigest.getInstance("SHA-256")
val signatures = packageInfo.signatures.map { sha256.digest(it.toByteArray()) }
val verified = signatures.size == certificates.size &&
    signatures.all { s -> certificates.any { it.contentEquals(s) } }