Fornire dati di contatto e di spedizione da un'app per pagamenti Android

Come aggiornare l'app di pagamento per Android per fornire l'indirizzo di spedizione e i dati di contatto del pagatore con le API Web Payments.

Sahel Sharify
Sahel Sharify

Inserire l'indirizzo di spedizione e i dati di contatto tramite un modulo web può essere un'esperienza complicata per i clienti. Può causare errori e ridurre il tasso di conversione.

Ecco perché l'API Payment Request supporta una funzionalità per richiedere l'indirizzo di spedizione e i dati di contatto. Questo approccio offre diversi vantaggi:

  • Gli utenti possono scegliere l'indirizzo giusto con pochi tocchi.
  • L'indirizzo viene sempre restituito nel formato standardizzato.
  • È meno probabile che venga inviato un indirizzo errato.

I browser possono rimandare la raccolta dell'indirizzo di spedizione e dei dati di contatto a un'app di pagamento per offrire un'esperienza di pagamento unificata. Questa funzionalità è chiamata delega.

Ove possibile, Chrome delega la raccolta dell'indirizzo di spedizione e dei dati di contatto di un cliente all'app di pagamento Android invocata. La delega riduce le difficoltà durante il pagamento.

Il sito web del commerciante può aggiornare dinamicamente le opzioni di spedizione e il prezzo totale in base alla scelta del cliente dell'indirizzo di spedizione e dell'opzione di spedizione.

Modifica dell'opzione di spedizione e dell'indirizzo di spedizione. Scopri in che modo influisce in modo dinamico sulle opzioni di spedizione e sul prezzo totale.

Per aggiungere il supporto della delega a un'app di pagamento per Android esistente: implementa i seguenti passaggi:

  1. Dichiara le deleghe supportate.
  2. Esegui l'analisi degli extra dell'intent PAY per le opzioni di pagamento obbligatorie.
  3. Fornisci i dati richiesti nella risposta al pagamento.
  4. [Facoltativo] Supporto del flusso dinamico:
    1. Avvisa il commerciante delle modifiche al metodo di pagamento, all'indirizzo di spedizione o all'opzione di spedizione selezionati dall'utente.
    2. Ricevere dal commerciante i dettagli di pagamento aggiornati (ad esempio l'importo totale adeguato in base al costo dell'opzione di spedizione selezionata).

Dichiara le deleghe supportate

Il browser deve conoscere l'elenco delle informazioni aggiuntive che la tua app di pagamento può fornire per poter delegare la raccolta di queste informazioni alla tua app. Dichiara le deleghe supportate come <meta-data> in AndroidManifest.xml della tua app.

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

<resource> deve essere un elenco di stringhe scelte tra i seguenti valori validi:

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

L'esempio seguente può fornire solo un indirizzo di spedizione e l'indirizzo email del pagatore.

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

Analizza gli extra dell'intento PAY per le opzioni di pagamento richieste

Il commerciante può specificare ulteriori informazioni richieste utilizzando il dizionario paymentOptions. Chrome fornirà l'elenco delle opzioni richieste che la tua app può fornire passando i seguenti parametri all'attività PAY come Intent extras.

paymentOptions

paymentOptions è il sottoinsieme di opzioni di pagamento specificate dal commerciante per cui la tua app ha dichiarato il supporto della delega.

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")

Può includere i seguenti parametri:

  • requestPayerName: il valore booleano che indica se il nome del pagatore è obbligatorio o meno.
  • requestPayerPhone: il valore booleano che indica se il telefono del pagatore è obbligatorio o meno.
  • requestPayerEmail: il valore booleano che indica se l'indirizzo email del pagatore è obbligatorio o meno.
  • requestShipping: il valore booleano che indica se sono necessarie o meno le informazioni di spedizione.
  • shippingType: la stringa che mostra il tipo di spedizione. Il tipo di spedizione può essere "shipping", "delivery" o "pickup". La tua app può utilizzare questo suggerimento nella sua UI quando chiede all'utente l'indirizzo o la scelta delle opzioni di spedizione.

shippingOptions

shippingOptions è l'array di opzioni di spedizione specificate dal commerciante che può essere suddiviso in pacchetti. Questo parametro esisterà solo quando paymentOptions.requestShipping == true.

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

Ogni opzione di spedizione è un Bundle con le seguenti chiavi.

  • id: l'identificatore dell'opzione di spedizione.
  • label: l'etichetta dell'opzione di spedizione mostrata all'utente.
  • amount: il bundle del costo di spedizione contenente le chiavi currency e value con valori di stringa.
  • selected: indica se l'opzione di spedizione deve essere selezionata o meno quando l'app di pagamento mostra le opzioni di spedizione.

Tutte le chiavi diverse da selected hanno valori di stringa. selected ha un valore 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)

Fornire le informazioni richieste in una risposta al pagamento

L'app deve includere le informazioni aggiuntive richieste nella risposta all'attività PAY.

