Guide du développeur d'applications de paiement Android

Découvrez comment adapter votre application de paiement Android pour qu'elle fonctionne avec les paiements Web et offre une meilleure expérience utilisateur.

L'API Payment Request 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 à une plate-forme.

Navigateurs pris en charge

  • 60
  • 15
  • 11.1

Source

Processus de paiement avec l'application Google Pay spécifique à la plate-forme qui utilise les paiements Web.

Par rapport aux 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 la forme d'une modale, dans le contexte du site Web du marchand.
  • L'implémentation vient compléter votre application de paiement existante et vous permet de profiter de votre base d'utilisateurs.
  • La signature de l'application de paiement est vérifiée pour éviter le chargement indépendant.
  • Les applications de paiement acceptent plusieurs modes de paiement.
  • Tous les modes de paiement (cryptomonnaie, virements bancaires, etc.) peuvent être intégrés. Les applications de paiement sur les appareils Android peuvent même intégrer des méthodes nécessitant un accès à la puce matérielle de l'appareil.

Pour implémenter les paiements Web dans une application de paiement Android, vous devez suivre quatre étapes:

  1. Permettez aux marchands de découvrir votre application de paiement.
  2. Indiquez à un marchand si un client dispose d'un mode de paiement enregistré (comme une carte de crédit) prêt à payer.
  3. Permettez à un client d'effectuer un paiement.
  4. Vérifiez le certificat de signature de l'appelant.

Pour voir les paiements Web en action, consultez la démonstration android-web-payment.

Étape 1: Permettez aux marchands de découvrir votre application 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 accepté à l'aide de l'identifiant du mode de paiement.

Si vous disposez d'un identifiant de mode de paiement propre à votre application de paiement, vous pouvez configurer votre propre fichier manifeste de mode de paiement afin que les navigateurs puissent détecter votre application.

Étape 2: Indiquez à un marchand si un client dispose d'un mode de paiement enregistré et prêt à être utilisé

Le marchand peut appeler hasEnrolledInstrument() pour demander 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 avec 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'existe aucun gestionnaire d'intent de ce type 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:

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);
}

Mettre en œuvre la règle IsReadyToPayService

L'implémentation la plus simple de IsReadyToPayService est illustrée dans l'exemple suivant:

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
  }
}

Réponse

Le service peut envoyer sa réponse via la méthode handleIsReadyToPay(Boolean).

callback?.handleIsReadyToPay(true)

Autorisation

Vous pouvez utiliser Binder.getCallingUid() pour vérifier qui est l'appelant. Notez que vous devez effectuer cette opération dans la méthode isReadyToPay, et non dans la méthode onBind.

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

Consultez Vérifier le certificat de signature de l'appelant pour savoir comment vérifier que le package à l'origine de l'appel dispose de la signature appropriée.

Étape 3: Laissez un client 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 via un intent Android PAY avec des informations de transaction dans les paramètres d'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 objet JavaScript pour le marchand via la désérialisation JSON, mais n'applique aucune autre validité. Le navigateur ne modifie pas le details. La valeur de ce paramètre est transmise directement au marchand.

AndroidManifest.xml

L'activité avec le filtre d'intent PAY doit comporter 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 une balise <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/method_names" />
</activity>

resource doit être une liste de chaînes, chacune d'elles 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="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 d'intent:

  • methodNames
  • methodData
  • topLevelOrigin
  • topLevelCertificateChain
  • paymentRequestOrigin
  • total
  • modifiers
  • paymentRequestId
val extras: Bundle? = intent?.extras

methodNames

Noms des méthodes utilisées. Les éléments sont les clés du dictionnaire methodData. Voici les modes acceptés par l'application de paiement.

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

methodData

Un mappage de chacun des methodNames vers methodData.

val methodData: Bundle? = 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)

val merchantName: String? = 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 en tant que mystore.com.

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

topLevelCertificateChain

Chaîne de certificats du marchand (chaîne de certificats du contexte de navigation de premier niveau). Null pour "localhost" et "fichier sur le disque", qui sont tous deux des contextes sécurisés sans certificats SSL. Chaque Parcelable est un bundle avec une clé certificate et une valeur de tableau d'octets.

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

paymentRequestOrigin

Origine sans schéma du contexte de navigation iFrame qui a appelé le constructeur new PaymentRequest(methodData, details, options) dans JavaScript. Si le constructeur a été appelé à partir du contexte de premier niveau, la valeur de ce paramètre est égale à celle du paramètre topLevelOrigin.

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

total

Chaîne JSON représentant le montant total de la transaction.

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

Voici un exemple de contenu de la chaîne:

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

modifiers

La sortie de JSON.stringify(details.modifiers), où details.modifiers ne contient que supportedMethods et total.

paymentRequestId

Le champ PaymentRequest.id que les applications "push-payment" doivent associer à l'état de la transaction Les sites Web marchands utiliseront ce champ pour interroger les applications de paiement push afin de connaître l'état de la transaction hors bande.

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

Réponse

L'activité peut renvoyer sa réponse via setResult avec RESULT_OK.

setResult(Activity.RESULT_OK, Intent().apply {
  putExtra("methodName", "https://bobbucks.dev/pay")
  putExtra("details", "{\"token\": \"put-some-data-here\"}")
})
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 terminer la transaction. Si la réussite est true, alors details doit être construit de manière à ce que JSON.parse(details) réussisse.

Vous pouvez transmettre 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 bon code pour son compte dans l'application de paiement. Le navigateur peut autoriser l'utilisateur à choisir une autre application de paiement.

setResult(RESULT_CANCELED)
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 recherche les éléments methodName et details non vides dans ses éléments supplémentaires. Si la validation échoue, Chrome renvoie une promesse refusée 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().

val caller: String? = callingPackage

La dernière étape consiste à vérifier le certificat de signature de l'appelant afin de confirmer que la signature du package à l'origine de l'appel est correcte.

Étape 4: Vérifiez le certificat de signature de l'appelant

Vous pouvez vérifier le nom de package de l'appelant avec Binder.getCallingUid() dans IS_READY_TO_PAY et avec Activity.getCallingPackage() dans PAY. Pour vous assurer que l'appelant est 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 intégrez un navigateur comportant un seul certificat de signature, vous pouvez utiliser 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() est recommandé pour les navigateurs à certificat unique, car il gère correctement la rotation des certificats. (Chrome possède un seul certificat de signature.) Les applications comportant plusieurs certificats de signature ne peuvent pas les faire pivoter.

Si vous devez prendre en charge les anciens niveaux d'API 27 ou inférieurs, ou si vous devez gérer des navigateurs avec plusieurs certificats de signature, vous pouvez utiliser 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) } }