Panduan developer aplikasi pembayaran Android

Pelajari cara menyesuaikan aplikasi pembayaran Android Anda agar dapat digunakan dengan Pembayaran Web dan memberikan pengalaman pengguna yang lebih baik bagi pelanggan.

Dipublikasikan: 5 Mei 2020, Terakhir diperbarui: 27 Mei 2025

Payment Request API menghadirkan antarmuka berbasis browser bawaan ke web yang memungkinkan pengguna memasukkan informasi pembayaran yang diperlukan dengan lebih mudah dari sebelumnya. API ini juga dapat memanggil aplikasi pembayaran khusus platform.

Browser Support

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

Source

Alur checkout dengan aplikasi Google Pay khusus platform yang menggunakan Pembayaran Web.

Dibandingkan dengan hanya menggunakan Intent Android, Pembayaran Web memungkinkan integrasi yang lebih baik dengan browser, keamanan, dan pengalaman pengguna:

  • Aplikasi pembayaran diluncurkan sebagai modal, dalam konteks situs penjual.
  • Implementasi ini bersifat tambahan untuk aplikasi pembayaran yang ada, sehingga Anda dapat memanfaatkan basis pengguna.
  • Tanda tangan aplikasi pembayaran diperiksa untuk mencegah sideloading.
  • Aplikasi pembayaran dapat mendukung beberapa metode pembayaran.
  • Metode pembayaran apa pun, seperti mata uang kripto, transfer bank, dan lainnya, dapat diintegrasikan. Aplikasi pembayaran di perangkat Android bahkan dapat mengintegrasikan metode yang memerlukan akses ke chip hardware di perangkat.

Ada empat langkah untuk menerapkan Pembayaran Web di aplikasi pembayaran Android:

  1. Memungkinkan penjual menemukan aplikasi pembayaran Anda.
  2. Memberi tahu penjual jika pelanggan memiliki instrumen terdaftar (seperti kartu kredit) yang siap membayar.
  3. Memungkinkan pelanggan melakukan pembayaran.
  4. Memverifikasi sertifikat penandatanganan pemanggil.

Untuk melihat Pembayaran Web beraksi, lihat android-web-payment demo.

Langkah 1: Memungkinkan penjual menemukan aplikasi pembayaran Anda

Tetapkan properti related_applications dalam manifes aplikasi web sesuai dengan petunjuk di Menyiapkan metode pembayaran.

Agar dapat menggunakan aplikasi pembayaran Anda, penjual harus menggunakan Payment Request API dan menentukan metode pembayaran yang Anda dukung menggunakan ID metode pembayaran.

Jika memiliki ID metode pembayaran yang unik untuk aplikasi pembayaran Anda, Anda dapat menyiapkan metode pembayaran manifes sendiri sehingga browser dapat menemukan aplikasi Anda.

Langkah 2: Memberi tahu penjual jika pelanggan memiliki instrumen terdaftar yang siap membayar

Penjual dapat memanggil hasEnrolledInstrument() untuk membuat kueri apakah pelanggan dapat melakukan pembayaran. Anda dapat menerapkan IS_READY_TO_PAY sebagai layanan Android untuk menjawab kueri ini.

AndroidManifest.xml

Deklarasikan layanan Anda dengan filter intent dengan tindakan 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>

Layanan IS_READY_TO_PAY bersifat opsional. Jika tidak ada pengendali intent seperti itu di aplikasi pembayaran, browser web akan menganggap bahwa aplikasi tersebut selalu dapat melakukan pembayaran.

AIDL

API untuk layanan IS_READY_TO_PAY ditentukan dalam AIDL. Buat dua file AIDL dengan konten berikut:

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

Menerapkan IsReadyToPayService

Implementasi IsReadyToPayService yang paling sederhana ditampilkan dalam contoh berikut:

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

Respons

Layanan dapat mengirimkan responsnya menggunakan metode handleIsReadyToPay(Boolean).

Kotlin

callback?.handleIsReadyToPay(true)

Java

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

Izin

Anda dapat menggunakan Binder.getCallingUid() untuk memeriksa siapa pemanggilnya. Perhatikan bahwa Anda harus melakukannya dalam metode isReadyToPay, bukan dalam metode onBind, karena Android OS dapat menyimpan dalam cache dan menggunakan kembali koneksi layanan, yang tidak memicu metode 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());
        // ...

