كيفية تعديل تطبيق الدفع على Android لتقديم عنوان الشحن ومعلومات الاتصال الخاصة بالدافع باستخدام واجهات برمجة تطبيقات Web Payments
تاريخ النشر: 17 يوليو 2020، تاريخ آخر تعديل: 27 مايو 2025
قد تكون تجربة إدخال عنوان الشحن ومعلومات الاتصال من خلال نموذج ويب تجربة شاقة بالنسبة إلى العملاء. وقد يؤدي ذلك إلى حدوث أخطاء وانخفاض معدّل الإحالات الناجحة.
لهذا السبب، تتيح واجهة Payment Request API ميزة لطلب عنوان الشحن ومعلومات الاتصال. يوفّر ذلك مزايا متعددة:
- يمكن للمستخدمين اختيار العنوان المناسب ببضع نقرات فقط.
- يتم عرض العنوان دائمًا بالتنسيق الموحّد.
- من غير المرجّح إرسال عنوان غير صحيح.
يمكن للمتصفّحات تأجيل جمع عنوان الشحن ومعلومات الاتصال إلى تطبيق دفع لتوفير تجربة دفع موحّدة. تُعرف هذه الوظيفة باسم التفويض.
كلما أمكن ذلك، يفوّض Chrome تطبيق الدفع على Android الذي تم استدعاؤه بجمع عنوان الشحن ومعلومات الاتصال الخاصة بالعميل، ما يقلّل من المشاكل التي قد تواجه العميل أثناء عملية الدفع.
يمكن للموقع الإلكتروني الخاص بالتاجر تعديل خيارات الشحن والسعر الإجمالي بشكل ديناميكي استنادًا إلى العنوان وخيار الشحن اللذين يحدّدهما العميل.
لإضافة ميزة تفويض الدفع إلى تطبيق دفع حالي على Android، اتّبِع الخطوات التالية:
- تحديد عمليات التفويض المتاحة
- تحليل إضافات الغرض
PAY
للحصول على خيارات الدفع المطلوبة - تقديم المعلومات المطلوبة في رد الدفع
- اختياري: إتاحة التدفق الديناميكي:
تحديد التفويضات المتاحة
يحتاج المتصفّح إلى معرفة قائمة المعلومات الإضافية التي يمكن أن يقدّمها تطبيق الدفع، وذلك حتى يتمكّن من تفويض عملية جمع هذه المعلومات إلى تطبيقك. عليك الإفصاح عن عمليات التفويض المتاحة كـ <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
مع قيم السلسلة- تعرض
currency
عملة تكلفة الشحن كرمز أبجدي مكوّن من 3 أحرف بتنسيق ISO4217. - يمثّل
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
، وهما أيضًا حِزم.
يعني المفتاح غير المتوفّر أنّ قيمته لم تتغيّر.