Entwicklerleitfaden für Android-Zahlungs-Apps

Hier erfahren Sie, wie Sie Ihre Android-Zahlungs-App an Web Payments anpassen und Kunden so eine bessere Nutzererfahrung bieten können.

Veröffentlicht am 5. Mai 2020, zuletzt aktualisiert am 27. Mai 2025

Die Payment Request API bietet eine integrierte browserbasierte Benutzeroberfläche, mit der Nutzer erforderliche Zahlungsinformationen einfacher als je zuvor eingeben können. Über die API können auch plattformspezifische Zahlungs-Apps aufgerufen werden.

Browser Support

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

Source

Bezahlvorgang mit plattformspezifischer Google Pay App, die Web Payments verwendet.

Im Vergleich zur Verwendung von Android-Intents bietet Web Payments eine bessere Integration in den Browser sowie in Bezug auf Sicherheit und Nutzerfreundlichkeit:

  • Die Zahlungs-App wird als Modalfenster im Kontext der Händlerwebsite gestartet.
  • Die Implementierung ergänzt Ihre bestehende Zahlungs-App, sodass Sie Ihre Nutzerbasis nutzen können.
  • Die Signatur der Zahlungs-App wird geprüft, um Sideloading zu verhindern.
  • Zahlungs-Apps können mehrere Zahlungsmethoden unterstützen.
  • Es können beliebige Zahlungsmethoden wie Kryptowährungen und Banküberweisungen eingebunden werden. Zahlungs-Apps auf Android-Geräten können sogar Methoden einbinden, die Zugriff auf den Hardwarechip des Geräts erfordern.

Die Implementierung von Web Payments in einer Android-Zahlungs-App umfasst vier Schritte:

  1. Händler können Ihre Zahlungs-App finden.
  2. Händler darüber informieren, ob ein Kunde ein registriertes Zahlungsmittel (z. B. eine Kreditkarte) hat, mit dem er bezahlen kann.
  3. Kunden bezahlen lassen
  4. Überprüfen Sie das Signaturzertifikat des Anrufers.

Wenn Sie Web Payments in Aktion sehen möchten, sehen Sie sich die android-web-payment-Demo an.

Schritt 1: Händlern ermöglichen, Ihre Zahlungs-App zu finden

Legen Sie das Attribut related_applications im Web-App-Manifest gemäß der Anleitung unter Zahlungsmethode einrichten fest.

Damit ein Händler Ihre Zahlungs-App verwenden kann, muss er die Payment Request API verwenden und die von Ihnen unterstützte Zahlungsmethode mit der Zahlungsmethoden-ID angeben.

Wenn Sie eine Zahlungsmethoden-ID haben, die für Ihre Zahlungs-App eindeutig ist, können Sie Ihr eigenes Zahlungsmethodenmanifest einrichten, damit Browser Ihre App erkennen können.

Schritt 2: Händler informieren, wenn ein Kunde ein registriertes Zahlungsmittel hat, das für die Zahlung bereit ist

Der Händler kann hasEnrolledInstrument() aufrufen, um zu prüfen, ob der Kunde eine Zahlung vornehmen kann. Sie können IS_READY_TO_PAY als Android-Dienst implementieren, um diese Anfrage zu beantworten.

AndroidManifest.xml

Deklarieren Sie Ihren Dienst mit einem Intent-Filter mit der Aktion 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>

Der Dienst IS_READY_TO_PAY ist optional. Wenn in der Zahlungs-App kein solcher Intent-Handler vorhanden ist, geht der Webbrowser davon aus, dass die App immer Zahlungen ausführen kann.

AIDL

Die API für den Dienst IS_READY_TO_PAY ist in AIDL definiert. Erstellen Sie zwei AIDL-Dateien mit folgendem Inhalt:

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 implementieren

Die einfachste Implementierung von IsReadyToPayService wird im folgenden Beispiel gezeigt:

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

Antwort

Der Dienst kann seine Antwort mit der Methode handleIsReadyToPay(Boolean) senden.

Kotlin

callback?.handleIsReadyToPay(true)

Java

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

Berechtigung

Mit Binder.getCallingUid() können Sie prüfen, wer der Anrufer ist. Das muss in der Methode isReadyToPay und nicht in der Methode onBind erfolgen, da das Android-Betriebssystem die Dienstverbindung zwischenspeichern und wiederverwenden kann, wodurch die Methode onBind() nicht ausgelöst wird.

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());
        // ...

