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

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

Sahel Sharify
Sahel Sharify

تاريخ النشر: 17 يوليو 2020، تاريخ آخر تعديل: 27 مايو 2025

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

لهذا السبب، تتيح واجهة 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/chromium_payment_supported_delegations" />
</activity>

يجب أن يشير android:resource إلى <string-array> يحتوي على كل القيم التالية أو مجموعة فرعية منها:

  • payerName
  • payerEmail
  • payerPhone
  • shippingAddress

لا يمكن للمثال التالي تقديم سوى عنوان شحن وعنوان البريد الإلكتروني الخاص بالدافع.

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

تحليل إضافات الغرض PAY للحصول على خيارات الدفع المطلوبة

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

paymentOptions

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

Kotlin

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")

Java

Bundle paymentOptions = extras.getBundle("paymentOptions");
if (paymentOptions != null) {
    Boolean requestPayerName = paymentOptions.getBoolean("requestPayerName");
    Boolean requestPayerPhone = paymentOptions.getBoolean("requestPayerPhone");
    Boolean requestPayerEmail = paymentOptions.getBoolean("requestPayerEmail");
    Boolean requestShipping = paymentOptions.getBoolean("requestShipping");
    String shippingType = paymentOptions.getString("shippingType");
}

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

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

shippingOptions

shippingOptions هي مصفوفة قابلة للتجزئة تتضمّن خيارات الشحن التي يحدّدها التاجر. لن تكون هذه المَعلمة متاحة إلا عندما تكون القيمة paymentOptions.requestShipping == true.

Kotlin

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

Java

Parcelable[] shippingOptions = extras.getParcelableArray("shippingOptions");
for (Parcelable it : shippingOptions) {
  if (it != null && it instanceof Bundle) {
    Bundle shippingOption = (Bundle) it;
  }
}

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

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

تحتوي جميع المفاتيح الأخرى غير selected على قيم سلسلة. تحتوي السمة selected على قيمة منطقية.

Kotlin

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)

Java

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

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

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

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

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

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

إذا تم ضبط نتيجة النشاط لردّ الدفع الذي تم تلقّيه من تطبيق الدفع الذي تم استدعاؤه على 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".'

في ما يلي نموذج لرمز صالح:

Kotlin

fun Intent.populateRequestedPaymentOptions() {
    if (requestPayerName) {
        putExtra("payerName", "John Smith")
    }
    if (requestPayerPhone) {
        putExtra("payerPhone", "5555555555")
    }
    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", "5555555555")
        putExtra("shippingAddress", address)
        putExtra("shippingOptionId", "standard")
    }
}

Java

private Intent populateRequestedPaymentOptions() {
    Intent result = new Intent();
    if (requestPayerName) {
        result.putExtra("payerName", "John Smith");
    }
    if (requestPayerPhone) {
        presult.utExtra("payerPhone", "5555555555");
    }
    if (requestPayerEmail) {
        result.putExtra("payerEmail", "john.smith@gmail.com");
    }
    if (requestShipping) {
        Bundle address = new Bundle();
        address.putExtra("countryCode", "CA");
        address.putExtra("postalCode", "M5H2G4");
        address.putExtra("region", "Ontario");
        address.putExtra("city", "Toronto");
        String[] addressLines = new String[] {"111 Richmond st. West"};
        address.putExtra("addressLines", addressLines);
        address.putExtra("recipient", "John Smith");
        address.putExtra("phone", "5555555555");
        result.putExtra("shippingAddress", address);
        result.putExtra("shippingOptionId", "standard");
    }
    return result;
}

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

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

لإعلام التاجر بالتغييرات الجديدة، عليك تنفيذ واجهة IPaymentDetailsUpdateServiceCallback وتعريفها في AndroidManifest.xml باستخدام فلتر الغرض UPDATE_PAYMENT_DETAILS.

