تعرَّف على كيفية تعديل تطبيق الدفع على Android بما يتوافق مع Web Payments وتقديم تجربة مستخدم أفضل للعملاء.
تتيح Payment Request API توفير ويب، واجهة مدمجة تستند إلى المتصفح تسمح للمستخدمين بإدخال الدفعة المطلوبة المعلومات أسهل من أي وقت مضى. يمكن أن تطلب واجهة برمجة التطبيقات أيضًا عمليات دفع خاصة بالنظام الأساسي التطبيقات.
مقارنةً باستخدام أهداف Android Intent فقط، تتيح خدمة Web Payments عملية تكامل أفضل مع المتصفّح والأمان وتجربة المستخدم:
- تم إطلاق تطبيق الدفع كوسيلة في سياق الموقع الإلكتروني للتاجر.
- تُعدّ عملية التنفيذ عنصرًا تكميليًا لتطبيق الدفع الحالي، ما يتيح لك الاستفادة من قاعدة مستخدميك.
- يتم وضع علامة في مربّع توقيع تطبيق الدفع لمنع التثبيت من مصدر غير معروف.
- يمكن أن تتيح تطبيقات الدفع استخدام طرق دفع متعددة.
- يمكن استخدام أي طريقة دفع، مثل العملات المشفّرة والحوالات المصرفية وغيرها التكامل. يمكن لتطبيقات الدفع على أجهزة Android أيضًا دمج طرق تتطلّب الوصول إلى شريحة الجهاز على الجهاز.
هناك أربع خطوات لاستخدام Web Payments في تطبيق الدفع على Android:
- يمكنك السماح للتجّار باكتشاف تطبيق الدفع الخاص بك.
- يمكنك السماح للتاجر بمعرفة ما إذا كان العميل لديه وسيلة تسجيل مُسجّلة (مثل الرصيد). جاهزة للدفع).
- دع العميل يدفع.
- تحقَّق من شهادة توقيع المتصل.
للاطلاع على دفعات الويب عمليًا، راجع android-web-payment تجريبي.
الخطوة 1: السماح للتجّار باكتشاف تطبيق الدفع الخاص بك
ليتمكّن التاجر من استخدام تطبيق الدفع، عليه استخدام طريقة الدفع. طلب واجهة برمجة التطبيقات و حدِّد طريقة الدفع المتوافقة باستخدام طريقة الدفع المعرّف.
إذا كان لديك معرّف طريقة دفع فريد لتطبيق الدفع الذي تستخدمه، إعداد طريقة الدفع الخاصة بك حتى تتمكن المتصفحات من اكتشاف تطبيقك.
الخطوة 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
اختيارية. إذا لم يكن هناك معالج لمثل هذا الهدف في
تطبيق الدفع، فسيفترض متصفح الويب أن التطبيق يمكنه دائمًا إجراء
والمدفوعات.
لغة تعريف واجهة نظام Android (AIDL)
يتم تحديد واجهة برمجة التطبيقات لخدمة IS_READY_TO_PAY
في 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
مع تضمين معلومات المعاملة في مَعلمات intent.
يستجيب تطبيق الدفع باستخدام methodName
وdetails
، وهما تطبيقان للدفع.
محددة وغير واضحة بالنسبة إلى المتصفح. يحوِّل المتصفّح details
إلى كائن JavaScript للتاجر من خلال إلغاء تسلسل JSON،
لا تفرض أي صلاحية تتجاوز ذلك. لا يُعدِّل المتصفح
details
؛ يتم تمرير قيمة هذه المعلمة إلى التاجر مباشرةً.
AndroidManifest.xml
يجب أن يحتوي النشاط المزوّد بفلتر الأهداف PAY
على علامة <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
قائمة من السلاسل، ويجب أن تكون كل منها قيمة صالحة
عنوان URL كامل بنظام 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:
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
محتوى علامة HTML <title>
في صفحة الدفع الخاصة بالتاجر (
سياق التصفح الأعلى مستوى للمتصفح).
val merchantName: String? = extras.getString("merchantName")
topLevelOrigin
أصل التاجر بدون المخطط (أصل
سياق التصفح على المستوى الأعلى). على سبيل المثال، https://mystore.com/checkout
هو
تم اجتياز الاختبار باسم mystore.com
.
val topLevelOrigin: String? = extras.getString("topLevelOrigin")
topLevelCertificateChain
سلسلة شهادات التاجر (سلسلة الشهادات من المستوى الأعلى
سياق التصفح). قيمة خالية للمضيف المحلي والملف على القرص، وكلاهما آمن
والسياقات بدون شهادات طبقة المقابس الآمنة. كل Parcelable
عبارة عن حزمة تحتوي على
المفتاح certificate
وقيمة مصفوفة بايت.
val topLevelCertificateChain: Array<Parcelable>? =
extras.getParcelableArray("topLevelCertificateChain")
val list: List<ByteArray>? = topLevelCertificateChain?.mapNotNull { p ->
(p as Bundle).getByteArray("certificate")
}
paymentRequestOrigin
المصدر الخالي من المخطط لسياق تصفّح إطار iframe الذي استدعِ الدالة الإنشائية new
PaymentRequest(methodData, details, options)
في JavaScript. إذا كانت
تم استدعاء الدالة الإنشائية من سياق المستوى الأعلى، ثم قيمة هذه
تساوي قيمة معلَمة 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
الحقل PaymentRequest.id
الذي "الدفع السريع" يجب ربطها
حالة المعاملة. ستستخدم المواقع الإلكترونية للتجّار هذا الحقل للاستعلام عن
"push-payment" تطبيقات لحالة المعاملات خارج النطاق.
val paymentRequestId: String? = extras.getString("paymentRequestId")
الرد
يمكن للنشاط إعادة إرسال ردّه من خلال "setResult
" من خلال "RESULT_OK
".
setResult(Activity.RESULT_OK, Intent().apply {
putExtra("methodName", "https://bobbucks.dev/pay")
putExtra("details", "{\"token\": \"put-some-data-here\"}")
})
finish()
يجب تحديد معاملين كعناصر إضافية في Intent:
methodName
: اسم الطريقة المستخدَمةdetails
: سلسلة JSON تحتوي على المعلومات اللازمة للتاجر إكمال المعاملة. إذا كانت قيمة الحقل "true
" ناجحة، يجب ضبط السمةdetails
على النحو التالي: يتم إنشاؤها بطريقة تؤدي إلى نجاحJSON.parse(details)
.
يمكنك اجتياز 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: التحقّق من شهادة توقيع المتصل
يمكنك التحقّق من اسم الحزمة الخاص بالمتصل من خلال Binder.getCallingUid()
.
IS_READY_TO_PAY
، ومع "Activity.getCallingPackage()
" في PAY
. من أجل
التأكد من أن المتصل هو المتصفح الذي في ذهنك، فيجب عليك
التحقق من شهادة التوقيع والتأكد من تطابقها مع
إذا كنت تستهدف المستوى 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 من واجهة برمجة التطبيقات القديمة والمستويات الأدنى، أو إذا كنت بحاجة إلى معالجة
المتصفحات التي تحتوي على شهادات توقيع متعددة، يمكنك استخدام
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) } }