تقديم معلومات الشحن والاتصال من تطبيق دفع على Android

كيفية تحديث تطبيق الدفع على Android لتوفير عنوان الشحن ومعلومات الاتصال الخاصة بالجهة المسدِّدة باستخدام واجهات برمجة التطبيقات Web Payments API

Sahel Sharify
Sahel Sharify

قد يكون إدخال عنوان الشحن ومعلومات الاتصال من خلال نموذج ويب تجربة مرهقة للعملاء. قد تتسبب في حدوث أخطاء وانخفاض معدل الإحالات الناجحة.

ولهذا السبب توفر واجهة برمجة تطبيقات طلب الدفع ميزة لطلب عنوان الشحن ومعلومات الاتصال. يوفر ذلك مزايا متعددة:

  • ويمكن للمستخدمين اختيار العنوان الصحيح ببضع نقرات فقط.
  • ويتم عرض العنوان دائمًا بالتنسيق الموحّد.
  • ومن غير المرجح أن يتم إرسال عنوان غير صحيح.

ويمكن للمتصفّحات تأجيل جمع عنوان الشحن ومعلومات الاتصال إلى أحد تطبيقات الدفع لتوفير تجربة دفع موحّدة. تُسمى هذه الوظيفة التفويض.

يفوض Chrome، كلما أمكن، جمع عنوان الشحن ومعلومات الاتصال الخاصة بالعميل إلى تطبيق الدفع على Android الذي تم استدعاؤه. ويساهم هذا التفويض في الحد من المعوقات المرتبطة بعملية الدفع.

يمكن أن يعدّل موقع التاجر الإلكتروني خيارات الشحن والسعر الإجمالي ديناميكيًا استنادًا إلى اختيار العميل لعنوان الشحن وخيار الشحن.

يتم حاليًا تغيير خيار الشحن وعنوان الشحن. ويمكنك الاطّلاع على كيفية تأثيره في خيارات الشحن والسعر الإجمالي ديناميكيًا.

لإضافة دعم التفويض إلى تطبيق دفع حالي في Android، يمكنك تنفيذ الخطوات التالية:

  1. توضيح التفويضات المتوافقة
  2. تحليل PAY intent الإضافية لخيارات الدفع المطلوبة.
  3. تقديم المعلومات المطلوبة في الردّ على عمليات الدفع:
  4. [اختياري] إتاحة المسار الديناميكي:
    1. إبلاغ التاجر بالتغييرات في طريقة الدفع أو عنوان الشحن أو خيار الشحن التي اختارها المستخدم:
    2. تلقّي تفاصيل دفع معدّلة من التاجر (على سبيل المثال، المبلغ الإجمالي المعدَّل استنادًا إلى تكلفة خيار الشحن المحدَّد).

تعريف التفويضات المتوافقة

يحتاج المتصفّح إلى معرفة قائمة المعلومات الإضافية التي يمكن أن يوفّرها تطبيق الدفع كي يتمكّن من تفويض جمع هذه المعلومات إلى تطبيقك. يُرجى توضيح التفويضات المتوافقة باعتبارها <meta-data> في ملف AndroidManifest.xml الخاص بتطبيقك.

<activity
  android:name=".PaymentActivity"
  …
  <meta-data
    android:name="org.chromium.payment_supported_delegations"
    android:resource="@array/supported_delegations" />
</activity>

يجب أن يكون <resource> قائمة بسلاسل يتم اختيارها من القيم الصالحة التالية:

[ "payerName", "payerEmail", "payerPhone", "shippingAddress" ]

يمكن في المثال التالي تقديم عنوان الشحن وعنوان البريد الإلكتروني للجهة المسدِّدة فقط.

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <string-array name="supported_delegations">
    <item>payerEmail</item>
    <item>shippingAddress</item>
  </string-array>
</resources>

تحليل عناصر "PAY" الإضافية حسب النية بالشراء لخيارات الدفع المطلوبة

ويمكن للتاجر تحديد المعلومات الإضافية المطلوبة باستخدام قاموس paymentOptions. سيقدّم Chrome قائمة بالخيارات المطلوبة التي يمكن أن يوفّرها تطبيقك من خلال تمرير المَعلمات التالية إلى نشاط PAY باعتبارها عناصر إضافية intent.

paymentOptions

paymentOptions هي مجموعة فرعية من خيارات الدفع التي يحدّدها التاجر والتي أعلن تطبيقك عن إتاحة التفويض لها.

