Découvrez comment adapter votre application de paiement Android pour qu'elle fonctionne avec les paiements Web et offrir une meilleure expérience utilisateur aux clients.
Publié le 5 mai 2020, dernière mise à jour le 27 mai 2025
L'API Payment Request apporte au Web une interface intégrée basée sur le 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 plate-forme.
Par rapport à l'utilisation des intents Android uniquement, les paiements Web permettent une meilleure intégration avec le navigateur, la sécurité et l'expérience utilisateur :
- L'application de paiement est lancée sous forme de fenêtre modale, dans le contexte du site Web du marchand.
- L'implémentation est complémentaire à votre application de paiement existante, ce qui vous permet de tirer parti de votre base d'utilisateurs.
- La signature de l'application de paiement est vérifiée pour empêcher le chargement de fichiers APK.
- Les applications de paiement peuvent accepter plusieurs modes de paiement.
- Vous pouvez intégrer n'importe quel mode de paiement, comme les cryptomonnaies, les virements bancaires, etc. Les applications de paiement sur les appareils Android peuvent même intégrer des méthodes qui nécessitent l'accès à la puce matérielle de l'appareil.
L'implémentation de Web Payments dans une application de paiement Android se fait en quatre étapes :
- Permettez aux marchands de découvrir votre application de paiement.
- Indiquez à un marchand si un client dispose d'un instrument enregistré (comme une carte de crédit) prêt à être utilisé pour le paiement.
- Permettre à un client d'effectuer un paiement
- Vérifiez le certificat de signature de l'appelant.
Pour voir Web Payments en action, consultez la démo android-web-payment.
Étape 1 : Permettez aux marchands de découvrir votre application de paiement
Définissez la propriété related_applications dans le fichier manifeste de l'application Web en suivant les instructions de la section Configurer un mode de paiement.
Pour qu'un marchand puisse utiliser votre application de paiement, il doit utiliser l'API Payment Request et spécifier le mode de paiement que vous acceptez à l'aide de l'identifiant du mode de paiement.
Si vous disposez d'un identifiant de mode de paiement unique à votre application de paiement, vous pouvez configurer votre propre manifeste de mode de paiement afin que les navigateurs puissent découvrir votre application.
Étape 2 : Informez un marchand si un client a enregistré un instrument de paiement prêt à être utilisé
Le marchand peut appeler hasEnrolledInstrument() pour vérifier si le client peut effectuer un paiement. Vous pouvez implémenter 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'intent comportant l'action 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>
Le service IS_READY_TO_PAY est facultatif. S'il n'y a pas de gestionnaire d'intent dans l'application de paiement, le navigateur Web suppose que l'application peut toujours effectuer des paiements.
AIDL
L'API du service IS_READY_TO_PAY est définie dans AIDL. Créez deux fichiers AIDL avec le contenu suivant :
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);
}
Mettre en œuvre la directive IsReadyToPayService
L'implémentation la plus simple de IsReadyToPayService est illustrée dans l'exemple suivant :
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;
}
}
Réponse
Le service peut envoyer sa réponse à l'aide de la méthode handleIsReadyToPay(Boolean).
Kotlin
callback?.handleIsReadyToPay(true)
Java
if (callback != null) {
callback.handleIsReadyToPay(true);
}
Autorisation
Vous pouvez utiliser Binder.getCallingUid() pour vérifier l'identité de l'appelant. Notez que vous devez le faire dans la méthode isReadyToPay, et non dans la méthode onBind, car l'OS Android peut mettre en cache et réutiliser la connexion au service, ce qui ne déclenche pas la méthode onBind().
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());
// ...
Vérifiez toujours les paramètres d'entrée pour null lorsque vous recevez des appels de communication entre processus (IPC). C'est particulièrement important, car différentes versions ou forks de l'OS Android peuvent se comporter de manière inattendue et entraîner des erreurs s'ils ne sont pas gérés.
Bien que packageManager.getPackagesForUid() renvoie généralement un seul élément, votre code doit gérer le scénario peu courant dans lequel un appelant utilise plusieurs noms de package. Cela garantit la robustesse de votre application.
Consultez Vérifier le certificat de signature de l'appelant pour savoir comment vérifier que le package appelant possède la bonne signature.
Paramètres
Le bundle parameters a été ajouté dans Chrome 139. Elle doit toujours être vérifiée par rapport à null.
Les paramètres suivants sont transmis au service dans le Bundle parameters :
packageNamemethodNamesmethodDatatopLevelOriginpaymentRequestOrigintopLevelCertificateChain
L'icône packageName a été ajoutée dans Chrome 138. Vous devez valider ce paramètre par rapport à Binder.getCallingUid() avant d'utiliser sa valeur. Cette validation est essentielle, car le bundle parameters est entièrement contrôlé par l'appelant, tandis que Binder.getCallingUid() est contrôlé par l'OS Android.
topLevelCertificateChain est null dans WebView et sur les sites Web non HTTPS qui sont généralement utilisés pour les tests locaux, tels que http://localhost.
Étape 3 : Permettre à un client d'effectuer un paiement
Le marchand appelle show() pour lancer l'application de paiement afin que le client puisse effectuer un paiement. L'application de paiement est appelée à l'aide d'un intent Android PAY avec des informations sur la transaction dans les paramètres de l'intent.
L'application de paiement répond avec methodName et details, qui sont spécifiques à l'application de paiement et opaques pour le navigateur. Le navigateur convertit la chaîne details en dictionnaire JavaScript pour le marchand à l'aide de la désérialisation de la chaîne JSON, mais n'applique aucune autre règle de validité. Le navigateur ne modifie pas details. La valeur de ce paramètre est transmise directement au marchand.
AndroidManifest.xml
L'activité avec le filtre d'intent PAY doit avoir une balise <meta-data> qui identifie l'identifiant du mode de paiement par défaut pour l'application.
Pour accepter plusieurs modes de paiement, ajoutez un tag <meta-data> avec une ressource <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/chromium_payment_method_names" />
</activity>
android:resource doit être une liste de chaînes, chacune devant être une URL absolue valide avec un schéma HTTPS, comme indiqué ici.
<?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>
Paramètres
Les paramètres suivants sont transmis à l'activité en tant qu'extras Intent :
methodNamesmethodDatamerchantNametopLevelOrigintopLevelCertificateChainpaymentRequestOrigintotalmodifierspaymentRequestIdpaymentOptionsshippingOptions
Kotlin
val extras: Bundle? = getIntent()?.extras
Java
Bundle extras = getIntent() != null ? getIntent().getExtras() : null;
methodNames
Noms des méthodes utilisées. Les éléments sont les clés du dictionnaire methodData. Il s'agit des méthodes acceptées par l'application de paiement.
Kotlin
val methodNames: List<String>? = extras.getStringArrayList("methodNames")
Java
List<String> methodNames = extras.getStringArrayList("methodNames");
methodData
Mappage de chaque methodNames au methodData.
Kotlin
val methodData: Bundle? = extras.getBundle("methodData")
Java
Bundle methodData = extras.getBundle("methodData");
merchantName
Contenu de la balise HTML <title> de la page de paiement du marchand (contexte de navigation de premier niveau du navigateur).
Kotlin
val merchantName: String? = extras.getString("merchantName")
Java
String merchantName = extras.getString("merchantName");
topLevelOrigin
Origine du marchand sans le schéma (origine sans schéma du contexte de navigation de premier niveau). Par exemple, https://mystore.com/checkout est transmis sous la forme mystore.com.
Kotlin
val topLevelOrigin: String? = extras.getString("topLevelOrigin")
Java
String topLevelOrigin = extras.getString("topLevelOrigin");
topLevelCertificateChain
Chaîne de certificats du marchand (chaîne de certificats du contexte de navigation de premier niveau). La valeur est null pour WebView, localhost ou un fichier sur le disque.
Chaque Parcelable est un Bundle avec une clé certificate et une valeur de tableau d'octets.
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
Origine sans schéma du contexte de navigation de l'iFrame qui a appelé le constructeur new
PaymentRequest(methodData, details, options) en JavaScript. Si le constructeur a été appelé à partir du contexte de premier niveau, la valeur de ce paramètre est égale à la valeur du paramètre topLevelOrigin.
Kotlin
val paymentRequestOrigin: String? = extras.getString("paymentRequestOrigin")
Java
String paymentRequestOrigin = extras.getString("paymentRequestOrigin");
total
Chaîne JSON représentant le montant total de la transaction.
Kotlin
val total: String? = extras.getString("total")
Java
String total = extras.getString("total");
Voici un exemple de contenu de la chaîne :
{"currency":"USD","value":"25.00"}
modifiers
Sortie de JSON.stringify(details.modifiers), où details.modifiers ne contient que supportedMethods, data et total.
paymentRequestId
Champ PaymentRequest.id que les applications de paiement push doivent associer à l'état de la transaction. Les sites Web des marchands utiliseront ce champ pour interroger les applications de paiement push sur l'état de la transaction hors bande.
Kotlin
val paymentRequestId: String? = extras.getString("paymentRequestId")
Java
String paymentRequestId = extras.getString("paymentRequestId");
Réponse
L'activité peut renvoyer sa réponse via setResult avec RESULT_OK.
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();
Vous devez spécifier deux paramètres en tant qu'extras d'intent :
methodName: nom de la méthode utilisée.details: chaîne JSON contenant les informations nécessaires au marchand pour finaliser la transaction. Si la valeur de réussite esttrue,detailsdoit être construit de manière à ce queJSON.parse(details)réussisse. S'il n'y a aucune donnée à renvoyer, cette chaîne peut être"{}", que le site Web du marchand recevra sous la forme d'un dictionnaire JavaScript vide.
Vous pouvez transmettre RESULT_CANCELED si l'utilisateur annule la transaction dans l'application de paiement. Cela entraînera le rejet de request.show() avec un AbortError sur le site Web du marchand, indiquant l'annulation par l'utilisateur.
Kotlin
setResult(Activity.RESULT_CANCELED)
finish()
Java
setResult(Activity.RESULT_CANCELED);
finish();
À partir de Chrome 149, les valeurs de résultat suivantes sont acceptées :
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)
Si l'application de paiement échoue en raison d'une erreur interne, vous pouvez l'indiquer en transmettant Activity.RESULT_FIRST_USER comme code de résultat.
Si INTERNAL_PAYMENT_APP_ERROR est renvoyé, request.show() sera refusé avec un OperationError sur le site Web du marchand, indiquant une erreur dans l'application de paiement.
Cette distinction entre RESULT_CANCELED (0) pour l'annulation par l'utilisateur, qui entraîne AbortError, et INTERNAL_PAYMENT_APP_ERROR (1) pour une erreur interne de l'application, qui entraîne OperationError, permet aux marchands de créer de meilleurs parcours utilisateur.
Kotlin
setResult(Activity.RESULT_FIRST_USER)
finish()
Java
setResult(Activity.RESULT_FIRST_USER);
finish();
Si le résultat d'activité d'une réponse de paiement reçue de l'application de paiement appelée est défini sur RESULT_OK, Chrome recherchera les valeurs methodName et details non vides dans ses extras. Si la validation échoue, Chrome renvoie une promesse refusée à partir de request.show() avec l'un des messages d'erreur suivants destinés aux développeurs :
'Payment app returned invalid response. Missing field "details".'
'Payment app returned invalid response. Missing field "methodName".'
Autorisation
L'activité peut vérifier l'appelant avec sa méthode getCallingPackage().
Kotlin
val caller: String? = callingPackage
Java
String caller = getCallingPackage();
La dernière étape consiste à valider le certificat de signature de l'appelant pour confirmer que le package appelant possède la bonne signature.
Étape 4 : Validez le certificat de signature de l'appelant
Vous pouvez vérifier le nom du package de l'appelant avec Binder.getCallingUid() dans IS_READY_TO_PAY et avec Activity.getCallingPackage() dans PAY. Pour vérifier que l'appelant est bien le navigateur que vous avez en tête, vous devez vérifier son certificat de signature et vous assurer qu'il correspond à la bonne valeur.
Si vous ciblez le niveau d'API 28 ou supérieur et que vous effectuez l'intégration à un navigateur qui possède un seul certificat de signature, vous pouvez utiliser PackageManager.hasSigningCertificate().
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() est préférable pour les navigateurs à certificat unique, car il gère correctement la rotation des certificats. (Chrome dispose d'un seul certificat de signature.) Les applications qui possèdent plusieurs certificats de signature ne peuvent pas les alterner.
Si vous devez prendre en charge les niveaux d'API 27 et inférieurs, ou si vous devez gérer les navigateurs avec plusieurs certificats de signature, vous pouvez utiliser PackageManager.GET_SIGNATURES.
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);
Déboguer
Utilisez la commande suivante pour observer les erreurs ou les messages d'information :
adb logcat | grep -i pay