Prüfen Sie immer die Eingabeparameter für null, wenn Sie IPC-Aufrufe (Inter-Process Communication) empfangen. Das ist besonders wichtig, da sich verschiedene Versionen oder Forks des Android-Betriebssystems unerwartet verhalten und zu Fehlern führen können, wenn sie nicht entsprechend behandelt werden.

packageManager.getPackagesForUid() gibt in der Regel ein einzelnes Element zurück. Ihr Code muss jedoch das ungewöhnliche Szenario berücksichtigen, in dem ein Aufrufer mehrere Paketnamen verwendet. So bleibt Ihre Anwendung stabil.

Unter Signaturzertifikat des Anrufers überprüfen erfahren Sie, wie Sie überprüfen, ob das aufrufende Paket die richtige Signatur hat.

Parameter

Das parameters-Bundle wurde in Chrome 139 hinzugefügt. Sie sollte immer mit null abgeglichen werden.

Die folgenden Parameter werden im parameters-Bundle an den Dienst übergeben:

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

Die packageName wurde in Chrome 138 hinzugefügt. Sie müssen diesen Parameter anhand von Binder.getCallingUid() überprüfen, bevor Sie seinen Wert verwenden. Diese Überprüfung ist wichtig, da das parameters-Bundle vollständig vom Aufrufer gesteuert wird, während Binder.getCallingUid() vom Android-Betriebssystem gesteuert wird.

Die topLevelCertificateChain ist null in WebView und auf Nicht-HTTPS-Websites, die normalerweise für lokale Tests verwendet werden, z. B. http://localhost.

Schritt 3: Kunden bezahlen lassen

Der Händler ruft show() auf, um die Zahlungs-App zu starten, damit der Kunde eine Zahlung vornehmen kann. Die Zahlungs-App wird über einen Android-Intent PAY mit Transaktionsinformationen in den Intent-Parametern aufgerufen.

Die Zahlungs-App antwortet mit methodName und details, die zahlungs-appspezifisch und für den Browser nicht sichtbar sind. Der Browser konvertiert den details-String mithilfe der JSON-String-Deserialisierung in ein JavaScript-Wörterbuch für den Händler, erzwingt aber darüber hinaus keine Gültigkeit. Der Browser ändert die details nicht. Der Wert dieses Parameters wird direkt an den Händler übergeben.

AndroidManifest.xml

Die Aktivität mit dem Intent-Filter PAY sollte ein <meta-data>-Tag haben, das die Standard-Zahlungsmethodenkennung für die App angibt.

Wenn Sie mehrere Zahlungsmethoden unterstützen möchten, fügen Sie ein <meta-data>-Tag mit einer <string-array>-Ressource hinzu.

<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 muss eine Liste von Strings sein, die jeweils eine gültige, absolute URL mit einem HTTPS-Schema sein müssen, wie hier gezeigt.

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

Die folgenden Parameter werden als Intent-Extras an die Aktivität übergeben:

  • 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

Die Namen der verwendeten Methoden. Die Elemente sind die Schlüssel im methodData-Dictionary. Dies sind die Methoden, die von der Zahlungs-App unterstützt werden.

Kotlin

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

Java

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

methodData

Eine Zuordnung von jedem der methodNames zum methodData.

Kotlin

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

Java

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

merchantName

Der Inhalt des <title>-HTML-Tags auf der Zahlungsseite des Händlers (der übergeordnete Browsing-Kontext des Browsers).

Kotlin

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

Java

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

topLevelOrigin

Der Ursprung des Händlers ohne das Schema (der schemalose Ursprung des Browsing-Kontexts der obersten Ebene). Beispiel: https://mystore.com/checkout wird als mystore.com übergeben.

Kotlin

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

Java

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

topLevelCertificateChain

Die Zertifikatskette des Händlers (die Zertifikatskette des Browsing-Kontexts der obersten Ebene). Der Wert ist null für WebView, localhost oder eine Datei auf der Festplatte. Jedes Parcelable ist ein Bundle mit dem Schlüssel certificate und einem Byte-Array-Wert.

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

Der schemalose Ursprung des iFrame-Browsing-Kontexts, der den new PaymentRequest(methodData, details, options)-Konstruktor in JavaScript aufgerufen hat. Wenn der Konstruktor aus dem Kontext der obersten Ebene aufgerufen wurde, entspricht der Wert dieses Parameters dem Wert des Parameters topLevelOrigin.

Kotlin

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

Java

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

total

Der JSON-String, der den Gesamtbetrag der Transaktion darstellt.

Kotlin

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

Java

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

Hier ist ein Beispiel für den Inhalt des Strings:

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

modifiers

