Como fornecer dados de contato e de envio de um app de pagamento para Android

Como atualizar seu aplicativo de pagamento do Android para fornecer o endereço de entrega e as informações de contato do pagador com as Web Payments APIs.

Sharify com o Sahel
Shel Sharify

Inserir o endereço de entrega e os dados de contato por meio de um formulário da Web pode ser uma experiência complicada para os clientes. Isso pode causar erros e reduzir a taxa de conversão.

É por isso que a API Payment Request tem suporte a um recurso para solicitar endereço de entrega e dados de contato. Isso oferece vários benefícios:

  • Os usuários podem escolher o endereço certo com apenas alguns toques.
  • O endereço é sempre retornado no formato padronizado.
  • É menos provável que o endereço seja enviado incorretamente.

Os navegadores podem adiar a coleta de endereços de entrega e dados de contato para um app de pagamento, proporcionando uma experiência de pagamento unificada. Essa funcionalidade é chamada de delegação.

Sempre que possível, o Chrome delega a coleta do endereço de entrega e dos dados de contato de um cliente ao app de pagamento do Android invocado. A delegação reduz o atrito durante a finalização da compra.

O site do comerciante pode atualizar dinamicamente as opções de envio e o preço total, dependendo das escolhas do cliente de endereço e opção de envio.

Mudança da opção de frete e do endereço de entrega em ação. Confira como isso afeta as opções de frete e o preço total dinamicamente.

Para adicionar suporte à delegação a um app de pagamento do Android já existente, implemente as seguintes etapas:

  1. Declare as delegações compatíveis.
  2. Analisar extras de intent PAY para opções de pagamento necessárias.
  3. Forneça as informações necessárias na resposta do pagamento.
  4. [Opcional] Suporte ao fluxo dinâmico:
    1. Notificar o comerciante sobre alterações na forma de pagamento, no endereço de entrega ou na opção de envio selecionada pelo usuário.
    2. Receber detalhes de pagamento atualizados do comerciante (por exemplo, o valor total ajustado com base no custo da opção de frete selecionada).

Declarar delegações compatíveis

O navegador precisa saber a lista de informações extras que o app de pagamento pode fornecer para delegar a coleta dessas informações ao app. Declare as delegações compatíveis como um <meta-data> no AndroidManifest.xml do app.

<activity
  android:name=".PaymentActivity"
  …
  <meta-data
    android:name="org.chromium.payment_supported_delegations"
    android:resource="@array/supported_delegations" />
</activity>

<resource> precisa ser uma lista de strings escolhidas entre os seguintes valores válidos:

[ "payerName", "payerEmail", "payerPhone", "shippingAddress" ]

O exemplo a seguir só pode fornecer um endereço de entrega e o endereço de e-mail do pagador.

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <string-array name="supported_delegations">
    <item>payerEmail</item>
    <item>shippingAddress</item>
  </string-array>
</resources>

Analisar extras de intent PAY para opções de pagamento necessárias

O comerciante pode especificar outras informações necessárias usando o dicionário paymentOptions. O Chrome vai fornecer a lista de opções obrigatórias que seu app pode fornecer transmitindo os seguintes parâmetros para a atividade PAY como extras de intent.

paymentOptions

paymentOptions é o subconjunto de opções de pagamento especificadas pelo comerciante para que seu app declarou compatibilidade com delegação.

val paymentOptions: Bundle? = extras.getBundle("paymentOptions")
val requestPayerName: Boolean? = paymentOptions?.getBoolean("requestPayerName")
val requestPayerPhone: Boolean? = paymentOptions?.getBoolean("requestPayerPhone")
val requestPayerEmail: Boolean? = paymentOptions?.getBoolean("requestPayerEmail")
val requestShipping: Boolean? = paymentOptions?.getBoolean("requestShipping")
val shippingType: String? = paymentOptions?.getString("shippingType")

Ela pode incluir os seguintes parâmetros:

  • requestPayerName: o booleano que indica se o nome do pagador é obrigatório.
  • requestPayerPhone: o booleano que indica se o telefone do pagador é obrigatório.
  • requestPayerEmail: o booleano que indica se o e-mail do pagador é obrigatório.
  • requestShipping: o booleano que indica se as informações de frete são obrigatórias ou não.
  • shippingType: a string que mostra o tipo de frete. O tipo de frete pode ser "shipping", "delivery" ou "pickup". O app pode usar essa dica na interface ao solicitar o endereço do usuário ou a escolha de opções de envio.

shippingOptions

shippingOptions é a matriz parcelable das opções de frete especificadas pelo comerciante. Esse parâmetro só existirá quando paymentOptions.requestShipping == true.

val shippingOptions: List<ShippingOption>? =
    extras.getParcelableArray("shippingOptions")?.mapNotNull {
        p -> from(p as Bundle)
    }

Cada opção de envio é um Bundle com as chaves a seguir.

  • id: é o identificador da opção de envio.
  • label: o rótulo da opção de envio mostrado ao usuário.
  • amount: o pacote do custo de frete que contém as chaves currency e value com valores de string.
  • selected: se a opção de frete deve ou não ser selecionada quando o app de pagamento exibir as opções de envio.

