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

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

ساهيل شارفي
ساهيل شارفي

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

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

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

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

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

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

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

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

  1. الإفصاح عن التفويضات المتوافقة:
  2. تحليل بيانات PAY الإضافية على الهدف لمعرفة خيارات الدفع المطلوبة
  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 مع قيم السلسلة.
  • 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 true.
  • payerPhone - رقم هاتف الجهة المسدِّدة يجب أن تكون هذه السلسلة غير فارغة عندما تكون قيمة paymentOptions.requestPayerPhone true.
  • payerEmail - عنوان البريد الإلكتروني الخاص بجهة الدفع يجب أن تكون هذه سلسلة غير فارغة عندما تكون قيمة paymentOptions.requestPayerEmail true.
  • 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 - حزمة تحتوي على مفاتيح اختيارية مماثلة لقيم عنوان الشحن وقيم السلسلة. يمثل كل مفتاح خطأ في التحقق يتعلق بالجزء المقابل له من عنوان الشحن.

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