val paymentOptions: Bundle? = extras.getBundle("paymentOptions")
val requestPayerName: Boolean? = paymentOptions?.getBoolean("requestPayerName")
val requestPayerPhone: Boolean? = paymentOptions?.getBoolean("requestPayerPhone")
val requestPayerEmail: Boolean? = paymentOptions?.getBoolean("requestPayerEmail")
val requestShipping: Boolean? = paymentOptions?.getBoolean("requestShipping")
val shippingType: String? = paymentOptions?.getString("shippingType")

يمكن أن يتضمّن المَعلمات التالية:

  • requestPayerName - القيمة المنطقية التي تشير إلى ما إذا كان اسم الجهة المسدِّدة مطلوبًا أم لا
  • requestPayerPhone - القيمة المنطقية التي تشير إلى ما إذا كان رقم هاتف الجهة المسدِّدة مطلوبًا أم لا
  • requestPayerEmail - القيمة المنطقية التي تشير إلى ما إذا كان يجب إدخال عنوان البريد الإلكتروني للمسؤول عن الدفع
  • requestShipping - القيمة المنطقية التي تشير إلى ما إذا كانت معلومات الشحن مطلوبة أم لا.
  • shippingType - السلسلة التي توضح نوع الشحن. يمكن أن يكون نوع الشحن "shipping" أو "delivery" أو "pickup". يمكن لتطبيقك استخدام هذا التلميح في واجهة المستخدم عند طلب عنوان المستخدم أو اختيار خيارات الشحن.

shippingOptions

تمثّل السمة shippingOptions مصفوفة من خيارات الشحن التي يحدّدها التاجر. ولن تتوفّر هذه المَعلمة إلا عند استخدام paymentOptions.requestShipping == true.

val shippingOptions: List<ShippingOption>? =
    extras.getParcelableArray("shippingOptions")?.mapNotNull {
        p -> from(p as Bundle)
    }

كل خيار شحن هو Bundle مع المفاتيح التالية.

  • id - معرّف خيار الشحن
  • label: تشير هذه السمة إلى تصنيف خيار الشحن المعروض للمستخدم.
  • amount: حزمة تكلفة الشحن التي تحتوي على مفتاحَي currency وvalue مع قيم سلسلة
    • تعرض السمة currency عملة تكلفة الشحن، وهي عبارة عن رمز أبجدي ISO4217 مكتوب بشكل صحيح مكوَّن من 3 أحرف.
    • تعرض value قيمة تكلفة الشحن في شكل قيمة نقدية صالحة لعدد عشري.
  • selected - لتحديد ما إذا كان يجب تحديد خيار الشحن عند عرض تطبيق الدفع خيارات الشحن أم لا.

تشتمل جميع المفاتيح غير selected على قيم سلسلة. تتضمن selected قيمة منطقية.

val id: String = bundle.getString("id")
val label: String = bundle.getString("label")
val amount: Bundle = bundle.getBundle("amount")
val selected: Boolean = bundle.getBoolean("selected", false)

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

يجب أن يُدرج تطبيقك المعلومات الإضافية المطلوبة في ردّه على نشاط PAY.

لإجراء ذلك، يجب تحديد المَعلمات التالية كعناصر إضافية في Intent:

  • payerName - الاسم الكامل للمسؤول عن الدفع يجب أن تكون هذه السلسلة غير فارغة عندما تكون قيمة paymentOptions.requestPayerName صحيحة.
  • payerPhone - رقم هاتف الجهة المسدِّدة يجب أن تكون هذه السلسلة غير فارغة عندما تكون قيمة paymentOptions.requestPayerPhone صحيحة.
  • payerEmail: عنوان البريد الإلكتروني للمسؤول عن الدفع يجب أن تكون هذه السلسلة غير فارغة عندما تكون قيمة paymentOptions.requestPayerEmail صحيحة.
  • shippingAddress: عنوان الشحن الذي قدّمه المستخدم يجب أن تكون هذه حزمة غير فارغة عندما تكون القيمة paymentOptions.requestShipping true. يجب أن تتضمّن الحزمة المفاتيح التالية التي تمثّل أجزاءً مختلفة في العنوان الجغرافي.
    • city
    • countryCode
    • dependentLocality
    • organization
    • phone
    • postalCode
    • recipient
    • region
    • sortingCode
    • addressLine تحتوي جميع المفاتيح غير addressLine على قيم سلسلة. السمة addressLine هي مصفوفة من السلاسل.
  • shippingOptionId: معرّف خيار الشحن الذي اختاره المستخدم ويجب أن تكون هذه السلسلة غير فارغة عندما تكون القيمة paymentOptions.requestShipping true.