Todas as chaves, exceto selected, têm valores de string. selected tem um valor booleano.

val id: String = bundle.getString("id")
val label: String = bundle.getString("label")
val amount: Bundle = bundle.getBundle("amount")
val selected: Boolean = bundle.getBoolean("selected", false)

Fornecer as informações necessárias em uma resposta de pagamento

Seu app precisa incluir as outras informações necessárias na resposta à atividade PAY.

Para isso, os seguintes parâmetros precisam ser especificados como extras de intent:

  • payerName: nome completo do pagador. Ela precisa ser uma string não vazia quando paymentOptions.requestPayerName for verdadeiro.
  • payerPhone: número de telefone do pagador. Ela precisa ser uma string não vazia quando paymentOptions.requestPayerPhone for verdadeiro.
  • payerEmail: endereço de e-mail do pagador. Ela precisa ser uma string que não esteja em branco quando paymentOptions.requestPayerEmail for verdadeiro.
  • shippingAddress: o endereço de entrega fornecido pelo usuário. Ele precisa ser um pacote não vazio quando paymentOptions.requestShipping for verdadeiro. O pacote precisa ter as seguintes chaves, que representam partes diferentes em um endereço físico.
    • city
    • countryCode
    • dependentLocality
    • organization
    • phone
    • postalCode
    • recipient
    • region
    • sortingCode
    • addressLine Todas as chaves, exceto addressLine, têm valores de string. O addressLine é uma matriz de strings.
  • shippingOptionId: o identificador da opção de envio selecionada pelo usuário. Ela precisa ser uma string não vazia quando paymentOptions.requestShipping for verdadeiro.

Validar resposta de pagamento

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á informações adicionais necessárias 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 "payerEmail".'
'Payment app returned invalid response. Missing field "payerName".'
'Payment app returned invalid response. Missing field "payerPhone".'
'Payment app returned invalid shipping address in response.'
'... is not a valid CLDR country code, should be 2 upper case letters [A-Z]'
'Payment app returned invalid response. Missing field "shipping option".'

O exemplo de código a seguir é um exemplo de uma resposta válida:

fun Intent.populateRequestedPaymentOptions() {
    if (requestPayerName) {
        putExtra("payerName", "John Smith")
    }
    if (requestPayerPhone) {
        putExtra("payerPhone", "4169158200")
    }
    if (requestPayerEmail) {
        putExtra("payerEmail", "john.smith@gmail.com")
    }
    if(requestShipping) {
        val address: Bundle = Bundle()
        address.putString("countryCode", "CA")
        val addressLines: Array<String> =
                arrayOf<String>("111 Richmond st. West")
        address.putStringArray("addressLines", addressLines)
        address.putString("region", "Ontario")
        address.putString("city", "Toronto")
        address.putString("postalCode", "M5H2G4")
        address.putString("recipient", "John Smith")
        address.putString("phone", "4169158200")
        putExtra("shippingAddress", address)
        putExtra("shippingOptionId", "standard")
    }
}

Opcional: suporte ao fluxo dinâmico

Às vezes, o custo total de uma transação aumenta, por exemplo, quando o usuário escolhe a opção de frete expresso ou quando a lista de opções de frete disponíveis ou os preços deles mudam quando o usuário escolhe um endereço de entrega internacional. Quando o app fornece a opção ou o endereço de entrega selecionado pelo usuário, ele precisa ser capaz de notificar o comerciante sobre qualquer mudança na opção ou no endereço de entrega e mostrar ao usuário os detalhes de pagamento atualizados (fornecidos pelo comerciante).

AIDL

Para notificar o comerciante sobre novas mudanças, use o serviço PaymentDetailsUpdateService declarado no AndroidManifest.xml do Chrome. Para usar esse serviço, crie dois arquivos AIDL com o seguinte conteúdo:

app/src/main/aidl/org/chromium/components/payments/IPaymentDetailsUpdateService

package org.chromium.components.payments;
import android.os.Bundle;

interface IPaymentDetailsUpdateServiceCallback {
    oneway void updateWith(in Bundle updatedPaymentDetails);

    oneway void paymentDetailsNotUpdated();
}

app/src/main/aidl/org/chromium/components/payments/IPaymentDetailsUpdateServiceCallback

package org.chromium.components.payments;
import android.os.Bundle;
import org.chromium.components.payments.IPaymentDetailsUpdateServiceCallback;

interface IPaymentDetailsUpdateService {
    oneway void changePaymentMethod(in Bundle paymentHandlerMethodData,
            IPaymentDetailsUpdateServiceCallback callback);

    oneway void changeShippingOption(in String shippingOptionId,
            IPaymentDetailsUpdateServiceCallback callback);

    oneway void changeShippingAddress(in Bundle shippingAddress,
            IPaymentDetailsUpdateServiceCallback callback);
}

