瞭解如何調整 Android 付款應用程式,以便與網路付款功能搭配使用,並為消費者提供更優質的使用者體驗。
Payment Request API 為網頁內建的瀏覽器式介面,可讓使用者以更輕鬆的方式輸入必要資訊。此外,這個 API 也可以叫用平台專屬的付款應用程式。
與僅使用 Android Intents 相比,網路付款功能可以更緊密地與瀏覽器、安全性和使用者體驗整合:
- 根據商家網站,付款應用程式是以強制回應的形式啟動。
- 實作作業可輔助您現有的付款應用程式,助您充分利用使用者族群。
- 系統會檢查付款應用程式的簽名,防止側載。
- 付款應用程式可支援多種付款方式。
- 任何付款方式均可整合,例如加密貨幣和銀行轉帳等。Android 裝置上的付款應用程式甚至可以整合需要存取裝置硬體晶片的方法。
如要在 Android 付款應用程式中實作網路付款功能,需要採取四個步驟:
- 讓商家找到您的付款應用程式。
- 告知商家,顧客有可以付款的付款方式 (例如信用卡) 是否可付款。
- 讓消費者付款。
- 驗證呼叫端的簽署憑證。
如要查看網路付款的應用實例,請查看 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
使用意圖參數中的交易資訊來叫用付款應用程式。
付款應用程式會回應 methodName
和 details
,後者是付款應用程式專屬,且在瀏覽器不透明的情況下。瀏覽器會透過 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
每個 methodNames
與 methodData
的對應關係。
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
無配置來源的 iframe 瀏覽結構定義,在 JavaScript 中叫用 new
PaymentRequest(methodData, details, options)
建構函式。如果建構函式是從頂層結構定義叫用,則這個參數的值會等於 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
僅包含 supportedMethods
和 total
。
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 會在其額外項目中檢查非空白的 methodName
和 details
。如果驗證失敗,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) } }