Scopri come adattare la tua app per i pagamenti Android in modo che funzioni con Web Payments e fornisca una migliore esperienza utente ai clienti.
L'API Payment Request consente di web un'interfaccia integrata basata su browser che consente agli utenti di inserire il pagamento richiesto informazioni più facilmente che mai. L'API può anche richiamare un metodo di pagamento app.
Rispetto all'utilizzo solo di Android Intent, i pagamenti web consentono una migliore integrazione con il browser, la sicurezza e l'esperienza utente:
- L'app di pagamento viene lanciata come modale, nel contesto del sito web del commerciante.
- L'implementazione è supplementare all'app di pagamento esistente e ti consente di: sfruttare la tua base utenti.
- La firma dell'app di pagamento viene controllata per evitare sideload.
- Le app di pagamento possono supportare più metodi di pagamento.
- È possibile utilizzare qualsiasi metodo di pagamento, ad esempio criptovalute, bonifici bancari e altro ancora integrate. Le app di pagamento sui dispositivi Android possono anche integrare metodi che richiedono l'accesso al chip hardware sul dispositivo.
Per implementare Web Payments in un'app per pagamenti Android, sono necessari quattro passaggi:
- Consenti ai commercianti di scoprire la tua app per i pagamenti.
- Comunicare a un commerciante se un cliente ha uno strumento registrato (come credito carta di credito) pronto per il pagamento.
- Consenti a un cliente di effettuare il pagamento.
- Verifica il certificato di firma del chiamante.
Per vedere Web Payments in azione, consulta la android-web-payment demo.
Passaggio 1: consenti ai commercianti di trovare la tua app di pagamento
Affinché un commerciante possa utilizzare la tua app di pagamento, deve utilizzare la API Request e specifica il metodo di pagamento che supporti utilizzando il metodo di pagamento identificativo.
Se disponi di un identificatore del metodo di pagamento univoco per la tua app di pagamento, puoi impostare il tuo metodo di pagamento manifest in modo che i browser possano a scoprire la tua app.
Passaggio 2: fai sapere a un commerciante se un cliente ha uno strumento registrato pronto per il pagamento
Il commerciante può chiamare hasEnrolledInstrument()
per chiedere al cliente
sia in grado di effettuare un pagamento. Puoi
implementare IS_READY_TO_PAY
come servizio Android per rispondere a questa query.
AndroidManifest.xml
Dichiara il 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 questo gestore di intent non è presente
l'app di pagamento, il browser web presume che l'app possa sempre
pagamenti.
AIDL
L'API per il servizio IS_READY_TO_PAY
è definita in AIDL. Crea due AIDL
file con i seguenti contenuti:
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);
}
Implementare IsReadyToPayService
Di seguito è mostrata l'implementazione più semplice di IsReadyToPayService
esempio:
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
}
}
Risposta
Il servizio può inviare la sua risposta con il metodo handleIsReadyToPay(Boolean)
.
callback?.handleIsReadyToPay(true)
Autorizzazione
Puoi utilizzare Binder.getCallingUid()
per verificare chi è il chiamante. Tieni presente che
deve farlo nel metodo isReadyToPay
, non nel metodo onBind
.
override fun isReadyToPay(callback: IsReadyToPayServiceCallback?) {
try {
val callingPackage = packageManager.getNameForUid(Binder.getCallingUid())
// …
Leggi l'articolo Verificare il certificato di firma del chiamante per sapere come procedere. per verificare che il pacchetto chiamante abbia la firma corretta.
Passaggio 3: consenti a un cliente di effettuare il pagamento
Il commerciante chiama show()
per lanciare il pagamento
app
per consentire al cliente di effettuare un pagamento. L'app di pagamento viene richiamata tramite un dispositivo Android
l'intent PAY
con informazioni sulla transazione nei parametri per intent.
L'app di pagamento risponde con methodName
e details
, che sono app di pagamento
specifici e sono opachi per il browser. Il browser converte details
in un oggetto JavaScript per il commerciante tramite la deserializzazione JSON, ma
non impone alcuna validità oltre. Il browser non modifica
details
; il valore di tale parametro viene trasmesso 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 supportare più metodi di pagamento, aggiungi un tag <meta-data>
con un
<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/method_names" />
</activity>
resource
deve essere un elenco di stringhe, ognuna delle quali deve essere una stringa
con uno schema HTTPS, come mostrato qui.
<?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>
Parametri
I seguenti parametri vengono passati all'attività come Intent extra:
methodNames
methodData
topLevelOrigin
topLevelCertificateChain
paymentRequestOrigin
total
modifiers
paymentRequestId
val extras: Bundle? = intent?.extras
methodNames
I nomi dei metodi utilizzati. Gli elementi sono le chiavi
Dizionario methodData
. Questi sono i metodi supportati dall'app di pagamento.
val methodNames: List<String>? = extras.getStringArrayList("methodNames")
methodData
Una mappatura di ogni elemento methodNames
alla
methodData
val methodData: Bundle? = 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).
val merchantName: String? = 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
è
passato come mystore.com
.
val topLevelOrigin: String? = extras.getString("topLevelOrigin")
topLevelCertificateChain
La catena di certificati del commerciante (la catena di certificati dell'offerta di primo livello
contesto di navigazione). Null per localhost e file su disco, entrambi sicuri
contesti senza certificati SSL. Ogni Parcelable
è un bundle con un
Chiave certificate
e un valore di array di byte.
val topLevelCertificateChain: Array<Parcelable>? =
extras.getParcelableArray("topLevelCertificateChain")
val list: List<ByteArray>? = topLevelCertificateChain?.mapNotNull { p ->
(p as Bundle).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, allora il valore
equivale al valore del parametro topLevelOrigin
.
val paymentRequestOrigin: String? = extras.getString("paymentRequestOrigin")
total
La stringa JSON che rappresenta l'importo totale della transazione.
val total: String? = 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
contenere solo supportedMethods
e total
.
paymentRequestId
Il campo PaymentRequest.id
che "push-payment" devono essere associate
lo stato della transazione. I siti web dei commercianti utilizzeranno questo campo per eseguire query
"push-payment" le app per lo stato della transazione fuori banda.
val paymentRequestId: String? = extras.getString("paymentRequestId")
Risposta
L'attività può inviare la sua risposta tramite setResult
con RESULT_OK
.
setResult(Activity.RESULT_OK, Intent().apply {
putExtra("methodName", "https://bobbucks.dev/pay")
putExtra("details", "{\"token\": \"put-some-data-here\"}")
})
finish()
Devi specificare due parametri come extra per intent:
methodName
: il nome del metodo utilizzato.details
: stringa JSON contenente le informazioni necessarie al commerciante per completare la transazione. Se l'esito ètrue
,details
deve essere costruito in modo tale cheJSON.parse(details)
abbia successo.
Puoi passare RESULT_CANCELED
se la transazione non è stata completata nel
all'app di pagamento, ad esempio se l'utente non ha inserito il codice PIN corretto per
il proprio account nell'app per i pagamenti. Il browser può consentire all'utente di scegliere
un'altra app di pagamento.
setResult(RESULT_CANCELED)
finish()
Se l'attività è il risultato di una risposta al pagamento ricevuta dal pagamento richiamato
è impostata su RESULT_OK
, Chrome verificherà la presenza di methodName
non vuoti e
details
nei suoi extra. Se la convalida non va a buon fine, Chrome restituisce un messaggio
promessa da request.show()
con uno dei seguenti errori degli sviluppatori
messaggi:
'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 suo metodo getCallingPackage()
.
val caller: String? = callingPackage
Il passaggio finale consiste nel verificare il certificato di firma del chiamante per confermare che che 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 avete in mente, dovete
controllare il certificato di firma e assicurarsi che corrisponda al certificato corretto
valore.
Se hai scelto come target il livello API 28 o superiore e hai eseguito l'integrazione con un browser
che ha un singolo certificato di firma, puoi usare
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
)
È preferibile usare PackageManager.hasSigningCertificate()
per un singolo certificato
browser web, 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 livelli API precedenti 27 e precedenti o se devi gestire
browser con più certificati di firma, puoi usare
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) } }