Notificar o comerciante sobre mudanças na forma de pagamento, no endereço de entrega ou na opção de envio selecionada pelo usuário

private fun bind() {
    // The action is introduced in Chrome version 92, which supports the service in Chrome
    // and other browsers (e.g., WebLayer).
    val newIntent = Intent("org.chromium.intent.action.UPDATE_PAYMENT_DETAILS")
        .setPackage(callingBrowserPackage)
    if (packageManager.resolveService(newIntent, PackageManager.GET_RESOLVED_FILTER) == null) {
        // Fallback to Chrome-only approach.
        newIntent.setClassName(
            callingBrowserPackage,
            "org.chromium.components.payments.PaymentDetailsUpdateService")
        newIntent.action = IPaymentDetailsUpdateService::class.java.name
    }
    isBound = bindService(newIntent, connection, Context.BIND_AUTO_CREATE)
}

private val connection = object : ServiceConnection {
    override fun onServiceConnected(className: ComponentName, service: IBinder) {
        val service = IPaymentDetailsUpdateService.Stub.asInterface(service)
        try {
            if (isOptionChange) {
                service?.changeShippingOption(selectedOptionId, callback)
            } else (isAddressChange) {
                service?.changeShippingAddress(selectedAddress, callback)
            } else {
                service?.changePaymentMethod(methodData, callback)
            }
        } catch (e: RemoteException) {
            // Handle the remote exception
        }
    }
}

O callingPackageName usado para a intent de inicialização do serviço pode ter um dos valores abaixo, dependendo do navegador que iniciou a solicitação de pagamento.

Canal do Chrome Nome do pacote
Estável "com.android.chrome"
Beta "com.chrome.beta"
Dev "com.chrome.dev"
Canary "com.chrome.canary"
Chromium "org.chromium.chrome"
Caixa de pesquisa rápida do Google (um incorporador da WebLayer) "com.google.android.googlequicksearchbox"

changePaymentMethod

Notifica o comerciante sobre alterações na forma de pagamento selecionada pelo usuário. O pacote paymentHandlerMethodData contém chaves methodName e details opcionais, ambos com valores de string. O Chrome vai verificar se há um pacote não vazio com um methodName não vazio e enviar um updatePaymentDetails com uma das seguintes mensagens de erro via callback.updateWith se a validação falhar.

'Method data required.'
'Method name required.'

changeShippingOption

Notifica o comerciante sobre alterações na opção de frete selecionada pelo usuário. shippingOptionId precisa ser o identificador de uma das opções de frete especificadas pelo comerciante. O Chrome vai verificar se há um shippingOptionId que não está vazio e enviar um updatePaymentDetails com a seguinte mensagem de erro via callback.updateWith se a validação falhar.

'Shipping option identifier required.'

changeShippingAddress

Notifica o comerciante sobre mudanças no endereço de entrega fornecido pelo usuário. O Chrome vai verificar se há um pacote shippingAddress não vazio com um countryCode válido e enviar uma updatePaymentDetails com a seguinte mensagem de erro via callback.updateWith se a validação falhar.

'Payment app returned invalid shipping address in response.'

Mensagem de erro de estado inválida

Se o Chrome encontrar um estado inválido ao receber qualquer uma das solicitações de mudança, ele vai chamar callback.updateWith com um pacote updatePaymentDetails editado. O pacote conterá apenas a chave error com "Invalid state". Confira alguns exemplos de estados inválidos:

  • Quando o Chrome ainda está aguardando a resposta do comerciante a uma alteração anterior, como um evento de alteração em andamento.
  • O identificador da opção de envio fornecido pelo app de pagamento não pertence a nenhuma das opções especificadas pelo comerciante.

Receber detalhes de pagamento atualizados do comerciante

private fun unbind() {
    if (isBound) {
        unbindService(connection)
        isBound = false
    }
}

private val callback: IPaymentDetailsUpdateServiceCallback =
    object : IPaymentDetailsUpdateServiceCallback.Stub() {
        override fun paymentDetailsNotUpdated() {
            // Payment request details have not changed.
            unbind()
        }

        override fun updateWith(updatedPaymentDetails: Bundle) {
            newPaymentDetails = updatedPaymentDetails
            unbind()
        }
    }

updatePaymentDetails é o pacote equivalente ao dicionário PaymentRequestDetailsUpdate WebIDL (após a edição do campo modifiers) e contém as seguintes chaves opcionais:

  • total: um pacote contendo as chaves currency e value, ambas com valores de string
  • shippingOptions: a matriz parcelable de opções de frete.
  • error: uma string que contém uma mensagem de erro genérica (por exemplo, quando changeShippingOption não fornece um identificador de opção de envio válido)
  • stringifiedPaymentMethodErrors: uma string JSON que representa os erros de validação da forma de pagamento.
  • addressErrors: um pacote com chaves opcionais idênticas aos endereços de entrega e valores de string. Cada chave representa um erro de validação relacionado à parte correspondente do endereço de entrega.

Uma chave ausente significa que o valor não foi alterado.