คู่มือนักพัฒนาแอปสำหรับการชำระเงินใน Android

ดูวิธีปรับแอปการชำระเงิน Android ให้ทำงานร่วมกับการชำระเงินผ่านเว็บและมอบประสบการณ์การใช้งานที่ดีขึ้นให้แก่ลูกค้า

เผยแพร่: 5 พฤษภาคม 2020 อัปเดตล่าสุด: 27 พฤษภาคม 2025

Payment Request API จะนำอินเทอร์เฟซแบบเบราว์เซอร์ในตัวมาสู่เว็บ ซึ่งช่วยให้ผู้ใช้ป้อนข้อมูลการชำระเงินที่จำเป็นได้ง่ายกว่าที่เคย นอกจากนี้ API ยังเรียกใช้แอปการชำระเงินเฉพาะแพลตฟอร์มได้ด้วย

Browser Support

  • Chrome: 60.
  • Edge: 15.
  • Firefox: behind a flag.
  • Safari: 11.1.

Source

ขั้นตอนการชำระเงินด้วยแอป Google Pay สำหรับแพลตฟอร์มเฉพาะที่ใช้การชำระเงินผ่านเว็บ

การชำระเงินผ่านเว็บช่วยให้ผสานรวมกับเบราว์เซอร์ ความปลอดภัย และประสบการณ์ของผู้ใช้ได้ดียิ่งขึ้นเมื่อเทียบกับการใช้ Intent ของ Android เพียงอย่างเดียว

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

การติดตั้งใช้งานการชำระเงินผ่านเว็บในแอปการชำระเงิน Android ประกอบด้วย 4 ขั้นตอนดังนี้

  1. ให้ผู้ขายค้นพบแอปการชำระเงินของคุณ
  2. แจ้งให้ผู้ขายทราบว่าลูกค้ามีเครื่องมือที่ลงทะเบียน (เช่น บัตรเครดิต) ซึ่งพร้อมชำระเงินหรือไม่
  3. ให้ลูกค้าชำระเงิน
  4. ยืนยันใบรับรองการรับรองของผู้โทร

หากต้องการดูการทํางานของการชำระเงินผ่านเว็บ ให้ดูการสาธิต android-web-payment

ขั้นตอนที่ 1: ให้ผู้ขายค้นพบแอปการชำระเงินของคุณ

ตั้งค่าพร็อพเพอร์ตี้ related_applications ในไฟล์ Manifest ของเว็บแอปตามวิธีการในการตั้งค่าวิธีการชำระเงิน

ผู้ขายต้องใช้ Payment Request API และระบุวิธีการชำระเงินที่คุณรองรับโดยใช้ตัวระบุวิธีการชำระเงินจึงจะใช้แอปการชำระเงินของคุณได้

หากมีตัวระบุวิธีการชำระเงินที่ไม่ซ้ำกันสำหรับแอปการชำระเงินของคุณ คุณจะตั้งค่าไฟล์ Manifest ของวิธีการชำระเงินของคุณเองเพื่อให้เบราว์เซอร์ค้นพบแอปได้

ขั้นตอนที่ 2: แจ้งให้ผู้ขายทราบหากลูกค้ามีเครื่องมือที่ลงทะเบียนไว้ซึ่งพร้อมชำระเงิน

ผู้ขายสามารถโทรไปที่ hasEnrolledInstrument() เพื่อสอบถามว่าลูกค้าสามารถชำระเงินได้หรือไม่ คุณสามารถใช้ IS_READY_TO_PAY เป็นบริการ Android เพื่อตอบคำถามนี้ได้

AndroidManifest.xml

ประกาศบริการด้วยตัวกรอง Intent ที่มีการดำเนินการ org.chromium.intent.action.IS_READY_TO_PAY

<service
  android:name=".SampleIsReadyToPayService"
  android:exported="true">
  <intent-filter>
    <action android:name="org.chromium.intent.action.IS_READY_TO_PAY" />
  </intent-filter>
</service>

คุณจะใช้บริการ IS_READY_TO_PAY หรือไม่ก็ได้ หากไม่มีตัวแฮนเดิล Intent ดังกล่าวในแอปการชำระเงิน เว็บเบราว์เซอร์จะถือว่าแอปชำระเงินได้เสมอ

AIDL

API สําหรับบริการ IS_READY_TO_PAY ได้รับการกําหนดไว้ใน AIDL สร้างไฟล์ AIDL 2 ไฟล์ที่มีเนื้อหาต่อไปนี้

