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

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

Sahel Sharify
Sahel Sharify

تاريخ النشر: 17 تموز (يوليو) 2020، تاريخ التعديل الأخير: 27 أيار (مايو) 2025

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

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

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

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

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

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

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

لإضافة ميزة التفويض إلى تطبيق دفع حالي على Android، اتّبِع الخطوات التالية:

  1. الإفصاح عن عمليات التفويض المتوافقة
  2. استخدم PAY extras لتحليل خيارات الدفع المطلوبة.
  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.

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

  • payerName - الاسم الكامل للدافع يجب أن تكون هذه السلسلة غير فارغة عندما تكون قيمة paymentOptions.requestPayerName صحيحة.
  • payerPhone: رقم هاتف المسؤول عن الدفع يجب أن تكون هذه السلسلة غير فارغة عندما تكون قيمة paymentOptions.requestPayerPhone صحيحة.
  • payerEmail: عنوان البريد الإلكتروني للمدفِع يجب أن تكون هذه القيمة سلسلة غير فارغة عندما تكون paymentOptions.requestPayerEmail صحيحة.
  • 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", "[email protected]")
    }
    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", "[email protected]");
    }
    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 - الصفيف القابل للتقسيم من shipping options
  • error: سلسلة تحتوي على رسالة خطأ عامة (مثلاً عندما لا يوفّر changeShippingOption معرّفًا صالحًا لخيار الشحن)
  • stringifiedPaymentMethodErrors - سلسلة JSON تمثّل أخطاء التحقّق لطريقة الدفع
  • addressErrors - حِزمة تحتوي على مفاتيح اختيارية مطابقة لقيم shipping address وسلسلة يمثّل كل مفتاح خطأ في عملية التحقّق من صحة القيمة المتعلّقة بالجزء المعني من عنوان الشحن.
  • modifiers: مصفوفة قابلة للتقسيم إلى حزم، يتضمّن كلّ منها حقلَي total و methodData، وهما أيضًا حِزم.

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