كيفية تحديث تطبيق الدفع على Android لتوفير عنوان الشحن ومعلومات الاتصال الخاصة بالجهة المسدِّدة باستخدام واجهات برمجة التطبيقات Web Payments API
قد تكون إدخال عنوان الشحن ومعلومات الاتصال من خلال نموذج ويب تجربة مزعجة للعملاء. ويمكن أن يؤدي ذلك إلى حدوث أخطاء وانخفاض في نسبة الإحالات الناجحة.
لهذا السبب، تتيح واجهة برمجة التطبيقات Payment Request API ميزة لطلب عنوان الشحن ومعلومات الاتصال. ويعود ذلك بالفائدة على النحو التالي:
- يمكن للمستخدمين اختيار العنوان الصحيح ببضع نقرات فقط.
- ويتم عرض العنوان دائمًا بالتنسيق المعيار.
- من غير المرجّح إرسال عنوان غير صحيح.
يمكن للمتصفّحات تأجيل جمع عنوان الشحن ومعلومات الاتصال إلى تطبيق دفع لتقديم تجربة دفع موحّدة. يُطلق على هذه الوظيفة اسم تفويض.
كلما أمكن، يفوّض Chrome جمع عنوان العميل للإرسال ومعلومات الاتصال به إلى تطبيق الدفع المتوافق مع Android الذي تمّ استدعاؤه. ويقلل التفوّض من الصعوبات التي تواجهك أثناء الدفع.
يمكن للموقع الإلكتروني للتاجر تعديل خيارات الشحن والسعر الإجمالي ديناميكيًا، وذلك استنادًا إلى اختيار العميل لعنوان الشحن وخيار الشحن.
لإضافة ميزة التفويض إلى تطبيق دفع حالي على Android، اتّبِع الخطوات التالية:
- الإفصاح عن عمليات التفويض المتوافقة
- تحليل
PAY
intent الإضافية لخيارات الدفع المطلوبة. - تقديم المعلومات المطلوبة في ردّ الدفع
- [اختياري] إتاحة المسار الديناميكي:
الإفصاح عن التفويضات المتوافقة
يحتاج المتصفّح إلى معرفة قائمة المعلومات الإضافية التي يمكن أن يوفّرها تطبيق الدفع كي يتمكّن من تفويض جمع هذه المعلومات إلى تطبيقك. يُرجى توضيح التفويضات المتوافقة باعتبارها <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
.
ولإجراء ذلك، يجب تحديد المَعلمات التالية على أنّها عناصر إضافية للهدف:
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
صحيحة.
التحقّق من صحة الرد على طلب الدفع
إذا تم ضبط نتيجة النشاط الخاصة بنتيجة رد على دفع تم استلامها من تطبيق الدفع الذي تم استدعاؤه على 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
- حِزمة تحتوي على مفاتيح اختيارية مطابقة لقيم shipping address وسلسلة يمثّل كل مفتاح خطأ في عملية التحقّق من صحة القيمة المتعلّقة بالجزء المعني من عنوان الشحن.
ويعني عدم توفّر المفتاح أنّ قيمته لم تتغيّر.