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

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

Sahel Sharify
Sahel Sharify

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

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

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

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

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

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

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

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

  1. להצהיר על הענקת גישה נתמכת
  2. מנתחים PAY תוספות Intent עבור אפשרויות התשלום הנדרשות.
  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>

ניתוח של PAY תוספות Intent עבור אפשרויות התשלום הנדרשות

המוכר יכול לציין מידע נדרש נוסף באמצעות המילון 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.

כדי לעשות זאת, יש לציין את הפרמטרים הבאים כתוספות Intent:

  • payerName – השם המלא של המשלם. כשהערך הוא paymentOptions.requestPayerName, הערך צריך להיות מחרוזת לא ריקה.
  • payerPhone – מספר הטלפון של המשלם. כשהערך הוא paymentOptions.requestPayerPhone, הערך צריך להיות מחרוזת לא ריקה.
  • payerEmail - כתובת האימייל של המשלם. כשהערך של paymentOptions.requestPayerEmail הוא True, המחרוזת לא יכולה להיות ריקה.
  • 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 – חבילה עם מפתחות אופציונליים שזהים לכתובת למשלוח ולערכי מחרוזות. כל מפתח מייצג שגיאת אימות שקשורה לחלק המתאים בכתובת למשלוח.

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