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.
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:
- Händler können Ihre Zahlungs-App finden.
- Händler darüber informieren, ob ein Kunde ein registriertes Zahlungsmittel (z. B. eine Kreditkarte) hat, mit dem er bezahlen kann.
- Kunden bezahlen lassen
- Ü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:
packageNamemethodNamesmethodDatatopLevelOriginpaymentRequestOrigintopLevelCertificateChain
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:
methodNamesmethodDatamerchantNametopLevelOrigintopLevelCertificateChainpaymentRequestOrigintotalmodifierspaymentRequestIdpaymentOptionsshippingOptions
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 Erfolgtrueist, mussdetailsso konstruiert werden, dassJSON.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