A tale scopo, i seguenti parametri devono essere specificati come extra di Intent:

  • payerName: il nome completo del pagatore. Deve essere una stringa non vuota quando paymentOptions.requestPayerName è true.
  • payerPhone: il numero di telefono di chi effettua il pagamento. Deve essere una stringa non vuota quando paymentOptions.requestPayerPhone è true.
  • payerEmail: l'indirizzo email del pagatore. Deve essere una stringa non vuota quando paymentOptions.requestPayerEmail è true.
  • shippingAddress: l'indirizzo di spedizione fornito dall'utente. Deve essere un bundle non vuoto quando paymentOptions.requestShipping è true. Il bundle deve avere le seguenti chiavi che rappresentano parti diverse di un indirizzo fisico.
    • city
    • countryCode
    • dependentLocality
    • organization
    • phone
    • postalCode
    • recipient
    • region
    • sortingCode
    • addressLine Tutte le chiavi diverse da addressLine hanno valori stringa. addressLine è un array di stringhe.
  • shippingOptionId: l'identificatore dell'opzione di spedizione selezionata dall'utente. Deve essere una stringa non vuota quando paymentOptions.requestShipping è true.

Convalida la risposta al pagamento

Se il risultato dell'attività di una risposta di pagamento ricevuta dall'app di pagamento invocata è impostato su RESULT_OK, Chrome controllerà se sono presenti informazioni aggiuntive obbligatorie nei suoi 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 "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".'

Il seguente codice di esempio è un esempio di risposta valida:

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")
    }
}

(Facoltativo) Supporta il flusso dinamico

A volte il costo totale di una transazione aumenta, ad esempio quando l'utente sceglie l'opzione di spedizione espressa o quando l'elenco delle opzioni di spedizione disponibili o i relativi prezzi cambia quando l'utente sceglie un indirizzo di spedizione internazionale. Quando la tua app fornisce l'indirizzo o l'opzione di spedizione selezionati dall'utente, dovrebbe essere in grado di notificare al commerciante eventuali modifiche all'indirizzo o all'opzione di spedizione e mostrare all'utente i dati di pagamento aggiornati (forniti dal commerciante).

AIDL

Per notificare al commerciante le nuove modifiche, utilizza il PaymentDetailsUpdateService servizio dichiarato in AndroidManifest.xml di Chrome. Per utilizzare questo servizio, crea due file AIDL con il contenuto seguente:

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);
}

Comunicare al commerciante modifiche al metodo di pagamento, all'indirizzo di spedizione o all'opzione di spedizione selezionati dall'utente

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
        }
    }
}

Il valore callingPackageName utilizzato per l'intent di avvio del servizio può avere uno dei seguenti valori, a seconda del browser che ha avviato la richiesta di pagamento.

Canale di Chrome Nome pacchetto
Stabile "com.android.chrome"
Beta "com.chrome.beta"
Sviluppo "com.chrome.dev"
Canary "com.chrome.canary"
Cromo "org.chromium.chrome"
Casella di ricerca rapida di Google (un inserzionista WebLayer) "com.google.android.googlequicksearchbox"

changePaymentMethod

Invia una notifica al commerciante in caso di modifiche al metodo di pagamento selezionato dall'utente. Il bundle paymentHandlerMethodData contiene le chiavi methodName e details facoltative, entrambe con valori di stringa. Se la convalida non va a buon fine, Chrome cercherà un bundle non vuoto con un methodName non vuoto e invierà un updatePaymentDetails con uno dei seguenti messaggi di errore tramite callback.updateWith.

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

changeShippingOption

Invia una notifica al commerciante in caso di modifiche all'opzione di spedizione selezionata dall'utente. shippingOptionId deve essere l'identificatore di una delle opzioni di spedizione specificate dal commerciante. Se la convalida non va a buon fine, Chrome controllerà la presenza di un shippingOptionId non vuoto e invierà un updatePaymentDetails con il seguente messaggio di errore tramite callback.updateWith.

'Shipping option identifier required.'

changeShippingAddress

Invia una notifica al commerciante in caso di modifiche all'indirizzo di spedizione fornito dall'utente. Chrome verificherà la presenza di un bundle shippingAddress non vuoto con un countryCode valido e invierà un updatePaymentDetails con il seguente messaggio di errore tramite callback.updateWith se la convalida non va a buon fine.

'Payment app returned invalid shipping address in response.'

Messaggio di errore relativo allo stato non valido

Se Chrome rileva uno stato non valido alla ricezione di una qualsiasi delle richieste di modifica, chiama callback.updateWith con un bundle updatePaymentDetails oscurato. Il bundle conterrà solo la chiave error con "Invalid state". Ecco alcuni esempi di stato non valido:

  • Quando Chrome è ancora in attesa della risposta del commerciante a una modifica precedente (ad esempio un evento di modifica in corso).
  • L'identificatore dell'opzione di spedizione fornito dall'app di pagamento non appartiene a nessuna delle opzioni di spedizione specificate dal commerciante.

Ricevere i dettagli di pagamento aggiornati dal commerciante

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 è il bundle equivalente al dizionario PaymentRequestDetailsUpdate WebIDL (dopo aver oscurato il campo modifiers) e contiene le seguenti chiavi facoltative:

  • total: un bundle contenente le chiavi currency e value, entrambe con valori di stringa
  • shippingOptions: l'array di opzioni di spedizione parcellabili
  • error: una stringa contenente un messaggio di errore generico (ad es. quandochangeShippingOption non fornisce un identificatore di opzione di spedizione valido)
  • stringifiedPaymentMethodErrors: una stringa JSON che rappresenta gli errori di convalida per il metodo di pagamento
  • addressErrors: un bundle con chiavi facoltative identiche a shipping address e valori stringa. Ogni chiave rappresenta un errore di convalida relativo alla parte corrispondente dell'indirizzo di spedizione.

Una chiave assente indica che il relativo valore non è cambiato.