Découvrez comment adapter votre application de paiement Android pour fonctionner avec Web Payments et offrir une meilleure expérience utilisateur aux clients.
Les API de demande de paiement apporte au Web une interface intégrée basée sur un navigateur qui permet aux utilisateurs de saisir les informations de paiement requises plus facilement que jamais. L'API peut également appeler des applications de paiement spécifiques à la plateforme.
Par rapport à l'utilisation d'Android Intents uniquement, les paiements Web permettent une meilleure intégration du navigateur, une meilleure sécurité et une meilleure expérience utilisateur:
- L'application de paiement est lancée en mode modal, dans le cadre du site Internet du commerçant.
- La mise en œuvre est complémentaire à votre application de paiement existante, vous permettant de profiter de votre base d'utilisateurs.
- La signature de l'application de paiement est vérifiée pour éviter
charge latérale. - Les applications de paiement peuvent prendre en charge plusieurs méthodes de paiement.
- Tout mode de paiement peut être intégré, comme les crypto-monnaies, les virements bancaires et plus encore. Les applications de paiement sur les appareils Android peuvent même intégrer des méthodes qui nécessitent un accès à la puce matérielle de l'appareil.
La mise en œuvre des paiements Web dans une application de paiement Android comporte quatre étapes:
- Faites découvrir aux commerçants votre application de paiement.
- Informez un commerçant si un client a un instrument enregistré (comme une carte de crédit) qu'il est prêt à payer.
- Laissez un client effectuer le paiement.
- Vérifiez le certificat de signature de l'appelant.
Pour voir les paiements Web en action, consultez le
paiement-web-android
manifestation.
Étape 1: laissez les marchands découvrir votre application de paiement
Pour qu'un commerçant utilise votre application de paiement, il doit utiliser le API de demande de paiement et spécifiez le mode de paiement que vous prenez en charge en utilisant le identifiant du moyen de paiement.
Si vous disposez d'un identifiant de mode de paiement unique pour votre application de paiement, vous pouvez définir le vôtre manifeste du mode de paiement afin que les navigateurs puissent découvrir votre application.
Étape 2: Informer un commerçant si un client a un instrument enregistré qu'il est prêt à payer
Le commerçant peut appeler hasEnrolledInstrument ()
pour voir si le client peut effectuer un paiement. Vous pouvez mettre en œuvre IS_READY_TO_PAY
en tant que service Android pour répondre à cette requête.
AndroidManifest.xml
Déclarez votre service avec un filtre d'intention avec action
org.chromium.intent.action.IS_READY_TO_PAY
.
<un service
android:patate douce=".SampleIsReadyToPayService"
android:exported="vrai">
<intent-filter>
<action android:patate douce="org.chromium.intent.action.IS_READY_TO_PAY" />
</intent-filter>
</un service>
Les IS_READY_TO_PAY
le service est facultatif. Si un tel gestionnaire d'intention n'existe pas dans l'application de paiement, le navigateur Web suppose que l'application peut toujours effectuer des paiements.
AIDL
L'API pour IS_READY_TO_PAY
le service est défini dans AIDL. Créez deux fichiers AIDL avec le contenu suivant:
app / src / main / aidl / org / chrome / IsReadyToPayServiceCallback.aidl
package org.chromium;
interface IsReadyToPayServiceCallback {
oneway void handleIsReadyToPay(boolean isReadyToPay);
}
app / src / main / aidl / org / chrome / IsReadyToPayService.aidl
package org.chromium;
importer org.chromium.IsReadyToPayServiceCallback;interface IsReadyToPayService {
oneway void isReadyToPay(IsReadyToPayServiceCallback callback);
}
Mettre en œuvre IsReadyToPayService
La mise en œuvre la plus simple de IsReadyToPayService
illustré dans l'exemple suivant:
classer SampleIsReadyToPayService : Service() {
private val binder = object : IsReadyToPayService.Stub() {
override fun isReadyToPay(rappeler: IsReadyToPayServiceCallback?) {
rappeler?.handleIsReadyToPay(vrai)
}
}override fun onBind(J'ai essayé: Intention?): IBinder? {
revenir binder
}
}
Paramètres
Passez les paramètres suivants à onBind
comme extras Intent:
methodNames
methodData
topLevelOrigin
topLevelCertificateChain
topLevelCertificateChain
paymentRequestOrigin
override fun onBind(J'ai essayé: Intention?): IBinder? {
val extras: Empaqueter? = J'ai essayé?.extras
}
methodNames
Les noms des méthodes interrogées. Les éléments sont les clés du
methodData
dictionnaire et indiquez les méthodes prises en charge par l'application de paiement.
val methodNames: Lister<Chaîne>? = extras.getStringArrayList("methodNames")
methodData
Un mappage de chaque entrée de methodNames
au
methodData
.
val methodData: Empaqueter? = extras.getBundle("methodData")
topLevelOrigin
L'origine du marchand sans le schéma (l'origine sans le schéma du contexte de navigation de niveau supérieur). Par exemple, https://mystore.com/checkout
ça passera comme mystore.com
.
val topLevelOrigin: Chaîne? = extras.getString("topLevelOrigin")
topLevelCertificateChain
La chaîne de certificats du commerçant (la chaîne de certificats du contexte de navigation de niveau supérieur). Null pour l'hôte local et le fichier sur le disque, qui sont des contextes sécurisés sans certificats SSL. La chaîne de certificats est nécessaire car une application de paiement peut avoir des exigences de confiance différentes pour les sites Web.
val topLevelCertificateChain: Array<Colisable>? =
extras.getParcelableArray("topLevelCertificateChain")
Chaque Colisable
c'est un Empaqueter
avec un "certificat"
key et une valeur de tableau d'octets.
val list: Lister<ByteArray>? = topLevelCertificateChain?.mapNotNull { p ->
(p as Empaqueter).getByteArray("certificat")
}
paymentRequestOrigin
La source non-schéma du contexte de navigation iframe qui invoquait new PaymentRequest (methodData, détails, options)
constructeur en JavaScript. Si le constructeur a été appelé à partir du contexte de niveau supérieur, la valeur de ce paramètre est égale à la valeur de topLevelOrigin
paramètre.
val paymentRequestOrigin: Chaîne? = extras.getString("paymentRequestOrigin")
Répondre
Le service peut envoyer sa réponse via handleIsReadyToPay (booléen)
méthode.
rappeler?.handleIsReadyToPay(vrai)
Permis
Vous pouvez utiliser Binder.getCallingUid ()
pour vérifier qui est l'appelant. Notez que vous devez le faire dans le isReadyToPay
méthode, pas la onBind
méthode.
override fun isReadyToPay(rappeler: IsReadyToPayServiceCallback?) {
try {
val callingPackage = packageManager.getNameForUid(Binder.getCallingUid())
Voir Vérifier le certificat de signature de l'appelant pour savoir comment vérifier que le paquet appelant a la signature correcte.
Étape 3: laissez un client vérifier
Le commerçant appelle Spectacle()
pour démarrer l'application de paiement afin que le client puisse effectuer un paiement. L'application payante est appelée via une intention Android PAYER
avec les informations de transaction dans les paramètres d'intention.
L'application de paiement répond avec methodName
y des détails
, qui sont spécifiques à l'application de paiement et sont opaques pour le navigateur. Le navigateur convertit le des détails
Chaîne dans un objet JavaScript pour le marchand via la désérialisation JSON, mais elle n'applique aucune validité au-delà de cela. Le navigateur ne modifie pas le
des détails
; la valeur de ce paramètre est transmise directement au commerçant.
AndroidManifest.xml
L'activité avec le PAYER
Le filtre d'intention doit avoir un Une balise qui identifie l'identifiant du mode de paiement par défaut pour l'application.
Pour prendre en charge plusieurs modes de paiement, ajoutez un étiquette avec un
Ressource.
<activity
android:patate douce=".PaymentActivity"
android:thème="@style/Theme.SamplePay.Dialog">
<intent-filter>
<action android:patate douce="org.chromium.intent.action.PAY" />
</intent-filter><meta-data
android:patate douce="org.chromium.default_payment_method_name"
android:valeur="https://bobpay.xyz/pay" />
<meta-data
android:patate douce="org.chromium.payment_method_names"
android:Ressource="@array/method_names" />
</activity>
Les Ressource
doit être une liste de chaînes, dont chacune doit être une URL absolue valide avec un schéma HTTPS comme indiqué ici.
<?xml version="1.0" encoding="utf-8"?>
<Ressources>
<string-array patate douce="method_names">
<Objet>https://alicepay.com/put/optional/path/here</Objet>
<Objet>https://charliepay.com/put/optional/path/here</Objet>
</string-array>
</Ressources>
Paramètres
Les paramètres suivants sont transmis à l'activité en tant que suppléments d'intention:
methodNames
methodData
topLevelOrigin
topLevelCertificateChain
paymentRequestOrigin
le total
modificateurs
paymentRequestId
val extras: Empaqueter? = J'ai essayé?.extras
methodNames
Les noms des méthodes utilisées. Les éléments sont les clés du
methodData
dictionnaire. Voici les méthodes prises en charge par l'application de paiement.
val methodNames: Lister<Chaîne>? = extras.getStringArrayList("methodNames")
methodData
Une cartographie de chacun des methodNames
au
methodData
.
val methodData: Empaqueter? = extras.getBundle("methodData")
Nom du commerçant
Le contenu du
Balise HTML de la page de paiement du marchand (le contexte de navigation de niveau supérieur du navigateur).
val merchantName: Chaîne? = extras.getString("merchantName")
topLevelOrigin
L'origine du marchand sans le schéma (L'origine sans le schéma du contexte de navigation de niveau supérieur). Par exemple, https://mystore.com/checkout
ça se passe comme mystore.com
.
val topLevelOrigin: Chaîne? = extras.getString("topLevelOrigin")
topLevelCertificateChain
La chaîne de certificats du commerçant (la chaîne de certificats du contexte de navigation de niveau supérieur). Null pour l'hôte local et le fichier sur le disque, qui sont des contextes sécurisés sans certificats SSL. Chaque Colisable
c'est un paquet avec un
certificat
key et une valeur de tableau d'octets.
val topLevelCertificateChain: Array<Colisable>? =
extras.getParcelableArray("topLevelCertificateChain")
val list: Lister<ByteArray>? = topLevelCertificateChain?.mapNotNull { p ->
(p as Empaqueter).getByteArray("certificat")
}
paymentRequestOrigin
La source non-schéma du contexte de navigation iframe qui invoquait new PaymentRequest (methodData, détails, options)
constructeur en JavaScript. Si le constructeur a été appelé à partir du contexte de niveau supérieur, la valeur de ce paramètre est égale à la valeur de topLevelOrigin
paramètre.
val paymentRequestOrigin: Chaîne? = extras.getString("paymentRequestOrigin")
le total
La chaîne JSON qui représente le montant total de la transaction.
val le total: Chaîne? = extras.getString("total")
Voici un exemple de contenu de chaîne:
{"currency":"USD","value":"25.00"}
modificateurs
La sortie de JSON.stringify (details.modifiers)
, où détails.modificateurs
contenir seulement Méthodes prises en charge
y le total
.
paymentRequestId
Les PaymentRequest.id
champ que les applications de «paiement automatique» doivent associer au statut de la transaction. Les sites Web marchands utiliseront ce champ pour interroger les applications de «paiement automatique» sur l'état des transactions hors bande.
val paymentRequestId: Chaîne? = extras.getString("paymentRequestId")
Répondre
L'activité peut envoyer sa réponse via setResult
avec RESULT_OK
.
setResult(Activity.RESULT_OK, Intention().apply {
putExtra("methodName", "https://bobpay.xyz/pay")
putExtra("details", "{"token": "put-some-data-here"}")
})
finish()
Vous devez spécifier deux paramètres comme extras d'intention:
methodName
: Le nom de la méthode utilisée.des détails
: Chaîne JSON contenant les informations nécessaires au commerçant pour terminer la transaction. Si le succès estvrai
, ensuitedes détails
doit être construit de telle manière queJSON.parse (détails)
ça pourrait arriver.
Tu peux passer RESULT_CANCELED
si la transaction n'a pas été effectuée dans l'application de paiement, par exemple si l'utilisateur n'a pas saisi le code PIN correct pour son compte dans l'application de paiement. Le navigateur peut permettre à l'utilisateur de choisir une autre application de paiement.
setResult(RESULT_CANCELED)
finish()
Si le résultat de l'activité d'une réponse de paiement reçue de l'application de paiement appelée est défini sur RESULT_OK
, alors Chrome vérifiera s'il n'est pas vide methodName
y
des détails
dans vos extras. Si la validation échoue, Chrome renverra une promesse rejetée de request.show ()
avec l'un des messages d'erreur suivants rencontrés par les développeurs:
'Payment app returned invalid response. Missing field "details".'
'Payment app returned invalid response. Missing field "methodName".'
Permis
L'activité peut vérifier l'appelant avec son getCallingPackage ()
méthode.
val caller: Chaîne? = callingPackage
La dernière étape consiste à vérifier le certificat de signature de l'appelant pour confirmer que le paquet appelant a la signature correcte.
Étape 4: vérifier le certificat de signature de l'appelant
Vous pouvez vérifier le nom du package de l'appelant avec Binder.getCallingUid ()
au
IS_READY_TO_PAY
, et avec Activity.getCallingPackage ()
au PAYER
. Pour vraiment vérifier que l'appelant est le navigateur que vous avez à l'esprit, vous devez vérifier son certificat de signature et vous assurer qu'il correspond à la valeur correcte.
Si vous ciblez le niveau d'API 28 et plus et que vous vous intégrez à un navigateur doté d'un seul certificat de signature, vous pouvez utiliser
PackageManager.hasSigningCertificate ()
.
val packageName: Chaîne = …
val certificat: ByteArray = …
val verified = packageManager.hasSigningCertificate(
callingPackage,
certificat,
PackageManager.CERT_INPUT_SHA256
)
PackageManager.hasSigningCertificate ()
Il est préférable pour les navigateurs de certificats uniques car il gère correctement la rotation des certificats. (Chrome n'a qu'un seul certificat de signature.) Les applications qui ont plusieurs certificats de signature ne peuvent pas les faire pivoter.
Si vous devez prendre en charge des niveaux d'API plus anciens que 27 et inférieurs, ou si vous devez gérer des navigateurs avec plusieurs certificats de signature, vous pouvez utiliser
PackageManager.GET_SIGNATURES
.
val packageName: Chaîne = …
val certificates: Ensemble<ByteArray> = … 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.Taille == certificates.Taille &&
signatures.là { s -> certificates.any { it.contentEquals(s) } }