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

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

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

دعم المتصفح

  • 60
  • 15
  • 11.1

المصدر

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

مقارنةً باستخدام أهداف Android Intent فقط، تتيح خدمة Web Payments التكامل بشكل أفضل مع المتصفّح والأمان وتجربة المستخدم:

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

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

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

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

يجب تحديد معاملين كعناصر إضافية في 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) } }