دليل مطوّري تطبيقات الدفع المتوافقة مع 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: السماح للتجّار باكتشاف تطبيق الدفع الخاص بك

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

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

الخطوة 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

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