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

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

סהל שארפי
סהל שארפי

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

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

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

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

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

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

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

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

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

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 הוא TRUE.
  • payerPhone - מספר הטלפון של המשלם. המחרוזת צריכה להיות לא ריקה אם הערך של paymentOptions.requestPayerPhone הוא TRUE.
  • 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 שמשמש ל-Intent ההתחלה של השירות יכול לקבל אחד מהערכים הבאים, בהתאם לדפדפן שממנו נשלחה בקשת התשלום.

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

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