คู่มือนักพัฒนาแอปสำหรับการชำระเงินใน 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 ที่มี Action 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() ก่อนที่จะใช้ค่าของพารามิเตอร์ การยืนยันนี้มีความสำคัญเนื่องจากผู้เรียกควบคุม Bundle parameters ได้อย่างเต็มที่ ขณะที่ระบบปฏิบัติการ Android ควบคุม Binder.getCallingUid()

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

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

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

แอปการชำระเงินจะตอบกลับด้วย methodName และ details ซึ่งเป็นข้อมูลเฉพาะของแอปการชำระเงินและเบราว์เซอร์ไม่สามารถเข้าถึงได้ เบราว์เซอร์จะแปลงสตริง details เป็น Dictionary 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 ต้องเป็นรายการสตริง ซึ่งแต่ละสตริงต้องเป็น Absolute 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

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

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("details", "{\"token\": \"put-some-data-here\"}");
result.putExtras(extras);
setResult(Activity.RESULT_OK, result);
finish();

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

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

คุณสามารถส่ง RESULT_CANCELED หากผู้ใช้ยกเลิกธุรกรรมในแอปการชำระเงิน การดำเนินการดังกล่าวจะทำให้ request.show() ปฏิเสธด้วย AbortError ในเว็บไซต์ผู้ขาย ซึ่งบ่งบอกว่าผู้ใช้ยกเลิก

Kotlin

setResult(Activity.RESULT_CANCELED)
finish()

Java

setResult(Activity.RESULT_CANCELED);
finish();

Chrome 149 เป็นต้นไปรองรับค่าผลลัพธ์ต่อไปนี้

Kotlin

Activity.RESULT_CANCELED // 0 (0x00000000)
Activity.RESULT_OK // -1 (0xffffffff)
const val INTERNAL_PAYMENT_APP_ERROR = Activity.RESULT_FIRST_USER // 1 (0x00000001)

Java

Activity.RESULT_CANCELED // 0 (0x00000000)
Activity.RESULT_OK // -1 (0xffffffff)
static final int INTERNAL_PAYMENT_APP_ERROR = Activity.RESULT_FIRST_USER; // 1 (0x00000001)

หากแอปการชำระเงินล้มเหลวเนื่องจากข้อผิดพลาดภายใน คุณสามารถระบุได้โดยส่ง Activity.RESULT_FIRST_USER เป็นรหัสผลลัพธ์

หากระบบส่งคืน INTERNAL_PAYMENT_APP_ERROR request.show() จะปฏิเสธด้วย OperationError ในเว็บไซต์ผู้ขาย ซึ่งบ่งบอกว่ามีข้อผิดพลาดในแอปการชำระเงิน

ความแตกต่างระหว่าง RESULT_CANCELED (0) สำหรับการยกเลิกของผู้ใช้ ซึ่งทำให้เกิด AbortError และ INTERNAL_PAYMENT_APP_ERROR (1) สำหรับข้อผิดพลาดภายในแอป ซึ่งทำให้เกิด OperationError ช่วยให้ผู้ขายสร้างโฟลว์ผู้ใช้ได้ดียิ่งขึ้น

Kotlin

setResult(Activity.RESULT_FIRST_USER)
finish()

Java

setResult(Activity.RESULT_FIRST_USER);
finish();

หากตั้งค่าผลลัพธ์ของกิจกรรมของการตอบกลับการชำระเงินที่ได้รับจากแอปการชำระเงินที่เรียกใช้เป็น RESULT_OK Chrome จะตรวจสอบ methodName และ details ที่ไม่ว่างเปล่าในส่วนเพิ่มเติม หากการตรวจสอบไม่สำเร็จ Chrome จะส่งคืน Promise ที่ถูกปฏิเสธจาก 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