Selalu periksa parameter input untuk null saat menerima panggilan Inter-Process Communication (IPC). Hal ini sangat penting karena berbagai versi atau fork Android OS dapat berperilaku dengan cara yang tidak terduga dan menyebabkan error jika tidak ditangani.

Meskipun packageManager.getPackagesForUid() biasanya menampilkan satu elemen, kode Anda harus menangani skenario yang tidak umum saat pemanggil menggunakan beberapa nama paket. Hal ini memastikan aplikasi Anda tetap kuat.

Lihat Memverifikasi sertifikat penandatanganan pemanggil tentang cara memverifikasi bahwa paket panggilan memiliki tanda tangan yang tepat.

Parameter

Paket parameters ditambahkan di Chrome 139. Paket ini harus selalu diperiksa terhadap null.

Parameter berikut diteruskan ke layanan dalam Paket parameters:

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

packageName ditambahkan di Chrome 138. Anda harus memverifikasi parameter ini terhadap Binder.getCallingUid() sebelum menggunakan nilainya. Verifikasi ini penting karena paket parameters berada di bawah kontrol penuh pemanggil, sedangkan Binder.getCallingUid() dikontrol oleh Android OS.

topLevelCertificateChain adalah null di WebView dan di situs non-https yang biasanya digunakan untuk pengujian lokal, seperti http://localhost.

Langkah 3: Memungkinkan pelanggan melakukan pembayaran

Penjual memanggil show() untuk meluncurkan aplikasi pembayaran sehingga pelanggan dapat melakukan pembayaran. Aplikasi pembayaran dipanggil menggunakan intent Android PAY dengan informasi transaksi dalam parameter intent.

Aplikasi pembayaran merespons dengan methodName dan details, yang khusus untuk aplikasi pembayaran dan tidak dapat diakses oleh browser. Browser mengonversi string details menjadi kamus JavaScript untuk penjual menggunakan deserialisasi string JSON, tetapi tidak menerapkan validitas apa pun di luar itu. Browser tidak mengubah details; nilai parameter tersebut diteruskan langsung ke penjual.

AndroidManifest.xml

Aktivitas dengan filter intent PAY harus memiliki tag <meta-data> yang mengidentifikasi ID metode pembayaran default untuk aplikasi.

Untuk mendukung beberapa metode pembayaran, tambahkan tag <meta-data> dengan <string-array> resource.

<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 harus berupa daftar string, yang masing-masing harus berupa URL absolut yang valid dengan skema HTTPS seperti yang ditunjukkan di sini.

<?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>

Parameter

Parameter berikut diteruskan ke aktivitas sebagai tambahan 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

Nama metode yang digunakan. Elemennya adalah kunci dalam kamus methodData. Ini adalah metode yang didukung aplikasi pembayaran.

Kotlin

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

Java

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

methodData

Pemetaan dari setiap methodNames ke methodData.

Kotlin

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

Java

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

merchantName

Konten tag HTML <title> halaman checkout penjual (konteks penjelajahan tingkat atas browser).

Kotlin

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

Java

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

topLevelOrigin

Asal penjual tanpa skema (Asal tanpa skema dari konteks penjelajahan tingkat atas). Misalnya, https://mystore.com/checkout diteruskan sebagai mystore.com.

Kotlin

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

Java

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

topLevelCertificateChain

Rantai sertifikat penjual (rantai sertifikat konteks penjelajahan tingkat atas). Nilainya adalah null untuk WebView, localhost, atau file di disk. Setiap Parcelable adalah Paket dengan kunci certificate dan nilai array byte.

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

Asal tanpa skema dari konteks penjelajahan iframe yang memanggil konstruktor new PaymentRequest(methodData, details, options) di JavaScript. Jika konstruktor dipanggil dari konteks tingkat atas, nilai parameter ini akan sama dengan nilai parameter topLevelOrigin.

Kotlin

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

Java

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

total

String JSON yang mewakili jumlah total transaksi.

Kotlin

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

Java

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

Berikut adalah contoh konten string:

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

modifiers

Output JSON.stringify(details.modifiers), dengan details.modifiers hanya berisi supportedMethods, data, dan total.