org/chromium/IsReadyToPayServiceCallback.aidl

package org.chromium;

interface IsReadyToPayServiceCallback {
    oneway void handleIsReadyToPay(boolean isReadyToPay);
}

org/chromium/IsReadyToPayService.aidl

package org.chromium;

import org.chromium.IsReadyToPayServiceCallback;

interface IsReadyToPayService {
    oneway void isReadyToPay(IsReadyToPayServiceCallback callback, in Bundle parameters);
}

การใช้ IsReadyToPayService

การใช้งาน IsReadyToPayService ที่ง่ายที่สุดแสดงอยู่ในตัวอย่างต่อไปนี้

Kotlin

class SampleIsReadyToPayService : Service() {
    private val binder = object : IsReadyToPayService.Stub() {
        override fun isReadyToPay(callback: IsReadyToPayServiceCallback?, parameters: Bundle?) {
            callback?.handleIsReadyToPay(true)
        }
    }

    override fun onBind(intent: Intent?): IBinder? {
        return binder
    }
}

Java

import org.chromium.IsReadyToPayService;

public class SampleIsReadyToPayService extends Service {
    private final IsReadyToPayService.Stub mBinder =
        new IsReadyToPayService.Stub() {
            @Override
            public void isReadyToPay(IsReadyToPayServiceCallback callback, Bundle parameters) {
                if (callback != null) {
                    callback.handleIsReadyToPay(true);
                }
            }
        };

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
}

การตอบกลับ

บริการสามารถส่งการตอบกลับได้โดยใช้เมธอด handleIsReadyToPay(Boolean)

Kotlin

callback?.handleIsReadyToPay(true)

Java

if (callback != null) {
    callback.handleIsReadyToPay(true);
}

สิทธิ์

คุณสามารถใช้ Binder.getCallingUid() เพื่อตรวจสอบว่าใครเป็นผู้โทร โปรดทราบว่าคุณต้องดำเนินการนี้ในเมธอด isReadyToPay ไม่ใช่เมธอด onBind เนื่องจากระบบปฏิบัติการ Android สามารถแคชและนำการเชื่อมต่อบริการกลับมาใช้ใหม่ได้ ซึ่งจะไม่ทริกเกอร์เมธอด onBind()

Kotlin