التحقّق من صحة الرد على طلب الدفع

إذا تم ضبط نتيجة النشاط الخاصة بنتيجة رد على دفع تم استلامها من تطبيق الدفع الذي تم استدعاؤه على RESULT_OK، سيتحقق Chrome من المعلومات الإضافية المطلوبة في الميزات الإضافية. إذا تعذّر إجراء عملية التحقّق، سيعرض Chrome وعدًا مرفوضًا من request.show() يتضمّن إحدى رسائل الخطأ التالية التي يواجهها المطوّرون:

'Payment app returned invalid response. Missing field "payerEmail".'
'Payment app returned invalid response. Missing field "payerName".'
'Payment app returned invalid response. Missing field "payerPhone".'
'Payment app returned invalid shipping address in response.'
'... is not a valid CLDR country code, should be 2 upper case letters [A-Z]'
'Payment app returned invalid response. Missing field "shipping option".'

نموذج الرمز البرمجي التالي هو مثال على استجابة صالحة:

fun Intent.populateRequestedPaymentOptions() {
    if (requestPayerName) {
        putExtra("payerName", "John Smith")
    }
    if (requestPayerPhone) {
        putExtra("payerPhone", "4169158200")
    }
    if (requestPayerEmail) {
        putExtra("payerEmail", "john.smith@gmail.com")
    }
    if(requestShipping) {
        val address: Bundle = Bundle()
        address.putString("countryCode", "CA")
        val addressLines: Array<String> =
                arrayOf<String>("111 Richmond st. West")
        address.putStringArray("addressLines", addressLines)
        address.putString("region", "Ontario")
        address.putString("city", "Toronto")
        address.putString("postalCode", "M5H2G4")
        address.putString("recipient", "John Smith")
        address.putString("phone", "4169158200")
        putExtra("shippingAddress", address)
        putExtra("shippingOptionId", "standard")
    }
}

اختياري: إتاحة التدفق الديناميكي

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

لغة تعريف واجهة نظام Android ‏(AIDL)

لإبلاغ التاجر بالتغييرات الجديدة، استخدِم خدمة PaymentDetailsUpdateService المعلَنة في ملف AndroidManifest.xml من Chrome. لاستخدام هذه الخدمة، أنشئ ملفين AIDL بالمحتوى التالي:

app/src/main/aidl/org/chromium/components/payments/IPaymentDetailsUpdateService

package org.chromium.components.payments;
import android.os.Bundle;

interface IPaymentDetailsUpdateServiceCallback {
    oneway void updateWith(in Bundle updatedPaymentDetails);

    oneway void paymentDetailsNotUpdated();
}

app/src/main/aidl/org/chromium/components/payments/IPaymentDetailsUpdateServiceCallback

package org.chromium.components.payments;
import android.os.Bundle;
import org.chromium.components.payments.IPaymentDetailsUpdateServiceCallback;

interface IPaymentDetailsUpdateService {
    oneway void changePaymentMethod(in Bundle paymentHandlerMethodData,
            IPaymentDetailsUpdateServiceCallback callback);

    oneway void changeShippingOption(in String shippingOptionId,
            IPaymentDetailsUpdateServiceCallback callback);

    oneway void changeShippingAddress(in Bundle shippingAddress,
            IPaymentDetailsUpdateServiceCallback callback);
}

إبلاغ التاجر بالتغييرات التي أجراها المستخدم على طريقة الدفع أو عنوان الشحن أو خيار الشحن التي اختارها

private fun bind() {
    // The action is introduced in Chrome version 92, which supports the service in Chrome
    // and other browsers (e.g., WebLayer).
    val newIntent = Intent("org.chromium.intent.action.UPDATE_PAYMENT_DETAILS")
        .setPackage(callingBrowserPackage)
    if (packageManager.resolveService(newIntent, PackageManager.GET_RESOLVED_FILTER) == null) {
        // Fallback to Chrome-only approach.
        newIntent.setClassName(
            callingBrowserPackage,
            "org.chromium.components.payments.PaymentDetailsUpdateService")
        newIntent.action = IPaymentDetailsUpdateService::class.java.name
    }
    isBound = bindService(newIntent, connection, Context.BIND_AUTO_CREATE)
}

private val connection = object : ServiceConnection {
    override fun onServiceConnected(className: ComponentName, service: IBinder) {
        val service = IPaymentDetailsUpdateService.Stub.asInterface(service)
        try {
            if (isOptionChange) {
                service?.changeShippingOption(selectedOptionId, callback)
            } else (isAddressChange) {
                service?.changeShippingAddress(selectedAddress, callback)
            } else {
                service?.changePaymentMethod(methodData, callback)
            }
        } catch (e: RemoteException) {
            // Handle the remote exception
        }
    }
}

