Android 付款應用程式開發人員指南

瞭解如何調整 Android 付款應用程式,使其與網路付款功能搭配使用,為客戶提供更優質的使用者體驗。

Payment Request API 透過瀏覽器內建瀏覽器介面,提供使用者以前所未有的輕鬆方式輸入必要的付款資訊。此 API 也可以叫用特定平台的付款應用程式。

瀏覽器支援

  • 60
  • 15
  • 11.1

來源

透過採用 Web Payments 的平台專屬 Google Pay 應用程式結帳流程。

相較於僅使用 Android 意圖,使用網路付款功能可進一步整合瀏覽器、安全性和使用者體驗:

  • 付款應用程式是在商家網站上以互動模式啟動。
  • 實作可以補充現有的付款應用程式,讓您充分利用使用者族群。
  • 系統會檢查付款應用程式的簽章,避免側載
  • 付款應用程式支援多種付款方式。
  • 任何付款方式 (例如加密貨幣、銀行轉帳等) 都可以整合。Android 裝置上的付款應用程式甚至可以整合需要存取裝置硬體晶片的方法。

在 Android 付款應用程式中實作 Web Payments 需要四個步驟:

  1. 讓商家找到你的付款應用程式。
  2. 告知商家客戶是否已有可以用來付款的已註冊付款方式 (例如信用卡)。
  3. 讓客戶付款。
  4. 驗證呼叫端的簽署憑證。

如要查看 Web Payments 的實際應用情形,請參閱 android-web-payment 示範。

步驟 1:讓商家找到您的付款應用程式

商家必須透過 Payment Request API,並使用付款方式 ID 指定您支援的付款方式,才能使用您的付款應用程式。

如果您有付款應用程式專用的付款方式 ID,可以設定自己的付款方式資訊清單,讓瀏覽器能找到您的應用程式。

步驟 2:通知商家客戶是否已購買可付款的付款方式

商家可以呼叫 hasEnrolledInstrument()查詢客戶是否能付款。您可以將 IS_READY_TO_PAY 實作為 Android 服務,來回答這項查詢。

AndroidManifest.xml

使用 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 服務為選用項目。如果付款應用程式中沒有這類意圖處理常式,網路瀏覽器就會假設應用程式隨時可以付款。

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 意圖 PAY,透過意圖參數中的交易資訊叫用付款應用程式。

付款應用程式會回應 methodNamedetails,這是付款應用程式專屬的回應,對瀏覽器不透明。瀏覽器會透過 JSON 去序列化將 details 字串轉換為商家的 JavaScript 物件,但不會強制執行之後的任何有效性。瀏覽器不會修改 details,而是直接傳遞至商家。

AndroidManifest.xml

具有 PAY 意圖篩選器的活動應包含 <meta-data> 標記,用於識別應用程式的預設付款方式 ID

如要支援多種付款方式,請新增含有 <string-array> 資源的 <meta-data> 標記。

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

參數

系統會以意圖額外項目的形式將下列參數傳送至活動:

  • 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

沒有配置 (頂層瀏覽結構定義的無配置來源) 的商家來源。例如,https://mystore.com/checkout 會以 mystore.com 的形式傳遞。

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

topLevelCertificateChain

商家的憑證鏈結 (頂層瀏覽情境的憑證鏈結)。localhost 和磁碟上的檔案為空值,這些都是沒有 SSL 憑證的安全內容。每個 Parcelable 都是含有 certificate 鍵和位元組陣列值的 Bundle。

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

「push-payment」應用程式的 PaymentRequest.id 欄位應與交易狀態建立關聯。商家網站會使用這個欄位查詢「推送付款」應用程式是否有頻外交易的狀態。

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

回覆

活動可以透過 RESULT_OK 透過 setResult 傳回回應。

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

您必須將兩個參數指定為意圖額外項目:

  • methodName:使用方法的名稱。
  • details:JSON 字串,包含商家完成交易所需的資訊。如果成功是 true,則必須以 JSON.parse(details) 成功的方式建構 details

如果交易未在付款應用程式中完成,例如使用者在付款應用程式中輸入帳戶的正確 PIN 碼,則可以傳遞 RESULT_CANCELED。瀏覽器可能會讓使用者選擇其他付款應用程式。

setResult(RESULT_CANCELED)
finish()

如果所叫用付款應用程式收到的付款回應設為 RESULT_OK,Chrome 會在額外項目中檢查非空白的 methodNamedetails。如果驗證失敗,Chrome 會從 request.show() 傳回遭拒的承諾,並顯示下列開發人員顯示的錯誤訊息:

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

權限

活動可以透過 getCallingPackage() 方法檢查呼叫端。

val caller: String? = callingPackage

最後一步是驗證呼叫端的簽署憑證,確認呼叫套件是否具備正確的簽章。

步驟 4:驗證呼叫端的簽署憑證

您可以在 IS_READY_TO_PAY 中使用 Binder.getCallingUid()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 具有單一簽署憑證)。擁有多個簽署憑證的應用程式無法輪替憑證。

如果您需要支援 27 以下版本的舊版 API,或是需要處理具有多個簽署憑證的瀏覽器,可以使用 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) } }