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

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

เผยแพร่: 5 พฤษภาคม 2020 อัปเดตล่าสุด: 25 มีนาคม 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 ไฟล์ที่มีเนื้อหาต่อไปนี้

app/src/main/aidl/org/chromium/IsReadyToPayServiceCallback.aidl

package org.chromium;
interface IsReadyToPayServiceCallback {
    oneway void handleIsReadyToPay(boolean isReadyToPay);
}

app/src/main/aidl/org/chromium/IsReadyToPayService.aidl

package org.chromium;
import org.chromium.IsReadyToPayServiceCallback;

interface IsReadyToPayService {
    oneway void isReadyToPay(IsReadyToPayServiceCallback callback);
}

การใช้ IsReadyToPayService

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

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

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

การตอบกลับ

บริการสามารถส่งการตอบกลับผ่านเมธอด handleIsReadyToPay(Boolean)

callback?.handleIsReadyToPay(true)

สิทธิ์

คุณสามารถใช้ Binder.getCallingUid() เพื่อตรวจสอบว่าใครเป็นผู้โทรเข้ามา โปรดทราบว่าคุณต้องทำในเมธอด isReadyToPay ไม่ใช่เมธอด onBind

override fun isReadyToPay(callback: IsReadyToPayServiceCallback?) {
  try {
    val callingPackage = packageManager.getNameForUid(Binder.getCallingUid())
    // …

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

ขั้นตอนที่ 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/method_names" />
</activity>

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

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="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
  • topLevelOrigin
  • topLevelCertificateChain
  • paymentRequestOrigin
  • total
  • modifiers
  • paymentRequestId
val extras: Bundle? = intent?.extras

methodNames

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

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

methodData

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

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

merchantName

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

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

topLevelOrigin

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

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

topLevelCertificateChain

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

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

paymentRequestOrigin

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

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

total

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

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

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

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

modifiers

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

paymentRequestId

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

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

การตอบกลับ

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

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

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

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

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

setResult(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()

val caller: String? = callingPackage

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

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

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

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

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
)

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

หากต้องการรองรับ API ระดับเก่ากว่า 27 ขึ้นไป หรือหากต้องจัดการเบราว์เซอร์ที่มีใบรับรองการรับรองหลายรายการ ให้ใช้ PackageManager.GET_SIGNATURES

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

val packageInfo = getPackageInfo(packageName, PackageManager.GET_SIGNATURES)
val sha256 = MessageDigest.getInstance("SHA-256")
val signatures = packageInfo.signatures.map { sha256.digest(it.toByteArray()) }
val verified = signatures.size == certificates.size &&
    signatures.all { s -> certificates.any { it.contentEquals(s) } }