يمكن أن تشتمل السمة callingPackageName المستخدمة لغرض بدء الخدمة على إحدى القيم التالية اعتمادًا على المتصفح الذي أرسل طلب الدفع.

قناة Chrome اسم الحزمة
إسطبل "com.android.chrome"
إصدار تجريبي "com.chrome.beta"
في وضع التطوير "com.chrome.dev"
الكاناري "com.chrome.canary"
Chromium "org.chromium.chrome"
مربّع البحث السريع من Google (أداة تضمين WebLayer) "com.google.android.googlequicksearchbox"

changePaymentMethod

يتم إشعار التاجر بالتغييرات في طريقة الدفع التي اختارها المستخدم. تحتوي الحزمة paymentHandlerMethodData على methodName ومفتاحَي details اختياريَين مع قيم سلسلة. سيتحقق Chrome من وجود حزمة غير فارغة مع methodName غير فارغ وسيرسل updatePaymentDetails مع إحدى رسائل الخطأ التالية عبر callback.updateWith في حال تعذُّر التحقق من الصحة.

'Method data required.'
'Method name required.'

changeShippingOption

يتم إشعار التاجر بالتغييرات في خيار الشحن الذي اختاره المستخدم. يجب أن تكون السمة shippingOptionId معرّفًا لأحد خيارات الشحن التي يحدّدها التاجر. سيتحقق Chrome من وجود shippingOptionId غير فارغ وسيرسل updatePaymentDetails مع رسالة الخطأ التالية عبر callback.updateWith في حال تعذُّر إتمام عملية التحقّق.

'Shipping option identifier required.'

changeShippingAddress

يتم إشعار التاجر بالتغييرات في عنوان الشحن الذي قدّمه المستخدم. سيبحث Chrome عن حزمة shippingAddress غير فارغة تتضمّن قيمة countryCode صالحة وسيرسل updatePaymentDetails مع رسالة الخطأ التالية عبر callback.updateWith في حال تعذُّر عملية التحقّق.

'Payment app returned invalid shipping address in response.'

رسالة خطأ حالة غير صالحة

إذا واجه Chrome حالة غير صالحة عند تلقّي أي من طلبات التغيير، سيتم طلب callback.updateWith مع حزمة updatePaymentDetails تم إخفاؤها. ستحتوي الحزمة على مفتاح error مع "Invalid state" فقط. تشمل الأمثلة على الحالة غير الصالحة ما يلي:

  • عندما يظل Chrome في انتظار رد التاجر على تغيير سابق (مثلاً، حدث تغيير جارٍ)
  • لا ينتمي معرّف خيار الشحن المقدَّم من تطبيق الدفع إلى أي من خيارات الشحن التي حدّدها التاجر.

تلقّي تفاصيل الدفع المعدّلة من التاجر

private fun unbind() {
    if (isBound) {
        unbindService(connection)
        isBound = false
    }
}

private val callback: IPaymentDetailsUpdateServiceCallback =
    object : IPaymentDetailsUpdateServiceCallback.Stub() {
        override fun paymentDetailsNotUpdated() {
            // Payment request details have not changed.
            unbind()
        }

        override fun updateWith(updatedPaymentDetails: Bundle) {
            newPaymentDetails = updatedPaymentDetails
            unbind()
        }
    }

تتشابه السمة updatePaymentDetails مع قاموس PaymentRequestDetailsUpdate WebIDL (بعد إخفاء الحقل modifiers) وتتضمّن المفاتيح الاختيارية التالية:

  • total - حزمة تحتوي على المفتاحين currency وvalue، ويحتوي كلا المفتاحين على قيم سلسلة
  • shippingOptions - مصفوفة أجزاء خيارات الشحن
  • error - سلسلة تحتوي على رسالة خطأ عامة (على سبيل المثال، عندما لا توفّر "changeShippingOption" معرّفًا صالحًا لخيار الشحن)
  • stringifiedPaymentMethodErrors - سلسلة JSON تمثل أخطاء التحقق من طريقة الدفع
  • addressErrors - حزمة تحتوي على مفاتيح اختيارية تتطابق مع عنوان الشحن وقيم السلسلة. يمثل كل مفتاح خطأ في التحقق من الصحة يتعلق بالجزء المقابل له من عنوان الشحن.

ويعني عدم توفّر المفتاح أنّ قيمته لم تتغيّر.