Hier erfahren Sie, wie Sie Ihre Android-Zahlungs-App für Webzahlungen anpassen und so die Nutzerfreundlichkeit für Kunden verbessern.
Veröffentlicht: 5. Mai 2020, letzte Aktualisierung: 27. Mai 2025
Die Payment Request API bietet eine integrierte browserbasierte Benutzeroberfläche, mit der Nutzer erforderliche Zahlungsinformationen einfacher denn je eingeben können. Die API kann auch plattformspezifische Zahlungs-Apps aufrufen.
Im Vergleich zur Verwendung von nur Android-Intents ermöglichen Webzahlungen eine bessere Integration in den Browser, eine höhere Sicherheit und eine bessere Nutzerfreundlichkeit:
- Die Zahlungs-App wird als Modalfenster im Kontext der Händlerwebsite gestartet.
- Die Implementierung ergänzt Ihre bestehende Zahlungs-App und ermöglicht es Ihnen, Ihre Nutzerbasis zu nutzen.
- 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 integrieren, für die Zugriff auf den Hardwarechip des Geräts erforderlich ist.
Die Implementierung von Webzahlungen in einer Android-Zahlungs-App umfasst vier Schritte:
- Händler auf Ihre Zahlungs-App aufmerksam machen
- Informiere den Händler, ob ein Kunde ein registriertes Zahlungsmittel (z. B. eine Kreditkarte) hat, mit dem er bezahlen kann.
- Kunden die Zahlung ermöglichen
- Prüfen Sie das Signaturzertifikat des Anrufers.
In der Demo android-web-payment können Sie Webzahlungen in Aktion sehen.
Schritt 1: Händlern Ihre Zahlungs-App präsentieren
Legen Sie das Attribut related_applications
im Manifest der Webanwendung 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 mithilfe der Zahlungsmethode-ID angeben.
Wenn Sie eine Zahlungsmethoden-ID haben, die nur für Ihre Zahlungs-App gilt, können Sie ein eigenes Manifest für Zahlungsmethoden einrichten, damit Ihre App von Browsern gefunden werden kann.
Schritt 2: Händler informieren, ob ein Kunde ein registriertes Zahlungsmittel hat, das zur Zahlung bereit ist
Der Händler kann hasEnrolledInstrument()
anrufen, um nachzufragen, ob der Kunde eine Zahlung vornehmen kann. Sie können IS_READY_TO_PAY
als Android-Dienst implementieren, um diese Abfrage 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 IS_READY_TO_PAY
-Dienst 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 IS_READY_TO_PAY
-Dienst 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:
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
}
}
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.
callback?.handleIsReadyToPay(true)
if (callback != null) {
callback.handleIsReadyToPay(true);
}
Berechtigung
Mit Binder.getCallingUid()
können Sie prüfen, wer der Anrufer ist. Beachten Sie, dass Sie dies in der Methode isReadyToPay
und nicht in der Methode onBind
tun müssen, da das Android-Betriebssystem die Dienstverbindung im Cache speichern und wiederverwenden kann, was die Methode onBind()
nicht auslöst.
override fun isReadyToPay(callback: IsReadyToPayServiceCallback?, parameters: Bundle?) {
try {
val untrustedPackageName = parameters?.getString("packageName")
val actualPackageNames = packageManager.getPackagesForUid(Binder.getCallingUid())
// ...
@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 bei IPC-Aufrufen (Inter-Process Communication) immer die Eingabeparameter für null
. Das ist besonders wichtig, da sich verschiedene Versionen oder Forks des Android-Betriebssystems unerwartet verhalten und bei unsachgemäßer Handhabung zu Fehlern führen können.
packageManager.getPackagesForUid()
gibt normalerweise ein einzelnes Element zurück. Ihr Code muss jedoch auch das seltene Szenario abdecken, in dem ein Aufrufer mehrere Paketnamen verwendet. So bleibt Ihre Anwendung robust.
Unter Signaturzertifikat des Anrufers prüfen erfahren Sie, wie Sie prüfen, ob das anrufende 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
Das packageName
wurde in Chrome 138 hinzugefügt. Sie müssen diesen Parameter mit Binder.getCallingUid()
vergleichen, 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.
In WebView und auf Websites ohne https, die in der Regel für lokale Tests verwendet werden, z. B. http://localhost
, ist die topLevelCertificateChain
null
.
Schritt 3: Kunden die Zahlung ermöglichen
Der Händler ruft show()
auf, um die Zahlungs-App zu starten, damit der Kunde eine Zahlung vornehmen kann. Die Zahlungs-App wird über eine Android-Intent-PAY
mit Transaktionsinformationen in den Intent-Parametern aufgerufen.
Die Zahlungs-App antwortet mit methodName
und details
, die für die Zahlungs-App spezifisch sind und für den Browser nicht transparent sind. Der Browser konvertiert den details
-String mithilfe der JSON-String-Deserialisierung in ein JavaScript-Wörterbuch für den Händler, erzwingt aber keine Gültigkeit darüber hinaus. Der Browser ändert details
nicht. Der Wert dieses Parameters wird direkt an den Händler übergeben.
AndroidManifest.xml
Die Aktivität mit dem PAY
-Intent-Filter sollte ein <meta-data>
-Tag enthalten, das die Standard-Zahlungsmethode für die App identifiziert.
Wenn du mehrere Zahlungsmethoden unterstützen möchtest, füge 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, von denen jeder eine gültige absolute URL mit einem HTTPS-Schema sein muss, 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
val extras: Bundle? = getIntent()?.extras
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.
val methodNames: List<String>? = extras.getStringArrayList("methodNames")
List<String> methodNames = extras.getStringArrayList("methodNames");
methodData
Eine Zuordnung von jedem der methodNames
zu methodData
.
val methodData: Bundle? = extras.getBundle("methodData")
Bundle methodData = extras.getBundle("methodData");
merchantName
Der Inhalt des <title>
-HTML-Tags der Zahlungsseite des Händlers (Browser-Browserkontext der obersten Ebene).
val merchantName: String? = extras.getString("merchantName")
String merchantName = extras.getString("merchantName");
topLevelOrigin
Der Ursprung des Händlers ohne Schema (der schemalose Ursprung des Browserkontexts der obersten Ebene). Beispiel: https://mystore.com/checkout
wird als mystore.com
übergeben.
val topLevelOrigin: String? = extras.getString("topLevelOrigin")
String topLevelOrigin = extras.getString("topLevelOrigin");
topLevelCertificateChain
Die Zertifikatskette des Händlers (die Zertifikatskette des Browserkontexts der obersten Ebene). Der Wert ist null
für WebView, localhost oder eine Datei auf dem Laufwerk.
Jede Parcelable
ist ein Bundle mit einem certificate
-Schlüssel und einem Byte-Array-Wert.
val topLevelCertificateChain: Array<Parcelable>? =
extras.getParcelableArray("topLevelCertificateChain")
val list: List<ByteArray>? = topLevelCertificateChain?.mapNotNull { p ->
(p as Bundle).getByteArray("certificate")
}
Parcelable[] topLevelCertificateChain =
extras.getParcelableArray("topLevelCertificateChain");
if (topLevelCertificateChain != null) {
for (Parcelable p : topLevelCertificateChain) {
if (p != null && p instanceof Bundle) {
((Bundle) p).getByteArray("certificate");
}
}
}
paymentRequestOrigin
Der schemeless-Ursprung des iFrame-Browser-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
.
val paymentRequestOrigin: String? = extras.getString("paymentRequestOrigin")
String paymentRequestOrigin = extras.getString("paymentRequestOrigin");
total
Der JSON-String, der den Gesamtbetrag der Transaktion darstellt.
val total: String? = extras.getString("total")
String total = extras.getString("total");
Hier 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 PaymentRequest.id
-Feld, das Apps für Push-Zahlungen dem Transaktionsstatus zuordnen sollten. Händlerwebsites verwenden dieses Feld, um die Push-Zahlungs-Apps außerhalb des Bandes nach dem Transaktionsstatus zu fragen.
val paymentRequestId: String? = extras.getString("paymentRequestId")
String paymentRequestId = extras.getString("paymentRequestId");
Antwort
Die Aktivität kann ihre Antwort über setResult
mit RESULT_OK
zurücksenden.
setResult(Activity.RESULT_OK, Intent().apply {
putExtra("methodName", "https://bobbucks.dev/pay")
putExtra("details", "{\"token\": \"put-some-data-here\"}")
})
finish()
Intent result = new Intent();
Bundle extras = new Bundle();
extras.putString("methodName", "https://bobbucks.dev/pay");
extras.putString("instrumentDetails", "{\"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 der Händler zum Abschließen der Transaktion benötigt. Wenn „success“true
ist, mussdetails
so aufgebaut sein, dassJSON.parse(details)
erfolgreich ist. Wenn keine Daten zurückgegeben werden müssen, kann dieser String"{}"
sein. Die Händlerwebsite erhält dann ein leeres JavaScript-Dictionary.
Du kannst RESULT_CANCELED
übergeben, wenn die Transaktion in der Zahlungs-App nicht abgeschlossen wurde, z. B. wenn der Nutzer in der Zahlungs-App nicht die richtige PIN für sein Konto eingegeben hat. Der Browser kann den Nutzer auffordern, eine andere Zahlungs-App auszuwählen.
setResult(Activity.RESULT_CANCELED)
finish()
setResult(Activity.RESULT_CANCELED);
finish();
Wenn das Aktivitätsergebnis einer Zahlungsantwort, die von der aufgerufenen Zahlungs-App empfangen wurde, auf RESULT_OK
festgelegt ist, prüft Chrome in den Extras, ob methodName
und details
nicht leer sind. Wenn die Validierung fehlschlägt, gibt Chrome ein abgelehntes Versprechen 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 Anrufer mit der Methode getCallingPackage()
prüfen.
val caller: String? = callingPackage
String caller = getCallingPackage();
Im letzten Schritt wird das Signaturzertifikat des Aufrufers überprüft, um sicherzustellen, dass das Aufrufpaket die richtige Signatur hat.
Schritt 4: Signaturzertifikat des Anrufers prü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 zu überprüfen, ob der Anrufer der gewünschte Browser ist, sollten Sie sein Signaturzertifikat prüfen und darauf achten, dass es mit dem richtigen Wert übereinstimmt.
Wenn Sie API-Level 28 oder höher anstreben und die Integration in einen Browser mit einem einzelnen Signaturzertifikat vornehmen, können Sie PackageManager.hasSigningCertificate()
verwenden.
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
)
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 es die Zertifikatsrotation korrekt verarbeitet. Chrome hat ein einzelnes Signaturzertifikat. Bei Apps mit mehreren Signaturzertifikaten ist das nicht möglich.
Wenn Sie API-Level 27 und niedriger unterstützen oder Browser mit mehreren Signaturzertifikaten verarbeiten müssen, können Sie PackageManager.GET_SIGNATURES
verwenden.
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)
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