Tìm hiểu cách điều chỉnh ứng dụng thanh toán Android để hoạt động với tính năng Thanh toán trên web và mang lại trải nghiệm người dùng tốt hơn cho khách hàng.
API Yêu cầu thanh toán mang đến cho web một giao diện tích hợp sẵn dựa trên trình duyệt, cho phép người dùng nhập thông tin thanh toán bắt buộc dễ dàng hơn bao giờ hết. API này cũng có thể gọi các ứng dụng thanh toán dành riêng cho nền tảng.
So với việc chỉ sử dụng Android Intent, Thanh toán web cho phép tích hợp tốt hơn với trình duyệt, tính bảo mật và trải nghiệm người dùng:
- Ứng dụng thanh toán được chạy dưới dạng một cửa sổ bật lên trong bối cảnh trang web của người bán.
- Việc triển khai sẽ bổ sung cho ứng dụng thanh toán hiện có của bạn, cho phép bạn tận dụng cơ sở người dùng.
- Chữ ký của ứng dụng thanh toán được kiểm tra để ngăn chặn việc tải không qua cửa hàng.
- Ứng dụng thanh toán có thể hỗ trợ nhiều phương thức thanh toán.
- Bạn có thể tích hợp mọi phương thức thanh toán, chẳng hạn như tiền mã hoá, chuyển khoản ngân hàng, v.v. Các ứng dụng thanh toán trên thiết bị Android thậm chí có thể tích hợp các phương thức yêu cầu quyền truy cập vào khối phần cứng trên thiết bị.
Có 4 bước để triển khai tính năng Thanh toán trên web trong ứng dụng thanh toán Android:
- Cho phép người bán khám phá ứng dụng thanh toán của bạn.
- Cho người bán biết liệu khách hàng có phương thức thanh toán đã đăng ký (chẳng hạn như thẻ tín dụng) và sẵn sàng thanh toán hay không.
- Cho phép khách hàng thanh toán.
- Xác minh chứng chỉ ký của phương thức gọi.
Để xem hoạt động của tính năng Thanh toán trên web, hãy xem bản minh hoạ android-web-payment.
Bước 1: Cho phép người bán khám phá ứng dụng thanh toán của bạn
Để người bán có thể sử dụng ứng dụng thanh toán của bạn, họ cần sử dụng API yêu cầu thanh toán và chỉ định phương thức thanh toán mà bạn hỗ trợ bằng cách sử dụng mã nhận dạng phương thức thanh toán.
Nếu có mã nhận dạng phương thức thanh toán dành riêng cho ứng dụng thanh toán, bạn có thể thiết lập tệp kê khai phương thức thanh toán của riêng mình để trình duyệt có thể khám phá ứng dụng của bạn.
Bước 2: Cho người bán biết liệu khách hàng có phương thức thanh toán đã đăng ký và sẵn sàng thanh toán hay không
Người bán có thể gọi hasEnrolledInstrument()
để truy vấn xem khách hàng có thể thanh toán hay không. Bạn có thể triển khai IS_READY_TO_PAY
dưới dạng một dịch vụ Android để trả lời truy vấn này.
AndroidManifest.xml
Khai báo dịch vụ bằng bộ lọc ý định với thao tác 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>
Bạn không bắt buộc phải sử dụng dịch vụ IS_READY_TO_PAY
. Nếu không có trình xử lý ý định như vậy trong ứng dụng thanh toán, thì trình duyệt web sẽ giả định rằng ứng dụng luôn có thể thanh toán.
AIDL
API cho dịch vụ IS_READY_TO_PAY
được xác định trong AIDL. Tạo hai tệp AIDL có nội dung sau:
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);
}
Triển khai IsReadyToPayService
Cách triển khai đơn giản nhất của IsReadyToPayService
được thể hiện trong ví dụ sau:
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
}
}
Phản hồi
Dịch vụ có thể gửi phản hồi thông qua phương thức handleIsReadyToPay(Boolean)
.
callback?.handleIsReadyToPay(true)
Quyền
Bạn có thể sử dụng Binder.getCallingUid()
để kiểm tra xem phương thức gọi là ai. Lưu ý rằng bạn phải thực hiện việc này trong phương thức isReadyToPay
, chứ không phải trong phương thức onBind
.
override fun isReadyToPay(callback: IsReadyToPayServiceCallback?) {
try {
val callingPackage = packageManager.getNameForUid(Binder.getCallingUid())
// …
Xem bài viết Xác minh chứng chỉ ký của phương thức gọi để biết cách xác minh gói gọi có chữ ký chính xác.
Bước 3: Cho phép khách hàng thanh toán
Người bán gọi show()
để chạy ứng dụng thanh toán để khách hàng có thể thanh toán. Ứng dụng thanh toán được gọi thông qua một ý định PAY
trên Android với thông tin giao dịch trong các tham số ý định.
Ứng dụng thanh toán phản hồi bằng methodName
và details
, là các giá trị dành riêng cho ứng dụng thanh toán và không rõ ràng đối với trình duyệt. Trình duyệt chuyển đổi chuỗi details
thành đối tượng JavaScript cho người bán thông qua quá trình giải mã JSON, nhưng không thực thi bất kỳ giá trị hợp lệ nào ngoài đó. Trình duyệt không sửa đổi details
; giá trị của thông số đó được chuyển trực tiếp đến người bán.
AndroidManifest.xml
Hoạt động với bộ lọc ý định PAY
phải có thẻ <meta-data>
xác định giá trị nhận dạng phương thức thanh toán mặc định cho ứng dụng.
Để hỗ trợ nhiều phương thức thanh toán, hãy thêm thẻ <meta-data>
với tài nguyên <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
phải là một danh sách các chuỗi, mỗi chuỗi phải là một URL tuyệt đối, hợp lệ có lược đồ HTTPS như minh hoạ ở đây.
<?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>
Thông số
Các thông số sau đây được truyền đến hoạt động dưới dạng phần bổ sung Ý định:
methodNames
methodData
topLevelOrigin
topLevelCertificateChain
paymentRequestOrigin
total
modifiers
paymentRequestId
val extras: Bundle? = intent?.extras
methodNames
Tên của các phương thức đang được sử dụng. Các phần tử này là khoá trong từ điển methodData
. Đây là các phương thức mà ứng dụng thanh toán hỗ trợ.
val methodNames: List<String>? = extras.getStringArrayList("methodNames")
methodData
Một mối liên kết từ mỗi methodNames
đến methodData
.
val methodData: Bundle? = extras.getBundle("methodData")
merchantName
Nội dung của thẻ HTML <title>
trên trang thanh toán của người bán (bối cảnh duyệt web cấp cao nhất của trình duyệt).
val merchantName: String? = extras.getString("merchantName")
topLevelOrigin
Nguồn gốc của người bán không có lược đồ (Nguồn gốc không có lược đồ của ngữ cảnh duyệt web cấp cao nhất). Ví dụ: https://mystore.com/checkout
được truyền dưới dạng mystore.com
.
val topLevelOrigin: String? = extras.getString("topLevelOrigin")
topLevelCertificateChain
Chuỗi chứng chỉ của người bán (Chuỗi chứng chỉ của ngữ cảnh duyệt web cấp cao nhất). Rỗng đối với máy chủ cục bộ và tệp trên ổ đĩa, cả hai đều là ngữ cảnh bảo mật không có chứng chỉ SSL. Mỗi Parcelable
là một Gói có khoá certificate
và giá trị mảng byte.
val topLevelCertificateChain: Array<Parcelable>? =
extras.getParcelableArray("topLevelCertificateChain")
val list: List<ByteArray>? = topLevelCertificateChain?.mapNotNull { p ->
(p as Bundle).getByteArray("certificate")
}
paymentRequestOrigin
Nguồn gốc không có lược đồ của ngữ cảnh duyệt web iframe đã gọi hàm khởi tạo new
PaymentRequest(methodData, details, options)
trong JavaScript. Nếu hàm khởi tạo được gọi từ ngữ cảnh cấp cao nhất, thì giá trị của tham số này sẽ bằng giá trị của tham số topLevelOrigin
.
val paymentRequestOrigin: String? = extras.getString("paymentRequestOrigin")
total
Chuỗi JSON đại diện cho tổng số tiền của giao dịch.
val total: String? = extras.getString("total")
Dưới đây là nội dung mẫu của chuỗi:
{"currency":"USD","value":"25.00"}
modifiers
Kết quả của JSON.stringify(details.modifiers)
, trong đó details.modifiers
chỉ chứa supportedMethods
và total
.
paymentRequestId
Trường PaymentRequest.id
mà các ứng dụng "thanh toán không tiếp xúc" phải liên kết với trạng thái giao dịch. Trang web của người bán sẽ sử dụng trường này để truy vấn các ứng dụng "thanh toán đẩy" về trạng thái giao dịch ngoài phạm vi.
val paymentRequestId: String? = extras.getString("paymentRequestId")
Phản hồi
Hoạt động có thể gửi phản hồi qua setResult
bằng RESULT_OK
.
setResult(Activity.RESULT_OK, Intent().apply {
putExtra("methodName", "https://bobbucks.dev/pay")
putExtra("details", "{\"token\": \"put-some-data-here\"}")
})
finish()
Bạn phải chỉ định 2 tham số làm phần bổ sung Ý định:
methodName
: Tên của phương thức đang được sử dụng.details
: Chuỗi JSON chứa thông tin cần thiết để người bán hoàn tất giao dịch. Nếu thành công làtrue
, thìdetails
phải được tạo sao choJSON.parse(details)
thành công.
Bạn có thể chuyển RESULT_CANCELED
nếu giao dịch không được hoàn tất trong ứng dụng thanh toán, ví dụ: nếu người dùng không nhập đúng mã PIN cho tài khoản của họ trong ứng dụng thanh toán. Trình duyệt có thể cho phép người dùng chọn một ứng dụng thanh toán khác.
setResult(RESULT_CANCELED)
finish()
Nếu kết quả hoạt động của một phản hồi thanh toán nhận được từ ứng dụng thanh toán được gọi là RESULT_OK
, thì Chrome sẽ kiểm tra methodName
và details
không trống trong các phần bổ sung. Nếu xác thực không thành công, Chrome sẽ trả về lời hứa bị từ chối từ request.show()
kèm theo một trong các thông báo lỗi mà nhà phát triển gặp phải sau đây:
'Payment app returned invalid response. Missing field "details".'
'Payment app returned invalid response. Missing field "methodName".'
Quyền
Hoạt động có thể kiểm tra phương thức gọi bằng phương thức getCallingPackage()
.
val caller: String? = callingPackage
Bước cuối cùng là xác minh chứng chỉ ký của phương thức gọi để xác nhận rằng gói gọi có chữ ký chính xác.
Bước 4: Xác minh chứng chỉ ký của phương thức gọi
Bạn có thể kiểm tra tên gói của phương thức gọi bằng Binder.getCallingUid()
trong IS_READY_TO_PAY
và bằng Activity.getCallingPackage()
trong PAY
. Để thực sự xác minh rằng phương thức gọi là trình duyệt mà bạn đang nghĩ đến, bạn nên kiểm tra chứng chỉ ký của trình duyệt đó và đảm bảo rằng chứng chỉ đó khớp với giá trị chính xác.
Nếu đang nhắm đến API cấp 28 trở lên và đang tích hợp với một trình duyệt có một chứng chỉ ký, bạn có thể sử dụng 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()
được ưu tiên cho các trình duyệt chứng chỉ đơn lẻ, vì hàm này xử lý chính xác việc xoay chứng chỉ. (Chrome có một chứng chỉ ký duy nhất.) Các ứng dụng có nhiều chứng chỉ ký không thể xoay các chứng chỉ đó.
Nếu cần hỗ trợ API cũ cấp 27 trở xuống hoặc cần xử lý các trình duyệt có nhiều chứng chỉ ký, bạn có thể sử dụng 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) } }