Scopri come adattare la tua app di pagamento Android per funzionare con i pagamenti web e offrire una migliore esperienza utente ai clienti.
Pubblicato il 5 maggio 2020, ultimo aggiornamento il 27 maggio 2025
L'API Payment Request porta sul web un'interfaccia integrata basata su browser che consente agli utenti di inserire le informazioni di pagamento richieste in modo più semplice che mai. L'API può anche richiamare app di pagamento specifiche della piattaforma.
Rispetto all'utilizzo dei soli intent Android, i pagamenti web consentono una migliore integrazione con il browser, la sicurezza e l'esperienza utente:
- L'app di pagamento viene avviata come modale, nel contesto del sito web del commerciante.
- L'implementazione è complementare all'app di pagamento esistente, il che ti consente di sfruttare la tua base utenti.
- La firma dell'app di pagamento viene controllata per impedire il sideloading.
- Le app di pagamento possono supportare più metodi di pagamento.
- È possibile integrare qualsiasi metodo di pagamento, come criptovalute, bonifici bancari e altro ancora. Le app di pagamento sui dispositivi Android possono persino integrare metodi che richiedono l'accesso al chip hardware del dispositivo.
L'implementazione dei pagamenti web in un'app di pagamento Android richiede quattro passaggi:
- Consenti ai commercianti di scoprire la tua app di pagamento.
- Comunica a un commerciante se un cliente ha uno strumento registrato (ad es. una carta di credito) pronto per il pagamento.
- Consenti a un cliente di effettuare un pagamento.
- Verifica il certificato di firma del chiamante.
Per vedere i pagamenti web in azione, consulta la demo android-web-payment.
Passaggio 1: consenti ai commercianti di scoprire la tua app di pagamento
Imposta la proprietà related_applications nel manifest dell'app web seguendo le
istruzioni riportate in Configurare un metodo di pagamento.
Affinché un commerciante possa utilizzare la tua app di pagamento, deve utilizzare l'API Payment Request e specificare il metodo di pagamento che supporti utilizzando l'identificatore del metodo di pagamento.
Se hai un identificatore del metodo di pagamento univoco per la tua app di pagamento, puoi configurare il tuo manifest del metodo di pagamento in modo che i browser possano scoprire la tua app.
Passaggio 2: comunica a un commerciante se un cliente ha uno strumento registrato pronto per il pagamento
Il commerciante può chiamare hasEnrolledInstrument() per verificare se il cliente
è in grado di effettuare un pagamento. Puoi implementare IS_READY_TO_PAY come servizio Android per rispondere a questa query.
AndroidManifest.xml
Dichiara il tuo servizio con un filtro per intent con l'azione 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>
Il servizio IS_READY_TO_PAY è facoltativo. Se non è presente un gestore di intent di questo tipo nell'app di pagamento, il browser web presuppone che l'app possa sempre effettuare pagamenti.
AIDL
L'API per il servizio IS_READY_TO_PAY è definita in AIDL. Crea due file AIDL con i seguenti contenuti:
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);
}
Implementare IsReadyToPayService
L'implementazione più semplice di IsReadyToPayService è mostrata nell'esempio seguente:
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;
}
}
Risposta
Il servizio può inviare la sua risposta utilizzando il metodo handleIsReadyToPay(Boolean).
Kotlin
callback?.handleIsReadyToPay(true)
Java
if (callback != null) {
callback.handleIsReadyToPay(true);
}
Autorizzazione
Puoi utilizzare Binder.getCallingUid() per verificare chi è il chiamante. Tieni presente che devi farlo nel metodo isReadyToPay, non nel metodo onBind, perché il sistema operativo Android può memorizzare nella cache e riutilizzare la connessione al servizio, che non attiva il metodo 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());
// ...
Controlla sempre i parametri di input per null quando ricevi chiamate di comunicazione interprocesso (IPC). Questo è particolarmente importante perché versioni o fork diversi del sistema operativo Android possono comportarsi in modi imprevisti e causare errori se non vengono gestiti.
Anche se packageManager.getPackagesForUid() in genere restituisce un singolo elemento, il codice deve gestire lo scenario non comune in cui un chiamante utilizza più nomi di pacchetti. In questo modo, l'applicazione rimane robusta.
Consulta Verificare il certificato di firma del chiamante per scoprire come verificare che il pacchetto chiamante abbia la firma corretta.
Parametri
Il bundle parameters è stato aggiunto in Chrome 139. Deve sempre essere controllato rispetto a null.
I seguenti parametri vengono passati al servizio nel bundle parameters:
packageNamemethodNamesmethodDatatopLevelOriginpaymentRequestOrigintopLevelCertificateChain
packageName è stato aggiunto in Chrome 138. Devi verificare questo parametro rispetto a Binder.getCallingUid() prima di utilizzarne il valore. Questa verifica è essenziale perché il bundle parameters è sotto il controllo completo del chiamante, mentre Binder.getCallingUid() è controllato dal sistema operativo Android.
topLevelCertificateChain è null in WebView e sui siti web non HTTPS che vengono in genere utilizzati per i test locali, ad esempio http://localhost.
Passaggio 3: consenti a un cliente di effettuare un pagamento
Il commerciante chiama show() per avviare l'app di pagamento
in modo che il cliente possa effettuare un pagamento. L'app di pagamento viene richiamata utilizzando un intent Android PAY con le informazioni sulla transazione nei parametri dell'intent.
L'app di pagamento risponde con methodName e details, che sono specifici dell'app di pagamento e opachi per il browser. Il browser converte la stringa details in un dizionario JavaScript per il commerciante utilizzando la deserializzazione della stringa JSON, ma non applica alcuna validità oltre a questa. Il browser non modifica i details; il valore di questo parametro viene passato direttamente al commerciante.
AndroidManifest.xml
L'attività con il filtro per intent PAY deve avere un tag <meta-data> che identifica l'identificatore del metodo di pagamento predefinito per l'app.
Per supportare più metodi di pagamento, aggiungi un <meta-data> tag con una
<string-array> risorsa.
<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 deve essere un elenco di stringhe, ognuna delle quali deve essere un URL assoluto valido con uno schema HTTPS, come mostrato qui.
<?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>
Parametri
I seguenti parametri vengono passati all'attività come extra Intent:
methodNamesmethodDatamerchantNametopLevelOrigintopLevelCertificateChainpaymentRequestOrigintotalmodifierspaymentRequestIdpaymentOptionsshippingOptions
Kotlin
val extras: Bundle? = getIntent()?.extras
Java
Bundle extras = getIntent() != null ? getIntent().getExtras() : null;
methodNames
I nomi dei metodi utilizzati. Gli elementi sono le chiavi nel dizionario methodData. Questi sono i metodi supportati dall'app di pagamento.
Kotlin
val methodNames: List<String>? = extras.getStringArrayList("methodNames")
Java
List<String> methodNames = extras.getStringArrayList("methodNames");
methodData
Un mapping da ciascuno dei methodNames a
methodData.
Kotlin
val methodData: Bundle? = extras.getBundle("methodData")
Java
Bundle methodData = extras.getBundle("methodData");
merchantName
I contenuti del tag HTML <title> della pagina di pagamento del commerciante (il
contesto di navigazione di primo livello del browser).
Kotlin
val merchantName: String? = extras.getString("merchantName")
Java
String merchantName = extras.getString("merchantName");
topLevelOrigin
L'origine del commerciante senza lo schema (l'origine senza schema del contesto di navigazione di primo livello). Ad esempio, https://mystore.com/checkout viene
passato come mystore.com.
Kotlin
val topLevelOrigin: String? = extras.getString("topLevelOrigin")
Java
String topLevelOrigin = extras.getString("topLevelOrigin");
topLevelCertificateChain
La catena di certificati del commerciante (la catena di certificati del contesto di navigazione di primo livello). Il valore è null per WebView, localhost o un file su disco.
Ogni Parcelable è un bundle con una chiave certificate e un valore di array di byte.
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
L'origine senza schema del contesto di navigazione iframe che ha richiamato il costruttore new
PaymentRequest(methodData, details, options) in JavaScript. Se il costruttore è stato richiamato dal contesto di primo livello, il valore di questo parametro è uguale al valore del parametro topLevelOrigin.
Kotlin
val paymentRequestOrigin: String? = extras.getString("paymentRequestOrigin")
Java
String paymentRequestOrigin = extras.getString("paymentRequestOrigin");
total
La stringa JSON che rappresenta l'importo totale della transazione.
Kotlin
val total: String? = extras.getString("total")
Java
String total = extras.getString("total");
Ecco un esempio di contenuti della stringa:
{"currency":"USD","value":"25.00"}
modifiers
L'output di JSON.stringify(details.modifiers), dove details.modifiers
contiene solo supportedMethods, data e total.
paymentRequestId
Il campo PaymentRequest.id che le app di "pagamento push" devono associare allo stato della transazione. I siti web dei commercianti utilizzeranno questo campo per eseguire query sulle app di "pagamento push" per lo stato della transazione fuori banda.
Kotlin
val paymentRequestId: String? = extras.getString("paymentRequestId")
Java
String paymentRequestId = extras.getString("paymentRequestId");
Risposta
L'attività può inviare la sua risposta tramite setResult con 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();
Devi specificare due parametri come extra dell'intent:
methodName: il nome del metodo utilizzato.details: stringa JSON contenente le informazioni necessarie al commerciante per completare la transazione. Se success ètrue,detailsdeve essere costruito in modo cheJSON.parse(details)vada a buon fine. Se non sono presenti dati da restituire, questa stringa può essere"{}", che il sito web del commerciante riceverà come un dizionario JavaScript vuoto.
Puoi passare RESULT_CANCELED se l'utente annulla la transazione nell'app di pagamento. In questo modo, request.show() verrà rifiutato con un AbortError sul sito web del commerciante, indicando l'annullamento da parte dell'utente.
Kotlin
setResult(Activity.RESULT_CANCELED)
finish()
Java
setResult(Activity.RESULT_CANCELED);
finish();
A partire da Chrome 149, sono supportati i seguenti valori dei risultati:
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)
Se l'app di pagamento non funziona a causa di un errore interno, puoi indicarlo passando Activity.RESULT_FIRST_USER come codice risultato.
Se viene restituito INTERNAL_PAYMENT_APP_ERROR, request.show() verrà rifiutato con un OperationError sul sito web del commerciante, indicando un errore nell'app di pagamento.
Questa distinzione tra RESULT_CANCELED (0) per l'annullamento da parte dell'utente, che causa AbortError, e INTERNAL_PAYMENT_APP_ERROR (1) per un errore interno dell'app, che causa OperationError, consente ai commercianti di creare flussi utente migliori.
Kotlin
setResult(Activity.RESULT_FIRST_USER)
finish()
Java
setResult(Activity.RESULT_FIRST_USER);
finish();
Se il risultato dell'attività di una risposta di pagamento ricevuta dall'app di pagamento richiamata è impostato su RESULT_OK, Chrome verificherà la presenza di methodName e details non vuoti nei relativi extra. Se la convalida non va a buon fine, Chrome restituirà una promessa rifiutata da request.show() con uno dei seguenti messaggi di errore rivolti agli sviluppatori:
'Payment app returned invalid response. Missing field "details".'
'Payment app returned invalid response. Missing field "methodName".'
Autorizzazione
L'attività può controllare il chiamante con il metodo getCallingPackage().
Kotlin
val caller: String? = callingPackage
Java
String caller = getCallingPackage();
Il passaggio finale consiste nel verificare il certificato di firma del chiamante per confermare che il pacchetto chiamante abbia la firma corretta.
Passaggio 4: verifica il certificato di firma del chiamante
Puoi controllare il nome del pacchetto del chiamante con Binder.getCallingUid() in
IS_READY_TO_PAY e con Activity.getCallingPackage() in PAY. Per verificare effettivamente che il chiamante sia il browser che hai in mente, devi controllare il suo certificato di firma e assicurarti che corrisponda al valore corretto.
Se utilizzi il livello API 28 e versioni successive e ti stai integrando con un browser con un singolo certificato di firma, puoi utilizzare 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() è preferibile per i browser con un singolo certificato, perché gestisce correttamente la rotazione dei certificati. (Chrome ha un singolo certificato di firma.) Le app con più certificati di firma non possono ruotarli.
Se devi supportare i livelli API 27 e precedenti o se devi gestire browser con più certificati di firma, puoi utilizzare 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);
Debug
Utilizza il seguente comando per osservare gli errori o i messaggi informativi:
adb logcat | grep -i pay