paymentRequestId

Kolom PaymentRequest.id yang harus dikaitkan oleh aplikasi "push-payment" dengan status transaksi. Situs penjual akan menggunakan kolom ini untuk membuat kueri aplikasi "push-payment" untuk status transaksi di luar band.

Kotlin

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

Java

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

Respons

Aktivitas dapat mengirimkan responsnya kembali melalui setResult dengan 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();

Anda harus menentukan dua parameter sebagai tambahan Intent:

  • methodName: Nama metode yang digunakan.
  • details: String JSON yang berisi informasi yang diperlukan penjual untuk menyelesaikan transaksi. Jika true, maka details harus dibuat sedemikian rupa sehingga JSON.parse(details) akan berhasil. Jika tidak ada data yang perlu ditampilkan, string ini dapat berupa "{}", yang akan diterima situs penjual sebagai kamus JavaScript kosong.

Anda dapat meneruskan RESULT_CANCELED jika pengguna membatalkan transaksi di aplikasi pembayaran. Tindakan ini akan menyebabkan request.show() ditolak dengan AbortError di situs penjual, yang menunjukkan pembatalan pengguna.

Kotlin

setResult(Activity.RESULT_CANCELED)
finish()

Java

setResult(Activity.RESULT_CANCELED);
finish();

Mulai Chrome 149, nilai hasil berikut didukung:

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)

Jika aplikasi pembayaran gagal karena error internal, Anda dapat menunjukkannya dengan meneruskan Activity.RESULT_FIRST_USER sebagai kode hasil.

Jika INTERNAL_PAYMENT_APP_ERROR ditampilkan, request.show() akan ditolak dengan OperationError di situs penjual, yang menunjukkan error di aplikasi pembayaran.

Perbedaan antara RESULT_CANCELED (0) untuk pembatalan pengguna, yang menyebabkan AbortError, dan INTERNAL_PAYMENT_APP_ERROR (1) untuk error aplikasi internal, yang menyebabkan OperationError, memungkinkan penjual membuat alur pengguna yang lebih baik.

Kotlin

setResult(Activity.RESULT_FIRST_USER)
finish()

Java

setResult(Activity.RESULT_FIRST_USER);
finish();

Jika hasil aktivitas respons pembayaran yang diterima dari aplikasi pembayaran yang dipanggil ditetapkan ke RESULT_OK, Chrome akan memeriksa methodName dan details yang tidak kosong di tambahan. Jika validasi gagal, Chrome akan menampilkan promise yang ditolak dari request.show() dengan salah satu pesan error berikut yang ditujukan kepada developer:

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

Izin

Aktivitas dapat memeriksa pemanggil dengan metode getCallingPackage().

Kotlin

val caller: String? = callingPackage

Java

String caller = getCallingPackage();

Langkah terakhir adalah memverifikasi sertifikat penandatanganan pemanggil untuk mengonfirmasi bahwa paket panggilan memiliki tanda tangan yang tepat.

Langkah 4: Memverifikasi sertifikat penandatanganan pemanggil

Anda dapat memeriksa nama paket pemanggil dengan Binder.getCallingUid() di IS_READY_TO_PAY, dan dengan Activity.getCallingPackage() di PAY. Untuk benar-benar memverifikasi bahwa pemanggil adalah browser yang Anda maksud, Anda harus memeriksa sertifikat penandatanganannya dan memastikan bahwa sertifikat tersebut cocok dengan nilai yang benar.

Jika menargetkan API level 28 dan yang lebih baru serta berintegrasi dengan browser yang memiliki satu sertifikat penandatanganan, Anda dapat menggunakan 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() lebih disukai untuk browser dengan satu sertifikat, karena menangani rotasi sertifikat dengan benar. (Chrome memiliki satu sertifikat penandatanganan.) Aplikasi yang memiliki beberapa sertifikat penandatanganan tidak dapat merotasinya.

Jika perlu mendukung API level 27 dan yang lebih lama, atau jika perlu menangani browser dengan beberapa sertifikat penandatanganan, Anda dapat menggunakan 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);

Debug

Gunakan perintah berikut untuk mengamati error atau pesan informasi:

adb logcat | grep -i pay