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

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

تاريخ النشر: 5 أيار (مايو) 2020، تاريخ آخر تعديل: 25 آذار (مارس) 2025

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

Browser Support

  • Chrome: 60.
  • Edge: 15.
  • Firefox: behind a flag.
  • Safari: 11.1.

Source

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

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

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

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

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

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

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

اضبط سمة related_applications في بيان تطبيق الويب وفقًا للتعليمات الواردة في مقالة إعداد طريقة دفع.

لكي يتمكّن التاجر من استخدام تطبيق الدفع، عليه استخدام 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 من سياق المستوى الأعلى، تكون قيمة هذه المَعلمة مساوية لقيمة المَعلمة 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) } }