Saiba como adaptar seu app de pagamento para Android para trabalhar com pagamentos na Web e oferecer uma experiência do usuário melhor aos clientes.
Publicado em 5 de maio de 2020, atualizado pela última vez em 27 de maio de 2025
A Payment Request API oferece à Web uma interface integrada baseada no navegador que permite aos usuários inserir 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, os pagamentos na Web permitem 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 app de pagamento atual, permitindo que você aproveite sua base de usuários.
- A assinatura do app de pagamento é verificada para evitar o sideload.
- Os apps de pagamento podem oferecer suporte a várias formas de pagamento.
- Qualquer forma de pagamento, como criptomoedas, transferências bancárias e muito mais, pode ser integrada. 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 pagamentos na Web em um app de pagamento para Android:
- Permitir que os comerciantes descubram seu app de pagamento.
- Informar a um comerciante se um cliente tem um instrumento registrado (como um cartão de crédito) que está pronto para pagar.
- Permitir que um cliente faça um pagamento.
- Verificar 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
Defina a propriedade related_applications no manifesto do app da Web de acordo com as
instruções em Configurar uma forma de pagamento.
Para que um comerciante use seu app de pagamento, ele precisa usar a API Payment Request e especificar a forma de pagamento que você aceita usando o identificador da forma de pagamento.
Se você tiver um identificador de forma de pagamento exclusivo do seu app de pagamento, poderá configurar seu próprio manifesto de forma de pagamento para que os navegadores possam descobrir seu app.
Etapa 2: informar a um comerciante se um cliente tem um instrumento registrado que está pronto para pagar
O comerciante pode chamar hasEnrolledInstrument() para consultar se o cliente
pode fazer um pagamento. Você pode implementar IS_READY_TO_PAY como um serviço do 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 de intents desse tipo no app de pagamento, o navegador da Web vai presumir que o app sempre pode fazer pagamentos.
AIDL
A API do serviço IS_READY_TO_PAY é definida no AIDL. Crie dois arquivos AIDL com o seguinte conteúdo:
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);
}
Implementação de IsReadyToPayService
A implementação mais simples de IsReadyToPayService é mostrada no exemplo a seguir:
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;
}
}
Resposta
O serviço pode enviar a resposta usando o método handleIsReadyToPay(Boolean).
Kotlin
callback?.handleIsReadyToPay(true)
Java
if (callback != null) {
callback.handleIsReadyToPay(true);
}
Permissão
Você pode usar Binder.getCallingUid() para verificar quem é o autor da chamada. Observe que você
precisa fazer isso no método isReadyToPay, não no método onBind,
porque o SO Android pode armazenar em cache e reutilizar a conexão de serviço, o que não
aciona o método 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());
// ...
Sempre verifique os parâmetros de entrada para null ao receber chamadas de comunicação entre processos (IPC, na sigla em inglês). Isso é particularmente importante porque diferentes versões ou forks do SO Android podem se comportar de maneiras inesperadas e levar a erros se não forem tratados.
Embora packageManager.getPackagesForUid() normalmente retorne um único elemento, seu código precisa processar o cenário incomum em que um autor da chamada usa vários nomes de pacotes. Isso garante que seu aplicativo permaneça robusto.
Consulte Verificar o certificado de assinatura do autor da chamada para saber como verificar se o pacote de chamada tem a assinatura correta.
Parâmetros
O pacote parameters foi adicionado no Chrome 139. Ele sempre precisa ser verificado em relação a null.
Os parâmetros a seguir são transmitidos ao serviço no pacote parameters:
packageNamemethodNamesmethodDatatopLevelOriginpaymentRequestOrigintopLevelCertificateChain
O packageName foi adicionado no Chrome 138. É necessário verificar esse parâmetro em relação a Binder.getCallingUid() antes de usar o valor dele. Essa verificação é essencial porque o pacote parameters está sob o controle total do autor da chamada, enquanto Binder.getCallingUid() é controlado pelo SO Android.
O topLevelCertificateChain é null no WebView e em sites não HTTPS que são normalmente usados para testes locais, como http://localhost.
Etapa 3: permitir que um cliente faça um pagamento
O comerciante chama show() para iniciar o app
de pagamento
para que o cliente possa fazer um pagamento. O app de pagamento é invocado usando uma intent do Android PAY com informações de 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 ao navegador. O navegador converte a string details em um dicionário JavaScript para o comerciante usando a desserialização de string JSON, mas não impõe nenhuma validade além disso. O navegador não modifica os 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 de forma de pagamento padrão do
app.
Para oferecer suporte a várias formas de pagamento, adicione uma <meta-data> tag com um
<string-array> recurso.
<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>
O android:resource precisa ser uma lista de strings, 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="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>
Parâmetros
Os parâmetros a seguir são transmitidos à atividade como extras Intent:
methodNamesmethodDatamerchantNametopLevelOrigintopLevelCertificateChainpaymentRequestOrigintotalmodifierspaymentRequestIdpaymentOptionsshippingOptions
Kotlin
val extras: Bundle? = getIntent()?.extras
Java
Bundle extras = getIntent() != null ? getIntent().getExtras() : null;
methodNames
Os nomes dos métodos que estão sendo usados. Os elementos são as chaves no dicionário methodData. Esses são os métodos que o app de pagamento aceita.
Kotlin
val methodNames: List<String>? = extras.getStringArrayList("methodNames")
Java
List<String> methodNames = extras.getStringArrayList("methodNames");
methodData
Um mapeamento de cada um dos methodNames para o
methodData.
Kotlin
val methodData: Bundle? = extras.getBundle("methodData")
Java
Bundle methodData = 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).
Kotlin
val merchantName: String? = extras.getString("merchantName")
Java
String merchantName = 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.
Kotlin
val topLevelOrigin: String? = extras.getString("topLevelOrigin")
Java
String topLevelOrigin = extras.getString("topLevelOrigin");
topLevelCertificateChain
A cadeia de certificados do comerciante (a cadeia de certificados do contexto de navegação de nível superior). O valor é null para WebView, localhost ou um arquivo no disco.
Cada Parcelable é um pacote com uma chave certificate e um valor de matriz de bytes.
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
A origem sem esquema do contexto de navegação do iframe que invocou o construtor new
PaymentRequest(methodData, details, options) em JavaScript. Se o construtor foi invocado no contexto de nível superior, o valor desse parâmetro será igual ao valor do parâmetro topLevelOrigin.
Kotlin
val paymentRequestOrigin: String? = extras.getString("paymentRequestOrigin")
Java
String paymentRequestOrigin = extras.getString("paymentRequestOrigin");
total
A string JSON que representa o valor total da transação.
Kotlin
val total: String? = extras.getString("total")
Java
String total = extras.getString("total");
Confira 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, data e total.
paymentRequestId
O campo PaymentRequest.id que os apps de "pagamento por push" precisam associar ao estado da transação. Os sites de comerciantes vão usar esse campo para consultar os apps de "pagamento por push" sobre o estado da transação fora da banda.
Kotlin
val paymentRequestId: String? = extras.getString("paymentRequestId")
Java
String paymentRequestId = extras.getString("paymentRequestId");
Resposta
A atividade pode enviar a resposta de volta por setResult com 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();
É necessário 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, entãodetailsprecisará ser construído de forma queJSON.parse(details)seja bem-sucedido. Se não houver dados que precisem ser retornados, essa string poderá ser"{}", que o site do comerciante vai receber como um dicionário JavaScript vazio.
Você pode transmitir RESULT_CANCELED se o usuário cancelar a transação no app de pagamento. Isso fará com que request.show() seja rejeitado com um AbortError no site do comerciante, indicando o cancelamento do usuário.
Kotlin
setResult(Activity.RESULT_CANCELED)
finish()
Java
setResult(Activity.RESULT_CANCELED);
finish();
A partir do Chrome 149, os seguintes valores de resultado são aceitos:
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 o app de pagamento falhar devido a um erro interno, você poderá indicar isso transmitindo Activity.RESULT_FIRST_USER como código de resultado.
Se INTERNAL_PAYMENT_APP_ERROR for retornado, request.show() será rejeitado com um OperationError no site do comerciante, indicando um erro no app de pagamento.
Essa distinção entre RESULT_CANCELED (0) para cancelamento do usuário, que causa AbortError, e INTERNAL_PAYMENT_APP_ERROR (1) para um erro interno do app, que causa OperationError, permite que os comerciantes criem fluxos de usuários melhores.
Kotlin
setResult(Activity.RESULT_FIRST_USER)
finish()
Java
setResult(Activity.RESULT_FIRST_USER);
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 verificar se há methodName e details não vazios nos extras. Se a validação falhar, o Chrome vai retornar uma promessa rejeitada de request.show() com uma das seguintes mensagens de erro voltadas 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().
Kotlin
val caller: String? = callingPackage
Java
String caller = getCallingPackage();
A etapa final é verificar o certificado de assinatura do autor da chamada para confirmar se o pacote de chamada tem a assinatura correta.
Etapa 4: verificar o certificado de assinatura do autor da chamada
Você pode 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 se o autor da chamada é o navegador que você tem em mente, verifique o certificado de assinatura e confira se ele corresponde ao valor correto.
Se você estiver segmentando o nível 28 da API e versões mais recentes e estiver se integrando a um navegador que tenha um único certificado de assinatura, poderá usar 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() é preferível para navegadores de certificado único, porque processa corretamente a rotação de certificados. O Chrome tem um único certificado de assinatura. Os apps que têm vários certificados de assinatura não podem girá-los.
Se você precisar oferecer suporte aos níveis 27 e anteriores da API ou se precisar processar navegadores com vários certificados de assinatura, poderá usar 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);
Depurar
Use o comando a seguir para observar erros ou mensagens informativas:
adb logcat | grep -i pay