การให้ข้อมูลการจัดส่งและข้อมูลติดต่อจากแอปการชำระเงินของ Android

วิธีอัปเดตแอปการชำระเงิน Android เพื่อระบุที่อยู่สำหรับจัดส่งและข้อมูลติดต่อของผู้ชำระเงินด้วย Web Payments API

ซาเฮล Sharify
Sahel Sharify

การป้อนที่อยู่สำหรับจัดส่งและข้อมูลติดต่อผ่านเว็บฟอร์มอาจเป็นเรื่องยุ่งยากสำหรับลูกค้า อาจทำให้เกิดข้อผิดพลาด และอัตรา Conversion

ซึ่งเป็นเหตุผลที่ 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 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" แอปของคุณสามารถใช้คำแนะนำนี้ใน UI เมื่อขอที่อยู่ของผู้ใช้หรือตัวเลือกการจัดส่ง

shippingOptions

shippingOptions คือตัวเลือกการจัดส่งที่ผู้ขายระบุได้ซึ่งจัดเรียงเป็นที่ดิน พารามิเตอร์นี้จะมีอยู่ก็ต่อเมื่อ paymentOptions.requestShipping == true เท่านั้น

val shippingOptions: List<ShippingOption>? =
    extras.getParcelableArray("shippingOptions")?.mapNotNull {
        p -> from(p as Bundle)
    }

ตัวเลือกการจัดส่งแต่ละรายการคือ Bundle ที่มีคีย์ต่อไปนี้

  • id - ตัวระบุตัวเลือกการจัดส่ง
  • label - ป้ายกำกับตัวเลือกการจัดส่งที่แสดงต่อผู้ใช้
  • amount - แพ็กเกจค่าจัดส่งที่มี currency และ 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 เป็นจริง
  • shippingAddress - ที่อยู่สำหรับจัดส่งที่ได้จากผู้ใช้ ซึ่งควรเป็นแพ็กเกจที่ไม่ว่างเปล่าเมื่อ paymentOptions.requestShipping เป็นจริง แพ็กเกจดังกล่าวควรมีคีย์ต่อไปนี้ซึ่งแสดงส่วนต่างๆ ในที่อยู่จริง
    • 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")
    }
}

ไม่บังคับ: รองรับขั้นตอนแบบไดนามิก

บางครั้งค่าใช้จ่ายรวมในการทำธุรกรรมจะเพิ่มขึ้น เช่น เมื่อผู้ใช้เลือกตัวเลือกการจัดส่งด่วน หรือเมื่อรายการตัวเลือกการจัดส่งที่ใช้ได้หรือราคามีการเปลี่ยนแปลงเมื่อผู้ใช้เลือกที่อยู่สำหรับจัดส่งระหว่างประเทศ เมื่อแอปของคุณแจ้งที่อยู่สำหรับจัดส่งหรือตัวเลือกที่ผู้ใช้เลือกแล้ว แอปควรจะสามารถแจ้งผู้ขายเกี่ยวกับที่อยู่สำหรับจัดส่งหรือการเปลี่ยนแปลงตัวเลือกใดๆ และแสดงรายละเอียดการชำระเงินที่อัปเดตให้ผู้ใช้ทราบ (ซึ่งผู้ขายให้ไว้)

AIDL

หากต้องการแจ้งให้ผู้ขายทราบเกี่ยวกับการเปลี่ยนแปลงใหม่ ให้ใช้บริการ PaymentDetailsUpdateService ที่ประกาศใน AndroidManifest.xml ของ Chrome หากต้องการใช้บริการนี้ ให้สร้างไฟล์ AIDL 2 ไฟล์ที่มีเนื้อหาต่อไปนี้

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 Quick Search (เครื่องมือฝัง WebLayer) "com.google.android.googlequicksearchbox"

changePaymentMethod

แจ้งผู้ขายเกี่ยวกับการเปลี่ยนแปลงวิธีการชำระเงินที่ผู้ใช้เลือก แพ็กเกจ paymentHandlerMethodData มี methodName และคีย์ details ที่ไม่บังคับทั้ง 2 รายการที่มีค่าสตริง 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 โดยทั้ง 2 คีย์มีค่าสตริง
  • shippingOptions - อาร์เรย์ที่แยกวิเคราะห์ได้ของตัวเลือกการจัดส่ง
  • error - สตริงที่มีข้อความแสดงข้อผิดพลาดทั่วไป (เช่น เมื่อ changeShippingOption ไม่มีตัวระบุตัวเลือกการจัดส่งที่ถูกต้อง)
  • stringifiedPaymentMethodErrors - สตริง JSON ที่แสดงข้อผิดพลาดในการตรวจสอบความถูกต้องของวิธีการชำระเงิน
  • addressErrors - แพ็กเกจที่มีคีย์ที่ไม่บังคับซึ่งเหมือนกับที่อยู่จัดส่งและค่าสตริง แต่ละคีย์จะแสดงข้อผิดพลาดในการตรวจสอบความถูกต้องที่เกี่ยวข้องกับส่วนที่เกี่ยวข้องของที่อยู่สำหรับจัดส่ง

คีย์ที่ไม่มีหมายความว่าค่าคีย์ไม่มีการเปลี่ยนแปลง