override fun isReadyToPay(callback: IsReadyToPayServiceCallback?, parameters: Bundle?) {
    try {
        val untrustedPackageName = parameters?.getString("packageName")
        val actualPackageNames = packageManager.getPackagesForUid(Binder.getCallingUid())
        // ...

Java

@Override
public void isReadyToPay(IsReadyToPayServiceCallback callback, Bundle parameters) {
    try {
        String untrustedPackageName = parameters != null
                ? parameters.getString("packageName")
                : null;
        String[] actualPackageNames = packageManager.getPackagesForUid(Binder.getCallingUid());
        // ...

ตรวจสอบพารามิเตอร์อินพุตสำหรับ null เสมอเมื่อได้รับการเรียกใช้การสื่อสารระหว่างกระบวนการ (IPC) ซึ่งสำคัญอย่างยิ่งเนื่องจากระบบปฏิบัติการ Android เวอร์ชันต่างๆ หรือเวอร์ชันที่แยกออกมาอาจทำงานในลักษณะที่ไม่คาดคิดและทำให้เกิดข้อผิดพลาดได้หากไม่จัดการ

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

ดูยืนยันใบรับรองการรับรองของผู้โทรเกี่ยวกับวิธีตรวจสอบว่าแพ็กเกจการเรียกใช้มีลายเซ็นที่ถูกต้อง

พารามิเตอร์

เพิ่ม parameters Bundle ใน Chrome 139 แล้ว ควรตรวจสอบกับ null เสมอ

ระบบจะส่งพารามิเตอร์ต่อไปนี้ไปยังบริการใน parameters Bundle

  • packageName
  • methodNames
  • methodData
  • topLevelOrigin
  • paymentRequestOrigin
  • topLevelCertificateChain

packageName เพิ่มเข้ามาใน Chrome 138 คุณต้องยืนยันพารามิเตอร์นี้กับ Binder.getCallingUid() ก่อนใช้ค่า การยืนยันนี้มีความจําเป็นเนื่องจากผู้ใช้จะควบคุมแพ็กเกจ parameters ได้อย่างเต็มที่ ในขณะที่ระบบปฏิบัติการ Android จะควบคุม Binder.getCallingUid()

topLevelCertificateChain คือ null ใน WebView และในเว็บไซต์ที่ไม่ใช่ https ซึ่งมักใช้สำหรับการทดสอบในเครื่อง เช่น http://localhost

ขั้นตอนที่ 3: ให้ลูกค้าชำระเงิน

ผู้ขายโทรหา show() เพื่อเปิดแอปการชำระเงินเพื่อให้ลูกค้าชำระเงินได้ ระบบเรียกใช้แอปการชำระเงินโดยใช้ Android Intent PAY ที่มีข้อมูลธุรกรรมในพารามิเตอร์ Intent

แอปการชำระเงินจะตอบกลับด้วย methodName และ details ซึ่งเป็นค่าเฉพาะของแอปการชำระเงินและเบราว์เซอร์จะมองไม่เห็น โดยเบราว์เซอร์จะแปลงสตริง details เป็นพจนานุกรม JavaScript สำหรับผู้ขายโดยใช้การแปลงค่าสตริง JSON กลับเป็นรูปแบบเดิม แต่ไม่บังคับใช้ความถูกต้องนอกเหนือจากนั้น เบราว์เซอร์จะไม่แก้ไข details โดยระบบจะส่งค่าของพารามิเตอร์นั้นไปยังผู้ขายโดยตรง

AndroidManifest.xml

กิจกรรมที่มีตัวกรอง Intent PAY ควรมีแท็ก <meta-data> ที่ระบุตัวระบุวิธีการชำระเงินเริ่มต้นสําหรับแอป

หากต้องการรองรับวิธีการชำระเงินหลายวิธี ให้เพิ่มแท็ก <meta-data> ที่มีทรัพยากร <string-array>

<activity
  android:name=".PaymentActivity"
  android:theme="@style/Theme.SamplePay.Dialog">
  <intent-filter>
    <action android:name="org.chromium.intent.action.PAY" />
  </intent-filter>

  <meta-data
    android:name="org.chromium.default_payment_method_name"
    android:value="https://bobbucks.dev/pay" />
  <meta-data
    android:name="org.chromium.payment_method_names"
    android:resource="@array/chromium_payment_method_names" />
</activity>

android:resource ต้องเป็นรายการสตริง โดยแต่ละรายการต้องเป็น URL แบบสัมบูรณ์ที่ถูกต้องซึ่งมีรูปแบบ HTTPS ดังที่แสดงที่นี่

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="chromium_payment_method_names">
        <item>https://alicepay.com/put/optional/path/here</item>
        <item>https://charliepay.com/put/optional/path/here</item>
    </string-array>
</resources>

พารามิเตอร์

ระบบจะส่งพารามิเตอร์ต่อไปนี้ไปยังกิจกรรมเป็นIntentข้อมูลเพิ่มเติม

  • methodNames
  • methodData
  • merchantName
  • topLevelOrigin
  • topLevelCertificateChain
  • paymentRequestOrigin
  • total
  • modifiers
  • paymentRequestId
  • paymentOptions
  • shippingOptions

Kotlin

val extras: Bundle? = getIntent()?.extras

Java

Bundle extras = getIntent() != null ? getIntent().getExtras() : null;

methodNames

ชื่อของเมธอดที่ใช้ องค์ประกอบคือคีย์ในพจนานุกรม methodData ซึ่งแอปการชำระเงินรองรับวิธีเหล่านี้

Kotlin

val methodNames: List<String>? = extras.getStringArrayList("methodNames")

Java

List<String> methodNames = extras.getStringArrayList("methodNames");

methodData

การแมปจาก methodNames แต่ละรายการไปยัง methodData

Kotlin

val methodData: Bundle? = extras.getBundle("methodData")

Java

Bundle methodData = extras.getBundle("methodData");

merchantName

เนื้อหาของแท็ก <title> HTML ของหน้าชำระเงินของผู้ขาย (บริบทการท่องเว็บระดับบนสุดของเบราว์เซอร์)

Kotlin

val merchantName: String? = extras.getString("merchantName")

Java

String merchantName = extras.getString("merchantName");

topLevelOrigin

ต้นทางของผู้ขายที่ไม่มีรูปแบบ (ต้นทางที่ไม่มีรูปแบบของบริบทการท่องเว็บระดับบนสุด) เช่น https://mystore.com/checkout จะส่งเป็น mystore.com

Kotlin

val topLevelOrigin: String? = extras.getString("topLevelOrigin")

Java

String topLevelOrigin = extras.getString("topLevelOrigin");

topLevelCertificateChain

เชนใบรับรองของผู้ขาย (เชนใบรับรองของบริบทการท่องเว็บระดับบนสุด) ค่าคือ null สำหรับ WebView, localhost หรือไฟล์บนดิสก์ Parcelable แต่ละรายการคือ Bundle ที่มีคีย์ certificate และค่าอาร์เรย์ไบต์

Kotlin

val topLevelCertificateChain: Array<Parcelable>? =
        extras.getParcelableArray("topLevelCertificateChain")
val list: List<ByteArray>? = topLevelCertificateChain?.mapNotNull { p ->
    (p as Bundle).getByteArray("certificate")
}

Java

Parcelable[] topLevelCertificateChain =
        extras.getParcelableArray("topLevelCertificateChain");
if (topLevelCertificateChain != null) {
    for (Parcelable p : topLevelCertificateChain) {
        if (p != null && p instanceof Bundle) {
            ((Bundle) p).getByteArray("certificate");
        }
    }
}

paymentRequestOrigin

ต้นทางที่ไม่มีรูปแบบของบริบทการท่องเว็บ iframe ที่เรียกใช้คอนสตรัคเตอร์ new PaymentRequest(methodData, details, options) ใน JavaScript หากเรียกใช้คอนสตรคเตอร์จากบริบทระดับบนสุด ค่าของพารามิเตอร์นี้จะเท่ากับค่าของพารามิเตอร์ topLevelOrigin

Kotlin

val paymentRequestOrigin: String? = extras.getString("paymentRequestOrigin")

Java

String paymentRequestOrigin = extras.getString("paymentRequestOrigin");

total

สตริง JSON ที่แสดงยอดเงินรวมของธุรกรรม

Kotlin

val total: String? = extras.getString("total")

Java

String total = extras.getString("total");

ตัวอย่างเนื้อหาของสตริงมีดังนี้

{"currency":"USD","value":"25.00"}

modifiers

เอาต์พุตของ JSON.stringify(details.modifiers) โดยที่ details.modifiers มีเฉพาะ supportedMethods, data และ total

paymentRequestId

ช่อง PaymentRequest.id ที่แอป "การชำระเงินแบบ Push" ควรเชื่อมโยงกับสถานะธุรกรรม เว็บไซต์ของผู้ขายจะใช้ช่องนี้เพื่อค้นหาสถานะธุรกรรมนอกแบนด์ในแอป "การชำระเงินแบบ Push"

Kotlin

val paymentRequestId: String? = extras.getString("paymentRequestId")

Java

String paymentRequestId = extras.getString("paymentRequestId");

การตอบกลับ

กิจกรรมสามารถส่งการตอบกลับกลับผ่าน setResult ด้วย RESULT_OK

Kotlin

setResult(Activity.RESULT_OK, Intent().apply {
    putExtra("methodName", "https://bobbucks.dev/pay")
    putExtra("details", "{\"token\": \"put-some-data-here\"}")
})
finish()

Java

Intent result = new Intent();
Bundle extras = new Bundle();
extras.putString("methodName", "https://bobbucks.dev/pay");
extras.putString("instrumentDetails", "{\"token\": \"put-some-data-here\"}");
result.putExtras(extras);
setResult(Activity.RESULT_OK, result);
finish();

คุณต้องระบุพารามิเตอร์ 2 รายการเป็นข้อมูลเพิ่มเติมของ Intent ดังนี้

  • methodName: ชื่อเมธอดที่ใช้
  • details: สตริง JSON ที่มีข้อมูลที่จําเป็นสําหรับผู้ขายในการทําธุรกรรมให้เสร็จสมบูรณ์ หากความสําเร็จคือ true details ต้องสร้างขึ้นในลักษณะที่ JSON.parse(details) จะประสบความสําเร็จ หากไม่มีข้อมูลที่ต้องแสดงผล ให้ใช้สตริงเป็น "{}" ซึ่งเว็บไซต์ของผู้ขายจะได้รับเป็นพจนานุกรม JavaScript ว่าง

คุณสามารถส่ง RESULT_CANCELED ได้หากธุรกรรมยังไม่เสร็จสมบูรณ์ในแอปการชำระเงิน เช่น หากผู้ใช้พิมพ์รหัส PIN ที่ถูกต้องสำหรับบัญชีในแอปการชำระเงินไม่ได้ เบราว์เซอร์อาจอนุญาตให้ผู้ใช้เลือกแอปการชำระเงินอื่น

Kotlin

setResult(Activity.RESULT_CANCELED)
finish()

Java

setResult(Activity.RESULT_CANCELED);
finish();

หากตั้งค่าผลลัพธ์กิจกรรมของการตอบกลับการชำระเงินที่ได้รับจากแอปการชำระเงินที่เรียกให้แสดงเป็น RESULT_OK แล้ว Chrome จะตรวจสอบว่า methodName และ details ในข้อมูลเพิ่มเติมไม่ใช่ค่าว่าง หากการตรวจสอบไม่ผ่าน Chrome จะแสดงการปฏิเสธจาก request.show() พร้อมข้อความแสดงข้อผิดพลาดที่นักพัฒนาแอปเห็นอย่างใดอย่างหนึ่งต่อไปนี้

'Payment app returned invalid response. Missing field "details".'
'Payment app returned invalid response. Missing field "methodName".'

สิทธิ์

กิจกรรมจะตรวจสอบผู้เรียกได้ด้วยเมธอด getCallingPackage()

Kotlin

val caller: String? = callingPackage

Java

String caller = getCallingPackage();

ขั้นตอนสุดท้ายคือการยืนยันใบรับรองการรับรองของผู้โทรเพื่อยืนยันว่าแพ็กเกจการโทรมีลายเซ็นที่ถูกต้อง

ขั้นตอนที่ 4: ยืนยันใบรับรองการรับรองของผู้โทร

คุณสามารถตรวจสอบชื่อแพ็กเกจของผู้โทรได้ด้วย Binder.getCallingUid() ใน IS_READY_TO_PAY และ Activity.getCallingPackage() ใน PAY หากต้องการยืนยันว่าผู้เรียกใช้คือเบราว์เซอร์ที่คุณคิดไว้จริงๆ คุณควรตรวจสอบใบรับรองการรับรองและตรวจสอบว่าตรงกับค่าที่ถูกต้อง

หากกําหนดเป้าหมายเป็น API ระดับ 28 ขึ้นไปและผสานรวมกับเบราว์เซอร์ที่มีใบรับรองการรับรองใบเดียว คุณจะใช้ PackageManager.hasSigningCertificate() ได้

Kotlin

val packageName: String =  // The caller's package name
val certificate: ByteArray =  // The correct signing certificate
val verified = packageManager.hasSigningCertificate(
        callingPackage,
        certificate,
        PackageManager.CERT_INPUT_SHA256
)

Java

String packageName =  // The caller's package name
byte[] certificate =  // The correct signing certificate
boolean verified = packageManager.hasSigningCertificate(
        callingPackage,
        certificate,
        PackageManager.CERT_INPUT_SHA256);

เราขอแนะนำให้ใช้ PackageManager.hasSigningCertificate() สำหรับเบราว์เซอร์ที่มีใบรับรองใบเดียว เนื่องจากจะจัดการการหมุนเวียนใบรับรองได้อย่างถูกต้อง (Chrome มีใบรับรองการรับรองแบบรวม) แอปที่มีใบรับรองการรับรองหลายรายการจะหมุนใบรับรองไม่ได้

หากต้องการรองรับ API ระดับ 27 หรือต่ำกว่า หรือหากต้องจัดการเบราว์เซอร์ที่มีใบรับรองการรับรองหลายใบ คุณสามารถใช้ PackageManager.GET_SIGNATURES

Kotlin

val packageName: String =  // The caller's package name
val expected: Set<String> =  // The correct set of signing certificates

val packageInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES)
val sha256 = MessageDigest.getInstance("SHA-256")
val actual = packageInfo.signatures.map {
    SerializeByteArrayToString(sha256.digest(it.toByteArray()))
}
val verified = actual.equals(expected)

Java

String packageName  =  // The caller's package name
Set<String> expected =  // The correct set of signing certificates

PackageInfo packageInfo =
        packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
Set<String> actual = new HashSet<>();
for (Signature it : packageInfo.signatures) {
    actual.add(SerializeByteArrayToString(sha256.digest(it.toByteArray())));
}
boolean verified = actual.equals(expected);

แก้ไขข้อบกพร่อง

ใช้คําสั่งต่อไปนี้เพื่อดูข้อผิดพลาดหรือข้อความที่ให้ข้อมูล

adb logcat | grep -i pay