Die Ausgabe von JSON.stringify(details.modifiers), wobei details.modifiers nur supportedMethods, data und total enthält.

paymentRequestId

Das Feld PaymentRequest.id, das „Push-Payment“-Apps dem Transaktionsstatus zuordnen sollten. Händlerwebsites verwenden dieses Feld, um den Transaktionsstatus außerhalb des Bandes von den „Push-Payment“-Apps abzufragen.

Kotlin

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

Java

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

Antwort

Die Aktivität kann ihre Antwort über setResult mit RESULT_OK zurücksenden.

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

Sie müssen zwei Parameter als Intent-Extras angeben:

  • methodName: Der Name der verwendeten Methode.
  • details: JSON-String mit Informationen, die für den Händler erforderlich sind, um die Transaktion abzuschließen. Wenn der Erfolg true ist, muss details so konstruiert werden, dass JSON.parse(details) erfolgreich ist. Wenn keine Daten zurückgegeben werden müssen, kann dieser String "{}" sein. Die Händlerwebsite empfängt dann ein leeres JavaScript-Dictionary.

Sie können RESULT_CANCELED übergeben, wenn der Nutzer die Transaktion in der Zahlungs-App abbricht. Dadurch wird request.show() mit einem AbortError auf der Händlerwebsite abgelehnt, was auf einen Abbruch durch den Nutzer hinweist.

Kotlin

setResult(Activity.RESULT_CANCELED)
finish()

Java

setResult(Activity.RESULT_CANCELED);
finish();

Ab Chrome 149 werden die folgenden Ergebniswerte unterstützt:

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)

Wenn die Zahlungs-App aufgrund eines internen Fehlers fehlschlägt, können Sie dies angeben, indem Sie Activity.RESULT_FIRST_USER als Ergebniscode übergeben.

Wenn INTERNAL_PAYMENT_APP_ERROR zurückgegeben wird, lehnt request.show() mit einem OperationError auf der Händlerwebsite ab, was auf einen Fehler in der Zahlungs-App hinweist.

Durch die Unterscheidung zwischen RESULT_CANCELED (0) für eine Nutzerkündigung, die AbortError verursacht, und INTERNAL_PAYMENT_APP_ERROR (1) für einen internen App-Fehler, der OperationError verursacht, können Händler bessere Nutzerabläufe erstellen.

Kotlin

setResult(Activity.RESULT_FIRST_USER)
finish()

Java

setResult(Activity.RESULT_FIRST_USER);
finish();

Wenn das Aktivitätsergebnis einer Zahlungsantwort, die von der aufgerufenen Zahlungs-App empfangen wurde, auf RESULT_OK festgelegt ist, prüft Chrome, ob die Extras nicht leer sind.methodNamedetails Wenn die Validierung fehlschlägt, gibt Chrome ein abgelehntes Promise von request.show() mit einer der folgenden Fehlermeldungen für Entwickler zurück:

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

Berechtigung

Die Aktivität kann den Aufrufer mit ihrer Methode getCallingPackage() prüfen.

Kotlin

val caller: String? = callingPackage

Java

String caller = getCallingPackage();

Im letzten Schritt wird das Signaturzertifikat des Aufrufers überprüft, um zu bestätigen, dass das aufrufende Paket die richtige Signatur hat.

Schritt 4: Signaturzertifikat des Anrufers überprüfen

Sie können den Paketnamen des Anrufers mit Binder.getCallingUid() in IS_READY_TO_PAY und mit Activity.getCallingPackage() in PAY prüfen. Um tatsächlich zu überprüfen, ob der Anrufer der gewünschte Browser ist, sollten Sie das Signaturzertifikat prüfen und darauf achten, dass es mit dem richtigen Wert übereinstimmt.

Wenn Sie API-Level 28 oder höher verwenden und einen Browser mit einem einzelnen Signaturzertifikat einbinden, können Sie PackageManager.hasSigningCertificate() verwenden.

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() wird für Browser mit einem einzelnen Zertifikat bevorzugt, da die Zertifikatsrotation korrekt verarbeitet wird. Chrome hat ein einzelnes Signaturzertifikat. Bei Apps mit mehreren Signaturzertifikaten ist es nicht möglich, diese zu rotieren.

Wenn Sie API-Ebenen 27 und niedriger unterstützen müssen oder Browser mit mehreren Signaturzertifikaten verarbeiten müssen, können Sie PackageManager.GET_SIGNATURES verwenden.

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

Fehlerbehebung

Mit dem folgenden Befehl können Sie Fehler oder Informationsmeldungen beobachten:

adb logcat | grep -i pay