بعد استدعاء الغرض PAY مباشرةً، سيتصل Chrome بالخدمة UPDATE_PAYMENT_DETAILS (إذا كانت متوفرة) في الحزمة نفسها التي يتضمّنها الغرض PAY، وسيستدعي setPaymentDetailsUpdateService(service) لتزويد تطبيق الدفع بنقطة النهاية IPaymentDetailsUpdateService لإرسال إشعارات بشأن التغييرات في طريقة الدفع أو خيار الشحن أو عنوان الشحن الخاص بالمستخدم.

استخدِم packageManager.getPackagesForUid(Binder.getCallingUid()) عند تلقّي عملية اتصال بين العمليات (IPC) للتحقّق من أنّ التطبيق الذي استدعى الغرض PAY له اسم الحزمة نفسه الخاص بالتطبيق الذي استدعى طرق IPaymentDetailsUpdateServiceCallback.

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

أنشئ ملفَي AIDL يتضمّنان المحتوى التالي:

org/chromium/components/payments/IPaymentDetailsUpdateServiceCallback.aidl

package org.chromium.components.payments;

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

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

    oneway void paymentDetailsNotUpdated();

    oneway void setPaymentDetailsUpdateService(IPaymentDetailsUpdateService service);
}

org/chromium/components/payments/IPaymentDetailsUpdateService.aidl

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);
}

الخدمة

نفِّذ خدمة IPaymentDetailsUpdateServiceCallback.

Kotlin

class SampleUpdatePaymentDetailsCallbackService : Service() {
    private val binder = object : IPaymentDetailsUpdateServiceCallback.Stub() {
        override fun updateWith(updatedPaymentDetails: Bundle) {}

        override fun paymentDetailsNotUpdated() {}

        override fun setPaymentDetailsUpdateService(service: IPaymentDetailsUpdateService) {}
    }

    override fun onBind(intent: Intent?): IBinder? {
        return binder
    }
}

Java

import org.chromium.components.paymsnts.IPaymentDetailsUpdateServiceCallback;

public class SampleUpdatePaymentDetailsCallbackService extends Service {
    private final IPaymentDetailsUpdateServiceCallback.Stub mBinder =
        new IPaymentDetailsUpdateServiceCallback.Stub() {
            @Override
            public void updateWith(Bundle updatedPaymentDetails) {}

            @Override
            public void paymentDetailsNotUpdated() {}

            @Override
            public void setPaymentDetailsUpdateService(IPaymentDetailsUpdateService service) {}
        };

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
}

AndroidManifest.xml

عرض الخدمة IPaymentDetailsUpdateServiceCallback في AndroidManifest.xml

<service
    android:name=".SampleUpdatePaymentDetailsCallbackService"
    android:exported="true">
    <intent-filter>
        <action android:name="org.chromium.intent.action.UPDATE_PAYMENT_DETAILS" />
    </intent-filter>
</service>

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

Kotlin

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
}

Java

if (service == null) {
  return;
}

try {
    if (isOptionChange) {
        service.changeShippingOption(selectedOptionId, callback);
    } else (isAddressChange) {
        service.changeShippingAddress(selectedAddress, callback);
    } else {
        service.changePaymentMethod(methodData, callback);
    }
} catch (RemoteException e) {
    // Handle the remote exception
}

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 لا يزال ينتظر ردّ التاجر على تغيير سابق (مثل حدث تغيير جارٍ).
  • لا ينتمي معرّف خيار الشحن الذي يوفّره تطبيق الدفع إلى أي من خيارات الشحن التي يحدّدها التاجر.

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

Kotlin

override fun updateWith(updatedPaymentDetails: Bundle) {}

override fun paymentDetailsNotUpdated() {}

Java

@Override
public void updateWith(Bundle updatedPaymentDetails) {}

@Override
public void paymentDetailsNotUpdated() {}

updatedPaymentDetails هي حزمة مكافئة لقائمة PaymentRequestDetailsUpdate WebIDL وتحتوي على المفاتيح الاختيارية التالية:

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

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