מתן פרטים ליצירת קשר ופרטי משלוח מאפליקציית תשלומים ל-Android

איך מעדכנים את אפליקציית התשלומים ל-Android כדי לספק את הכתובת למשלוח ואת הפרטים ליצירת קשר של המשלם באמצעות ממשקי Web Payments API.

Sahel Sharify
Sahel Sharify

הקלדה של כתובת למשלוח ופרטים ליצירת קשר באמצעות טופס אינטרנט יכולה להיות חוויה מסורבלת ללקוחות. היא עלולה לגרום לשגיאות ולירידה בשיעור ההמרות.

לכן, Payment Request API תומך בתכונה שמאפשרת לבקש את הכתובת למשלוח ואת פרטי הקשר של הלקוח. יש לכך כמה יתרונות:

  • המשתמשים יכולים לבחור את הכתובת הנכונה בכמה הקשות פשוטות.
  • הכתובת תמיד מוחזרת בפורמט הסטנדרטי.
  • פחות סביר שתשלחו כתובת שגויה.

דפדפנים יכולים להעביר את האיסוף של פרטי הכתובת למשלוח והפרטים ליצירת קשר לאפליקציית תשלום כדי לספק חוויית תשלום מאוחדת. הפונקציונליות הזו נקראת הענקה.

כשהדבר אפשרי, Chrome מעביר את האיסוף של הכתובת למשלוח והפרטים ליצירת קשר של הלקוח לאפליקציית התשלום ב-Android שהופעלה. ההעברה הזו מפחיתה את החיכוך בתהליך התשלום.

אתר המוכר יכול לעדכן באופן דינמי את אפשרויות המשלוח ואת המחיר הכולל בהתאם לבחירה של הלקוח לגבי כתובת המשלוח ואפשרות המשלוח.

אפשרות המשלוח והכתובת למשלוח נכנסו לתוקף. תוכלו לראות איך הוא משפיע באופן דינמי על אפשרויות המשלוח ועל המחיר הכולל.

כדי להוסיף תמיכה בהענקת גישה לאפליקציית תשלום קיימת ל-Android:

  1. הצהרה על הענקות גישה נתמכות
  2. ניתוח של נתוני ה-extras של הכוונה 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>

ניתוח של רכיבי ה-intent הנוספים מסוג PAY לצורך זיהוי אפשרויות התשלום הנדרשות

המוכר יכול לציין מידע נדרש נוסף באמצעות המילון paymentOptions. Chrome יספק את רשימת האפשרויות הנדרשות שהאפליקציה יכולה לספק על ידי העברת הפרמטרים הבאים לפעילות PAY בתור Intent extras.

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 מציג את המטבע של עלות המשלוח, כקוד אלפביתי בן 3 אותיות בפורמט תקין לפי ISO4217
    • 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 הוא TRUE.
  • payerEmail – כתובת האימייל של המשלם. המחרוזת הזו צריכה להיות לא ריקה כש-paymentOptions.requestPayerEmail נכון.
  • 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")
    }
}

אופציונלי: זרימה דינמית של תמיכה

לפעמים העלות הכוללת של העסקה עולה, למשל כשהמשתמש בוחר באפשרות של משלוח מהיר, או כשהרשימה של אפשרויות המשלוח הזמינות או המחירים שלהן משתנה כשהמשתמש בוחר כתובת למשלוח בינלאומי. כשאתם מספקים את הכתובת או האפשרות למשלוח שנבחרו על ידי המשתמש, אתם צריכים להודיע למוכר על שינויים באפשרות או בכתובת למשלוח, ולהציג למשתמש את פרטי התשלום המעודכנים (שהמוכר סיפק).

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 – חבילה עם מפתחות אופציונליים שזהים לכתובת למשלוח ולערכי מחרוזות. כל מפתח מייצג שגיאת אימות שקשורה לחלק המתאים של הכתובת למשלוח.

אם מפתח לא מופיע, סימן שהערך שלו לא השתנה.