Entwicklerleitfaden für Android-Zahlungs-Apps

Hier erfährst du, wie du deine Android-Zahlungs-App für Web Payments anpassen und so für eine bessere Nutzererfahrung sorgen kannst.

Die Payment Request API ermöglicht das Eine integrierte browserbasierte Oberfläche, auf der Nutzer die fälligen Zahlungen eingeben können einfacher als je zuvor. Die API kann auch eine plattformspezifische Zahlung aufrufen. Apps.

Unterstützte Browser

  • Chrome: 60 <ph type="x-smartling-placeholder">
  • Rand: 15. <ph type="x-smartling-placeholder">
  • Firefox: hinter einer Flagge.
  • Safari: 11.1 <ph type="x-smartling-placeholder">

Quelle

<ph type="x-smartling-placeholder">
</ph>
Bezahlvorgang mit einer plattformspezifischen Google Pay-App, die Webzahlungen verwendet.

Im Vergleich zur Nutzung reiner Android Intents ermöglichen Web Payments eine bessere Integration mit dem Browser, der Sicherheit und der Nutzererfahrung:

  • Die Zahlungs-App wird als modales Fenster im Kontext der Händlerwebsite gestartet.
  • Die Implementierung ergänzt Ihre bestehende Zahlungs-App und bietet Ihnen folgende Vorteile: Ihre Nutzerbasis zunutze machen.
  • Die Signatur der Zahlungs-App wird geprüft, um zu verhindern, sideloading verwendet werden.
  • Zahlungs-Apps unterstützen mehrere Zahlungsmethoden.
  • Alle Zahlungsmethoden, z. B. Kryptowährungen oder Banküberweisungen, integriert. Zahlungs-Apps auf Android-Geräten können sogar Methoden integrieren, benötigen Zugriff auf den Hardwarechip des Geräts.

Zur Implementierung von Web Payments in einer Android-Zahlungs-App sind vier Schritte erforderlich:

  1. Machen Sie Händler auf Ihre Zahlungs-App aufmerksam.
  2. Händler informieren, wenn ein Kunde ein registriertes Zahlungsmittel (z. B. eine Gutschrift) hat die zum Bezahlen bereit ist.
  3. Kunden die Zahlung ermöglichen lassen.
  4. Prüfen Sie das Signaturzertifikat des Aufrufers.

Informationen zu Web Payments in Aktion findest du in der android-web-payment .

Schritt 1: Händler Ihre Zahlungs-App finden lassen

Damit ein Händler Ihre Zahlungs-App nutzen kann, muss er das Zahlungs-App-Konto API anfordern und die von Ihnen unterstützte Zahlungsmethode über die Zahlungsmethode Kennung.

Wenn Sie eine eindeutige Zahlungsmethoden-ID für Ihre Zahlungs-App haben, können Sie können Sie Ihre eigene Zahlungsmethode einrichten. Manifestdatei, damit Browser Ihre App zu finden.

Schritt 2: Händler informieren, ob ein Kunde ein registriertes Zahlungsmittel hat, das zum Bezahlen bereitsteht

Der Händler kann hasEnrolledInstrument() aufrufen, um zu fragen, ob der Kunde eine Zahlung ausführen kann. Sie können Implementiere IS_READY_TO_PAY als Android-Dienst, 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 es keinen solchen Intent-Handler in der Bezahl-App an, dann geht der Webbrowser davon aus, dass die App jederzeit Zahlungen.

AIDL

Die API für den Dienst IS_READY_TO_PAY ist in AIDL definiert. Zwei AIDL erstellen Dateien mit folgendem Inhalt:

app/src/main/aidl/org/chromium/IsReadyToPayServiceCallback.aidl

package org.chromium;
interface IsReadyToPayServiceCallback {
    oneway void handleIsReadyToPay(boolean isReadyToPay);
}

app/src/main/aidl/org/chromium/IsReadyToPayService.aidl

package org.chromium;
import org.chromium.IsReadyToPayServiceCallback;

interface IsReadyToPayService {
    oneway void isReadyToPay(IsReadyToPayServiceCallback callback);
}

IsReadyToPayService implementieren

Im Folgenden sehen Sie die einfachste Implementierung von IsReadyToPayService. Beispiel:

class SampleIsReadyToPayService : Service() {
  private val binder = object : IsReadyToPayService.Stub() {
    override fun isReadyToPay(callback: IsReadyToPayServiceCallback?) {
      callback?.handleIsReadyToPay(true)
    }
  }

  override fun onBind(intent: Intent?): IBinder? {
    return binder
  }
}

Antwort

Der Dienst kann seine Antwort über die Methode handleIsReadyToPay(Boolean) senden.

callback?.handleIsReadyToPay(true)

Berechtigung

Mit Binder.getCallingUid() können Sie prüfen, wer der Anrufer ist. Beachten Sie, dass Sie müssen Sie dies in der Methode isReadyToPay und nicht in der Methode onBind tun.

