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

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

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

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

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

แหล่งที่มา

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

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

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

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

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

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

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

ผู้ขายจะต้องใช้วิธีการชำระเงินแบบการชำระเงินเพื่อให้ใช้แอปการชำระเงินได้ ส่งคำขอ 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 ต้องเป็นรายการสตริง ซึ่งแต่ละรายการต้องเป็นสตริงที่ถูกต้อง Absolute 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

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

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

topLevelOrigin

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

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

topLevelCertificateChain

กลุ่มใบรับรองของผู้ขาย (กลุ่มใบรับรองของระดับบนสุด บริบทการท่องเว็บ) ไม่มีค่าสำหรับ 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 ที่ "พุชการชำระเงิน" แอปควรเชื่อมโยงกับ สถานะธุรกรรม เว็บไซต์ผู้ขายจะใช้ช่องนี้ในการค้นหา &quot;push-payment&quot; แอปสำหรับสถานะการทำธุรกรรมนอกขอบเขต

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) } }