Saiba como adaptar seu app de pagamento para Android para que ele funcione com o Web Payments e ofereça uma melhor experiência aos clientes.
A API Payment Request traz para a Web uma interface integrada baseada em navegador. Com ela, os usuários inserem as informações de pagamento necessárias com mais facilidade do que nunca. A API também pode invocar apps de pagamento específicos da plataforma.
Em comparação com o uso apenas de intents do Android, o Web Payments permite uma melhor integração com o navegador, a segurança e a experiência do usuário:
- O app de pagamento é iniciado como um modal, no contexto do site do comerciante.
- A implementação é complementar ao seu app de pagamento atual, permitindo que você aproveite sua base de usuários.
- A assinatura do app de pagamento é verificada para evitar por sideload.
- Os apps de pagamento aceitam várias formas de pagamento.
- Qualquer forma de pagamento pode ser integrada, como criptomoedas, transferências bancárias e outras. Os apps de pagamento em dispositivos Android podem até mesmo integrar métodos que exigem acesso ao chip de hardware no dispositivo.
São necessárias quatro etapas para implementar o Web Payments em um app de pagamento do Android:
- Permita que os comerciantes descubram seu app de pagamento.
- Informe um comerciante se um cliente tem um instrumento registrado (como cartão de crédito) pronto para pagamento.
- Permita que um cliente faça um pagamento.
- Verifique o certificado de assinatura do autor da chamada.
Para ver os Pagamentos na Web em ação, confira a demonstração android-web-payment.
Etapa 1: permitir que os comerciantes descubram seu app de pagamento
Para que um comerciante use seu app de pagamento, ele precisa utilizar a API Payment Request e especificar a forma de pagamento aceita usando o identificador da forma de pagamento.
Se você tiver um identificador de forma de pagamento exclusivo para seu app de pagamento, poderá configurar seu próprio manifesto da forma de pagamento para que os navegadores possam descobrir seu aplicativo.
Etapa 2: informar um comerciante se um cliente tiver um instrumento registrado pronto para pagamento
O comerciante pode chamar hasEnrolledInstrument()
para consultar se o cliente pode fazer um pagamento. Implemente
IS_READY_TO_PAY
como um serviço Android para responder a essa consulta.
AndroidManifest.xml
Declare seu serviço com um filtro de intent com a ação
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>
O serviço IS_READY_TO_PAY
é opcional. Se não houver um gerenciador desse tipo no
app de pagamento, o navegador da Web entenderá que o app sempre pode fazer
pagamentos.
AIDL
A API para o serviço IS_READY_TO_PAY
é definida na AIDL. Crie dois arquivos AIDL
com o seguinte conteúdo:
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);
}
Implementação de IsReadyToPayService
A implementação mais simples de IsReadyToPayService
é mostrada no exemplo
a seguir:
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
}
}
Resposta
O serviço pode enviar a resposta pelo método handleIsReadyToPay(Boolean)
.
callback?.handleIsReadyToPay(true)
Permissão
Você pode usar Binder.getCallingUid()
para verificar quem é o autor da chamada. Você precisa fazer isso no método isReadyToPay
, não no método onBind
.
override fun isReadyToPay(callback: IsReadyToPayServiceCallback?) {
try {
val callingPackage = packageManager.getNameForUid(Binder.getCallingUid())
// …
Consulte Verificar o certificado de assinatura do autor da chamada sobre como conferir se o pacote de chamada tem a assinatura correta.
Etapa 3: permitir que um cliente faça um pagamento
O comerciante chama show()
para iniciar o app de pagamento. Assim, o cliente pode efetuar um pagamento. O app de pagamento é invocado por uma PAY
da intent do Android com informações da transação nos parâmetros da intent.
O app de pagamento responde com methodName
e details
, que são específicos
do app de pagamento e opacos para o navegador. O navegador converte a string details
em um objeto JavaScript para o comerciante por meio da desserialização JSON, mas não aplica nenhuma validade além disso. O navegador não modifica o details
. O valor desse parâmetro é transmitido diretamente ao comerciante.
AndroidManifest.xml
A atividade com o filtro de intent PAY
precisa ter uma tag <meta-data>
que
identifique o identificador da forma de pagamento padrão do
app.
Para aceitar várias formas de pagamento, adicione uma tag <meta-data>
com um
recurso <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>
O resource
precisa ser uma lista de strings, e cada uma delas precisa ser um URL absoluto
válido com um esquema HTTPS, conforme mostrado aqui.
<?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>
Parâmetros
Os seguintes parâmetros são passados para a atividade como extras de intent:
methodNames
methodData
topLevelOrigin
topLevelCertificateChain
paymentRequestOrigin
total
modifiers
paymentRequestId
val extras: Bundle? = intent?.extras
methodNames
Os nomes dos métodos que estão sendo usados. Os elementos são as chaves no dicionário methodData
. Estas são as formas aceitas pelo app de pagamento.
val methodNames: List<String>? = extras.getStringArrayList("methodNames")
methodData
Um mapeamento de cada methodNames
para o
methodData
.
val methodData: Bundle? = extras.getBundle("methodData")
merchantName
O conteúdo da tag HTML <title>
da página de finalização da compra do comerciante (o contexto de navegação de nível superior do navegador).
val merchantName: String? = extras.getString("merchantName")
topLevelOrigin
A origem do comerciante sem o esquema (a origem sem esquema do contexto de navegação de nível superior). Por exemplo, https://mystore.com/checkout
é
transmitido como mystore.com
.
val topLevelOrigin: String? = extras.getString("topLevelOrigin")
topLevelCertificateChain
A cadeia de certificados do comerciante, ou seja, a cadeia de certificados do contexto de navegação
de nível superior. Nulo para localhost e arquivo no disco, que são contextos
seguros sem certificados SSL. Cada Parcelable
é um pacote com uma
chave certificate
e um valor de matriz de bytes.
val topLevelCertificateChain: Array<Parcelable>? =
extras.getParcelableArray("topLevelCertificateChain")
val list: List<ByteArray>? = topLevelCertificateChain?.mapNotNull { p ->
(p as Bundle).getByteArray("certificate")
}
paymentRequestOrigin
A origem sem esquema do contexto de navegação do iframe que invocou o construtor new
PaymentRequest(methodData, details, options)
no JavaScript. Se o
construtor tiver sido invocado do contexto de nível superior, o valor desse
parâmetro será igual ao do parâmetro topLevelOrigin
.
val paymentRequestOrigin: String? = extras.getString("paymentRequestOrigin")
total
A string JSON que representa o valor total da transação.
val total: String? = extras.getString("total")
Veja um exemplo de conteúdo da string:
{"currency":"USD","value":"25.00"}
modifiers
A saída de JSON.stringify(details.modifiers)
, em que details.modifiers
contém apenas supportedMethods
e total
.
paymentRequestId
O campo PaymentRequest.id
que os apps de "pagamento por push" precisam associar ao
estado da transação. Os sites dos comerciantes vão usar esse campo para consultar os apps de "pagamento por push" para o estado da transação fora de banda.
val paymentRequestId: String? = extras.getString("paymentRequestId")
Resposta
A atividade pode enviar a resposta de volta por setResult
com RESULT_OK
.
setResult(Activity.RESULT_OK, Intent().apply {
putExtra("methodName", "https://bobbucks.dev/pay")
putExtra("details", "{\"token\": \"put-some-data-here\"}")
})
finish()
É preciso especificar dois parâmetros como extras de intent:
methodName
: o nome do método que está sendo usado.details
: string JSON que contém as informações necessárias para que o comerciante conclua a transação. Se o sucesso fortrue
,details
precisará ser construído de modo queJSON.parse(details)
tenha êxito.
Você poderá transmitir RESULT_CANCELED
se a transação não for concluída no
app de pagamento. Por exemplo, se o usuário não tiver digitado o código PIN correto para
a conta no app. O navegador pode permitir que o usuário escolha um
app de pagamento diferente.
setResult(RESULT_CANCELED)
finish()
Se o resultado da atividade de uma resposta de pagamento recebida do app de pagamento
invocado estiver definido como RESULT_OK
, o Chrome vai procurar methodName
e
details
não vazios nos extras. Se a validação falhar, o Chrome retornará uma promessa
rejeitada de request.show()
com uma das seguintes mensagens de erro
para o desenvolvedor:
'Payment app returned invalid response. Missing field "details".'
'Payment app returned invalid response. Missing field "methodName".'
Permissão
A atividade pode verificar o autor da chamada com o método getCallingPackage()
.
val caller: String? = callingPackage
A etapa final é verificar o certificado de assinatura do autor da chamada para confirmar se o pacote que fez a chamada tem a assinatura correta.
Etapa 4: verificar o certificado de assinatura do autor da chamada
É possível verificar o nome do pacote do autor da chamada com Binder.getCallingUid()
em
IS_READY_TO_PAY
e com Activity.getCallingPackage()
em PAY
. Para
verificar realmente se o autor da chamada é o navegador que você tem em mente, verifique o certificado de assinatura dele e confira se ele corresponde ao valor
correto.
Se você estiver segmentando o nível 28 da API ou mais recente e estiver fazendo a integração com um navegador
que tem um único certificado de assinatura, use
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
)
O PackageManager.hasSigningCertificate()
é preferível para navegadores de certificado único, porque ele processa corretamente a rotação de certificados. O Chrome tem um
único certificado de assinatura. Apps que têm vários certificados de assinatura não podem
alterá-los.
Se você precisa oferecer suporte a APIs de nível 27 ou anteriores ou precisa lidar
com navegadores com vários certificados de assinatura, use
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) } }