دليل مطوّري تطبيقات الدفع المتوافقة مع Android

تعرَّف على كيفية تكييف تطبيق الدفع على Android للعمل مع Web Payments وتوفير تجربة مستخدم أفضل للعملاء.

توفّر واجهة برمجة التطبيقات Payment Request API واجهة برمجة تطبيقات مدمجة مستندة إلى المتصفّح على الويب تتيح للمستخدمين إدخال معلومات الدفع المطلوبة بسهولة أكبر من أي وقت مضى. يمكن أيضًا لواجهة برمجة التطبيقات استدعاء التطبيقات المتعلّقة بالدفع المخصّصة للمنصة.

توافق المتصفّح

  • Chrome: 60
  • Edge: 15.
  • Firefox: خلف علامة
  • ‫Safari: 11.1

المصدر

مسار الدفع من خلال تطبيق Google Pay الخاص بالمنصّة والذي يستخدم Web Payments

مقارنةً باستخدام "نوايا Android" فقط، تتيح "عمليات الدفع على الويب" عملية دمج أفضل مع المتصفّح والأمان وتجربة المستخدم:

  • تم إطلاق تطبيق الدفع كنموذج في سياق الموقع الإلكتروني للتاجر.
  • تُعدّ عملية التنفيذ عنصرًا تكميليًا لتطبيق الدفع الحالي، ما يتيح لك الاستفادة من قاعدة مستخدمي تطبيقك.
  • يتم التحقّق من توقيع تطبيق الدفع لمنع التثبيت من مصدر غير معروف.
  • يمكن أن تتيح تطبيقات الدفع طرق دفع متعددة.
  • يمكن دمج أي طريقة دفع، مثل العملات المشفَّرة والتحويلات المصرفية وغير ذلك. يمكن لتطبيقات الدفع على أجهزة Android أيضًا دمج طرق تتطلب الوصول إلى شريحة الجهاز على الجهاز.

تستغرق عملية تنفيذ Web Payments في تطبيق دفع على Android أربع خطوات:

  1. إتاحة تطبيق الدفع للتجّار
  2. إبلاغ التاجر إذا كان لدى العميل وسيلة دفع مسجّلة (مثل بطاقة الائتمان) جاهزة للدفع
  3. دع العميل يدفع.
  4. التحقّق من شهادة التوقيع الخاصة بالبرنامج الذي يطلب الزحف

للاطّلاع على Web Payments أثناء العمل، يمكنك الاطّلاع على الإصدار التمهيدي android-web-payment .

الخطوة 1: السماح للتجّار باكتشاف تطبيق الدفع الخاص بك

لكي يتمكّن التاجر من استخدام تطبيق الدفع، عليه استخدام Payment Request API وتحديد طريقة الدفع التي تتيحها باستخدام معرِّف طريقة الدفع .

إذا كان لديك معرّف طريقة دفع فريد لتطبيق الدفع، يمكنك إعداد بيان طريقة الدفع الخاص بك حتى تتمكّن المتصفّحات من اكتشاف تطبيقك.

الخطوة 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() لتشغيل تطبيق الدفع حتى يتمكّن العميل من إجراء عملية دفع. يتمّ استدعاء تطبيق الدفع من خلال PAYintentPAY في Android مع تضمين معلومات المعاملة في مَعلمات 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

سلسلة شهادات التاجر (سلسلة شهادات سياق التصفّح من المستوى الأعلى) قيمة فارغة لـ localhost والملف على القرص، وكلاهما سياقان آمنَان بدون شهادات طبقة المقابس الآمنة كل 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 الذي يجب أن تربطه تطبيقات "الدفع الفوري" بحالة المعاملة ستستخدم المواقع الإلكترونية للتجّار هذا الحقل لطلب معلومات من تطبيقات "الدفع الفوري" عن حالة المعاملة خارج النطاق.

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

يجب تحديد مَعلمتَين على أنّهما إضافات للهدف:

  • 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 والإصدارات الأقدم، أو إذا كنت بحاجة إلى التعامل مع browsers التي تحتوي على شهادات توقيع متعددة، يمكنك استخدام 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) } }