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

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

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

การรองรับเบราว์เซอร์

  • Chrome: 60
  • Edge: 15.
  • Firefox: อยู่หลังธง
  • Safari: 11.1

แหล่งที่มา

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

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

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

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

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

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

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

ผู้ขายต้องใช้ 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 แต่ละรายการคือแพ็กเกจที่มีคีย์ 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) } }