override fun isReadyToPay(callback: IsReadyToPayServiceCallback?) {
  try {
    val callingPackage = packageManager.getNameForUid(Binder.getCallingUid())
    // …

Weitere Informationen finden Sie im Hilfeartikel Signaturzertifikat des Anrufers überprüfen. um zu prüfen, ob das aufrufende Paket die richtige Signatur hat.

Schritt 3: Kunden die Zahlung vornehmen lassen

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

Die Zahlungs-App antwortet mit methodName und details, dies sind die Zahlungs-App spezifisch und für den Browser undurchsichtig. Der Browser konvertiert die details. in ein JavaScript-Objekt für den Händler über JSON-Deserialisierung, aber erwirkt darüber hinaus keine Gültigkeit. Der Browser nimmt keine Änderungen an der details; wird der Wert dieses Parameters 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-ID der Zahlungsmethode für die App.

Wenn du mehrere Zahlungsmethoden unterstützen möchtest, füge ein <meta-data>-Tag mit einem <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/method_names" />
</activity>

Die resource muss eine Liste von Strings sein, von denen jeder eine gültige, absolute URL mit einem HTTPS-Schema wie hier gezeigt.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="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
  • topLevelOrigin
  • topLevelCertificateChain
  • paymentRequestOrigin
  • total
  • modifiers
  • paymentRequestId
val extras: Bundle? = intent?.extras

methodNames

Die Namen der verwendeten Methoden. Die Elemente sind die Schlüssel im methodData-Wörterbuch. Die folgenden Methoden werden von der Zahlungs-App unterstützt.

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

methodData

Eine Zuordnung von methodNames zur methodData

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

merchantName

Der Inhalt des HTML-Tags <title> auf der Zahlungsseite des Händlers (der Browserkontext auf oberster Ebene angezeigt wird.

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

topLevelOrigin

Der Ursprung des Händlers ohne das Schema (der schemalose Ursprung des Browserkontext auf oberster Ebene). Beispiel: https://mystore.com/checkout ist übergeben als mystore.com.

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

topLevelCertificateChain

Die Zertifikatskette des Händlers (die Zertifikatskette der obersten Ebene Browserkontext). Null für localhost und Datei auf dem Laufwerk, die beide sicher sind Kontexte ohne SSL-Zertifikate. Jede Parcelable ist ein Bundle mit einem certificate-Schlüssel und einen Byte-Arraywert.

val topLevelCertificateChain: Array<Parcelable>? =
    extras.getParcelableArray("topLevelCertificateChain")
val list: List<ByteArray>? = topLevelCertificateChain?.mapNotNull { p ->
  (p as Bundle).getByteArray("certificate")
}

paymentRequestOrigin

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

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

total

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

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

Hier ist ein Beispielinhalt des Strings:

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

modifiers

Die Ausgabe von JSON.stringify(details.modifiers), wobei details.modifiers enthalten nur supportedMethods und total.

paymentRequestId

Das Feld PaymentRequest.id für „Push-Zahlung“ die Apps mit der Transaktionsstatus Händlerwebsites verwenden dieses Feld, um die &quot;push-payment&quot; Apps für den Status der Transaktion außerhalb des Bandes.

val paymentRequestId: String? = 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()

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

  • methodName: Der Name der verwendeten Methode.
  • details: JSON-String mit Informationen, die der Händler benötigt um die Transaktion abzuschließen. Wenn der Erfolg true lautet, muss details so konstruiert ist, dass JSON.parse(details) erfolgreich sein wird.

Sie können RESULT_CANCELED übergeben, wenn die Transaktion im Zahlungs-App, z. B. wenn der Nutzer bei der Eingabe des Codes Konto in der Zahlungs-App. Der Browser lässt den Nutzer möglicherweise ein eine andere Zahlungs-App verwenden.

setResult(RESULT_CANCELED)
finish()

Wenn die Aktivität aus einer Zahlungsantwort resultiert, die von der aufgerufenen Zahlung empfangen wurde App auf „RESULT_OK“ gesetzt ist, sucht Chrome nach dem nicht leeren methodName und details in seinen Extras. Wenn die Überprüfung fehlschlägt, zeigt Chrome eine abgelehnte Versprechen von request.show() mit einem der folgenden für Entwickler auftretenden Fehler Nachrichten:

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

Berechtigung

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

val caller: String? = callingPackage

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

Schritt 4: Signaturzertifikat des Aufrufers prüfen

Du kannst den Paketnamen des Anrufers mit Binder.getCallingUid() in IS_READY_TO_PAY und mit Activity.getCallingPackage() in PAY. Um um zu überprüfen, ob es sich bei dem Aufrufer um den Browser handelt, den Sie in Betracht ziehen, prüfen Sie das Signaturzertifikat und vergewissern Sie sich, dass es mit dem richtigen Wert.

Bei Ausrichtung auf API-Level 28 oder höher und Integration in einen Browser und ein einzelnes Signaturzertifikat hat, können Sie PackageManager.hasSigningCertificate()

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
)

PackageManager.hasSigningCertificate() wird für einzelne Zertifikate bevorzugt da er die Zertifikatsrotation korrekt verarbeitet. (Chrome hat eine ein einzelnes Signaturzertifikat.) Apps mit mehreren Signaturzertifikaten können drehen.

Wenn Sie ältere API-Levels 27 und niedriger unterstützen oder Browser mit mehreren Signaturzertifikaten verwenden, können Sie PackageManager.GET_SIGNATURES

val packageName: String =  // The caller's package name
val certificates: Set<ByteArray> =  // The correct set of signing certificates

val packageInfo = getPackageInfo(packageName, PackageManager.GET_SIGNATURES)
val sha256 = MessageDigest.getInstance("SHA-256")
val signatures = packageInfo.signatures.map { sha256.digest(it.toByteArray()) }
val verified = signatures.size == certificates.size &&
    signatures.all { s -> certificates.any { it.contentEquals(s) } }