Salvar cartões no Google Pay

Todas as indústrias do cartão têm casos de uso comuns. Por exemplo, todos os cartões de fidelidade, vales-presente, ofertas, ingressos de eventos, cartões de embarque para voos e cartões de transporte público podem ser adicionados ao app Google Pay de várias maneiras. Selecione um destes métodos para saber mais:


Em um app Android

Use os métodos a seguir para adicionar o botão Salvar no Google Pay ao seu app Android:

Usar o SDK do Android

A API Android permite salvar cartões no Google Pay. Ao integrar o botão Salvar no Google Pay ao app, fica mais fácil para os clientes salvarem o cartão no Google Pay.

Os passos a seguir descrevem como adicionar o botão Salvar no Google Pay no cartão de fidelidade, mas o processo usado é o mesmo em todos os cartões.

1. Criar uma classe

Primeiro, defina a LoyaltyClass. O exemplo a seguir mostra um recurso JSON que representa uma LoyaltyClass:

{
  "accountIdLabel": "Member Id",
  "accountNameLabel": "Member Name",
  "id": "2945482443380251551.ExampleClass1",
  "issuerName": "Baconrista",
  "kind": "walletobjects#loyaltyClass",
  "textModulesData": [
    {
      "header": "Rewards details",
      "body": "Welcome to Baconrista rewards.  Enjoy your rewards for being a loyal customer. " +
               "10 points for every dollar spent.  Redeem your points for free coffee, bacon and more!"
    }
  ],
  "linksModuleData": {
    "uris": [
      {
        "kind": "walletobjects#uri",
        "uri": "https://maps.google.com/map?q=google",
        "description": "Nearby Locations"
      },
      {
        "kind": "walletobjects#uri",
        "uri": "tel:6505555555",
        "description": "Call Customer Service"
      }
    ]
  },
  "imageModulesData": [
    {
      "mainImage": {
        "kind": "walletobjects#image",
        "sourceUri": {
          "kind": "walletobjects#uri",
          "uri": "https://farm4.staticflickr.com/3738/12440799783_3dc3c20606_b.jpg",
          "description": "Coffee beans"
        }
      }
    }
  ],
  "messages": [{
    "header": "Welcome to Banconrista Rewards!",
    "body": "Featuring our new bacon donuts.",
    "kind": "walletobjects#walletObjectMessage"
  }],
  "locations": [{
    "kind": "walletobjects#latLongPoint",
    "latitude": 37.424015499999996,
    "longitude": -122.09259560000001
    },{
    "kind": "walletobjects#latLongPoint",
    "latitude": 37.424354,
    "longitude": -122.09508869999999
    },{
    "kind": "walletobjects#latLongPoint",
    "latitude": 37.7901435,
    "longitude": -122.39026709999997
    },{
    "kind": "walletobjects#latLongPoint",
    "latitude": 40.7406578,
    "longitude": -74.00208940000002
  }],
  "programLogo": {
    "kind": "walletobjects#image",
    "sourceUri": {
      "kind": "walletobjects#uri",
      "uri": "https://farm8.staticflickr.com/7340/11177041185_a61a7f2139_o.jpg"
    }
  },
  "programName": "Baconrista Rewards",
  "rewardsTier": "Gold",
  "rewardsTierLabel": "Tier",
  "reviewStatus": "underReview",
  "hexBackgroundColor": "#ffffff",
  "heroImage": {
   "kind": "walletobjects#image",
   "sourceUri": {
     "kind": "walletobjects#uri",
     "uri": "https://farm8.staticflickr.com/7302/11177240353_115daa5729_o.jpg"
   }
  }
}

2. Criar um objeto

Depois de criar a classe, defina o LoyaltyObject, conforme mostrado no snippet a seguir:

{
  "classId": "2945482443380251551.ExampleClass1",
  "id": "2945482443380251551.ExampleObject1",
  "accountId": "1234567890",
  "accountName": "Jane Doe",
  "barcode": {
    "alternateText": "12345",
    "type": "qrCode",
    "value": "28343E3"
  },
  "textModulesData": [{
    "header": "Jane's Baconrista Rewards",
    "body": "Save more at your local Mountain View store Jane. " +
              "You get 1 bacon fat latte for every 5 coffees purchased.  " +
              "Also just for you, 10% off all pastries in the Mountain View store."
  }],
  "linksModuleData": {
    "uris": [
      {
        "kind": "walletobjects#uri",
        "uri": "https://www.baconrista.com/myaccount?id=1234567890",
        "description": "My Baconrista Account"
      }]
  },
  "infoModuleData": {
    "labelValueRows": [{
      "columns": [{
        "label": "Next Reward in",
        "value": "2 coffees"
      }, {
        "label": "Member Since",
        "value": "01/15/2013"
      }]
    }, {
      "columns": [{
        "label": "Local Store",
        "value": "Mountain View"
      }]
    }],
    "showLastUpdateTime": "true"
  },
  "loyaltyPoints": {
    "balance": {
      "string": "5000"
    },
    "label": "Points",
      "pointsType": "points"
  },
  "messages": [{
    "header": "Jane, welcome to Banconrista Rewards!",
    "body": "Thanks for joining our program. Show this message to " +
              "our barista for your first free coffee on us!"
  }],
  "state": "active"
}

3. Codificar um JWT não assinado

Depois de criar o objeto, codifique o LoyaltyClass e o LoyaltyObject em um JWT não assinado, como mostrado no snippet a seguir:

{
  "iss": "example_service_account@developer.gserviceaccount.com",
  "aud": "google",
  "typ": "savetoandroidpay",
  "iat": 1368029586,
  "payload": {
    "eventTicketClasses": [{
      ... //Event ticket Class JSON
    }],
    "eventTicketObjects": [{
      ... //Event ticket Object JSON
    }],
    "flightClasses": [{
      ... //Flight Class JSON
    }],
    "flightObjects": [{
      ... //Flight Object JSON
    }],
    "giftCardClasses": [{
      ... //Gift card Class JSON
    }],
    "giftCardObjects": [{
      ... //Gift card Object JSON
    }],
    "loyaltyClasses": [{
      ... //Loyalty Class JSON
    }],
    "loyaltyObjects": [{
      ... //Loyalty Object JSON
    }],
    "offerClasses": [{
      ... //Offer Class JSON
    }],
    "offerObjects": [{
      ... //Offer Object JSON
    }],
    "transitClasses": [{
      ... //Transit Class JSON
    }],
    "transitObjects": [{
      ... //Transit Object JSON
    }]
  },
  "origins": ["http://baconrista.com", "https://baconrista.com"]
}

4. Escolher um formato de solicitação para usar

O SDK do Android permite fazer solicitações com um dos formatos a seguir:

Para ver mais detalhes sobre como fazer uma solicitação, consulte Chamar o SDK do Android.

savePasses

As solicitações para o método savePasses têm um payload de string JSON. Isso significa que é possível usar diretamente o JSON para o objeto criado na etapa 3.

É possível fazer solicitações para o "Salvar no Google Pay" para classes e objetos que já existem ou que são inseridos como parte do processo de salvamento. Também é possível salvar vários cartões na mesma solicitação se a indústria de cartões for compatível com essa funcionalidade. Não é possível inserir/atualizar classes e objetos que já existem.

Para melhorar a segurança, alguns campos são considerados confidenciais e, nesses casos, não é possível salvar cartões que já existem apenas ao especificar o campo de ID do objeto. Só é possível salvar objetos que já existem se os campos confidenciais na solicitação corresponderem aos campos dos objetos atuais. Os seguintes campos são considerados confidenciais:

  • object.barcode.value
  • object.smartTapRedemptionValue
savePassesJwt

As solicitações para o método savePassesJwt têm um payload de token de string JWT. Para criar o JWT, assine o objeto da etapa 3 com a chave privada da conta de serviço do OAuth 2.0. Os snippets a seguir mostram como codificar um JWT em várias linguagens:

Java

WobCredentials credentials = null;
WobUtils utils = null;

// Instantiate the WobUtils class which contains handy functions
// Wob utils can be found in the quickstart sample
try {
  credentials = new WobCredentials(
    ServiceAccountEmailAddress,
    ServiceAccountPrivateKeyPath,
    ApplicationName,
    IssuerId);
  utils = new WobUtils(credentials);
} catch (GeneralSecurityException e) {
  e.printStackTrace();
} catch (IOException e) {
  e.printStackTrace();
}

// Add valid domains for the Save to Wallet button
List<String> origins = new ArrayList<String>();
origins.add("http://baconrista.com");
origins.add("https://baconrista.com");
origins.add(req.getScheme() + "://" + req.getServerName() + ":" + req.getLocalPort());

//Generate Objects and Classes here
//........

WobPayload payload = new WobPayload();
payload.addObject({WalletObject/WalletClass});

// Convert the object into a Save to Android Pay Jwt
String jwt = null;
try {
  jwt = utils.generateSaveJwt(payload, origins);
} catch (SignatureException e) {
  e.printStackTrace();
}

PHP

$requestBody = [
  "iss"=> SERVICE_ACCOUNT_EMAIL_ADDRESS,
  "aud" => "google",
  "typ" => "savetoandroidpay",
  "iat"=> time(),
  "payload" => {
    "eventTicketClasses" => [ ], # Event ticket classes
    "eventTicketObjects" => [ ], # Event ticket objects
    "flightClasses" => [ ],      # Flight classes
    "flightObjects" => [ ],      # Flight objects
    "giftCardClasses" => [ ],    # Gift card classes
    "giftCardObjects" => [ ],    # Gift card objects
    "loyaltyClasses" => [ ],     # Loyalty classes
    "loyaltyObjects" => [ ],     # Loyalty objects
    "offerClasses" => [ ],       # Offer classes
    "offerObjects" => [ ],       # Offer objects
    "transitClasses" => [ ],     # Transit classes
    "transitObjects" => [ ]      # Transit objects
  },
  "origins" => ["http://baconrista.com", "https://baconrista.com"]
]
// Generate the Save to Android Pay Jwt
echo $jwt = $assertObj->makeSignedJwt($requestBody, $client);

Python

jwt = {
  'iss': config.SERVICE_ACCOUNT_EMAIL_ADDRESS,
  'aud': 'google',
  'typ': 'savetoandroidpay',
  'iat':  int(time.time()),
  'payload': {
    'webserviceResponse': {
      'result': 'approved',
      'message': 'Success.'
    },
    'eventTicketClasses': [], # Event ticket classes
    'eventTicketObjects': [], # Event ticket objects
    'flightClasses': [],      # Flight classes
    'flightObjects': [],      # Flight objects
    'giftCardClasses': [],    # Gift card classes
    'giftCardObjects': [],    # Gift card objects
    'loyaltyClasses': [],     # Loyalty classes
    'loyaltyObjects': [],     # Loyalty objects
    'offerClasses': [],       # Offer classes
    'offerObjects': [],       # Offer objects
    'transitClasses': [],     # Transit classes
    'transitObjects': []      # Transit objects
  },
  'origins' : ['http://baconrista.com', 'https://baconrista.com']
}

// Generate the Save to Android Pay Jwt
signer = crypt.Signer.from_string(app_key)
signed_jwt = crypt.make_signed_jwt(signer, jwt)
response = webapp2.Response(signed_jwt)

É possível fazer solicitações para o "Salvar no Google Pay" para classes e objetos que já existem ou que são inseridos como parte do processo de salvamento. Também é possível salvar vários cartões na mesma solicitação se a indústria de cartões for compatível com essa funcionalidade. Não é possível inserir/atualizar classes e objetos que já existem. Os JWTs dinâmicos podem ser usados, desde que as classes e objetos já existam.

5. Chamar o SDK do Android

Primeiro, use o método getPayApiAvailabilityStatus para verificar se os métodos savePasses ou savePassesJwt estão disponíveis, conforme mostrado no exemplo a seguir:

import com.google.android.gms.common.api.UnsupportedApiCallException;
import com.google.android.gms.pay.Pay;
import com.google.android.gms.pay.PayApiAvailabilityStatus;
import com.google.android.gms.pay.PayClient;
…
PayClient payClient = Pay.getClient(this);
payClient
  // Use PayClient.RequestType.SAVE_PASSES_JWT for the savePassesJwt API
  .getPayApiAvailabilityStatus(PayClient.RequestType.SAVE_PASSES)
  .addOnSuccessListener(
    status -> {
      switch (status) {
        case PayApiAvailabilityStatus.AVAILABLE:
          // You can call the savePasses API or savePassesJwt API
          ...
          break;
        case PayApiAvailabilityStatus.NOT_ELIGIBLE:
        default:
          // We recommend to either:
          // 1) Hide the save button
          // 2) Fall back to a different Save Passes integration (e.g. JWT link)
          //    Note however that the user *will* only be able to access their
          //    passes on web
          // A not eligible user might become eligible in the future.
          ...
          break;
        }
      })
  .addOnFailureListener(
    exception -> {
      if (exception instanceof UnsupportedApiCallException) {
        // Google Play Services too old. We could not check API availability or
        // user eligibility. We recommend to either:
        // 1) Fall back to a different Save Passes integration (e.g. JWT link)
        //    Note however that the user *may* only be able to access their
        //    passes on web
        // 2) Hide the save button
        ...
      } else {
        // Very old version of Google Play Services or unexpected error!
        ...
      }
    });

Se a API estiver disponível, chame o método savePasses ou savePassesJwt quando o usuário tocar no botão Salvar no Google Pay.

savePasses

private static final int SAVE_TO_GOOGLE_PAY = 1000;
…
String jsonString = … // Build or fetch JSON request
PayClient payClient = Pay.getClient(this);
payClient.savePasses(jsonString, this, SAVE_TO_GOOGLE_PAY);

savePassesJwt

private static final int SAVE_TO_GOOGLE_PAY = 1000;
…
String jwtString = … // Fetch JWT from a secure server
PayClient payClient = Pay.getClient(this);
payClient.savePassesJwt(jwtString, this, SAVE_TO_GOOGLE_PAY);

Essa chamada aciona o fluxo de salvamento. Depois que o fluxo termina, seu app resolve o resultado com onActivityResult. Na sua atividade, esse receptor precisa ser definido desta forma:

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
  // `data` will only have information in the `SAVE_ERROR` case
  if (requestCode == SAVE_TO_GOOGLE_PAY) {
    switch (resultCode) {
      case Activity.RESULT_OK:
        // Save successful
        ...
        break;
      case Activity.RESULT_CANCELED:
        // Save canceled
        ...
        break;
      case PayClient.SavePassesResult.API_ERROR:
        // API error - this should not happen if getPayApiAvailabilityStatus is
        // used correctly
        ...
        break;
      case PayClient.SavePassesResult.SAVE_ERROR:
        // Save error - check EXTRA_API_ERROR_MESSAGE to debug the issue
        // Most save errors indicate an error in the app fingerprint or the Json
        // request payload. In most cases prompting the user to try again will not
        // help.
        if (data != null &&
            !isEmpty(data.getStringExtra(PayClient.EXTRA_API_ERROR_MESSAGE))) {
          ...
        } else {
          // Unexpected! A save error should always have a debug message associated
          // with it
          ...
        }
        break;
      case PayClient.SavePassesResult.INTERNAL_ERROR:
      default:
        // Internal error - prompt the user to try again, if the error persists
        // disable the button
        ...
        break;
    }
  } else {
    ...
  }
}

6. Adicionar o botão "Salvar no Google Pay" à IU

O Google Pay oferece um botão do SDK do Android para você fazer a integração ao app. Os recursos do botão estão disponíveis nas diretrizes da marca.

Este toolkit contém imagens vetoriais dos botões.

Para incorporar um botão ao aplicativo, copie a imagem do botão do toolkit para a pasta res do app e adicione o código a seguir ao arquivo de layout do Android. Cada botão exige a string contentDescription exclusiva e o valor minWidth, além do valor correto de src.

<ImageButton
             android:layout_width="match_parent"
             android:layout_height="48dp"
             android:minWidth="200dp"
             android:clickable="true"
             android:src="@drawable/s2ap" />

A layout_height do botão é de 48 dp, e a minWidth precisa ser de 200 dp.

Siga estes passos para salvar o cartão no Google Pay pelo app:

  1. Conclua as etapas em Adicionar o botão "Salvar no Google Pay" ao seu e-mail ou SMS.
  2. Use uma intent ACTION_VIEW para abrir o link direto pelo botão Salvar no Google Pay.

    Verifique se o botão que aciona a intent usa as diretrizes da marca.

Veja a seguir um exemplo de resumo de fluxo:

  1. Em algum momento antes de salvar seu cartão, uma classe é criada no back-end com a API REST.
  2. Quando o usuário final pede para salvar um cartão, o back-end do servidor envia ao app cliente para Android um JWT que representa um objeto.
  3. O app cliente para Android inclui um botão Salvar no Google Pay que segue nossas diretrizes da marca. Quando você clicar nele, uma intent ACTION_VIEW será aberta para um URI que contém o JWT no caminho. Veja um exemplo:
    https://pay.google.com/gp/v/save/{jwt_generated}
    

Usar o método de solicitação JWT POST

O método de solicitação JWT POST é uma alternativa usada na criação de classes e objetos de passagens aéreas ou ingressos de eventos em apps para Android. Ela é usada quando é muito difícil implementar o trabalho de back-end necessário para criar e inserir uma classe antes que um objeto seja salvo. Esse método é mais útil para ingressos de eventos e passagens aéreas, porque eles são cartões que podem ter várias classes criadas ao longo do tempo. Esse fluxo é resumido da seguinte forma:

  1. Quando o usuário final faz check-in no voo ou resgata o ingresso de um evento, o back-end do servidor renderiza um JWT para o app Android cliente que inclui a classe e o objeto.
  2. O app Android cliente inclui um botão Salvar no Google, que segue nossas diretrizes da marca. Veja o que acontece quando você clica nele:
    1. Uma solicitação POST envia o JWT para um endpoint do Google por HTTPS.
    2. Um URI do corpo de resposta HTTP resultante é enviado de volta. Use-o para abrir uma intent ACTION_VIEW.

O método de solicitação POST do JWT também requer uma chave de API. Ela é anexada como um parâmetro de consulta à chamada da API REST.

Criação de classes

Uma nova classe será criada no nosso back-end somente quando for apresentada com um class.id que não foi salvo anteriormente. Portanto, mesmo que você consiga transmitir os detalhes da classe para o Google pelo JWT várias vezes, o back-end reconhece que a classe já está salva e não cria classes novas toda vez que um cartão de embarque é salvo.

Atualizações de classes

Após o primeiro cartão de embarque, o objeto é salvo com a classe. É possível usar o class.id com nossa API REST para executar as operações ADDMESSAGE, GET, LIST, PATCH e UPDATE, conforme o esperado.

Para alterar os detalhes da classe, use a API Class Update. Se você criar uma classe com class.id=XYZ e outros detalhes e depois tentar criar mais uma classe com class.id=XYZ, mas com detalhes diferentes, a classe original será mantida e nenhuma alteração será aplicada.

Formato JWT

O formato do JWT que você envia está descrito com detalhes na nossa documentação de referência sobre o JWT da Google Pay API for Passes. Nesse payload, é necessário transmitir uma entrada para objetos, que representa o objeto criado por você, e uma entrada para classes, que contém a classe que você criou.

Solicitação HTTP

É possível usar o método INSERT para inserir classes e objetos especificados em um JWT. A chave da API precisa ser definida como um parâmetro de consulta.

Método JWT INSERT

Ao receber um JWT, o método INSERT insere as classes e objetos especificados no JWT. Caso esse processo seja bem-sucedido, você verá a mensagem de HTTP 200.

Solicitação HTTP
POST https://walletobjects.googleapis.com/walletobjects/v1/jwt/

Autorização

Esta solicitação não requer autorização. No entanto, o JWT precisa ser assinado com o método RSA-SHA256. A chave de assinatura é aquela gerada pela conta de serviço do OAuth.

Corpo da solicitação

No corpo da solicitação, forneça dados com esta estrutura:

{ “jwt” : string }

Corpo da resposta

Se o método for bem-sucedido um corpo de resposta com a seguinte estrutura será exibido:

{
    "saveUri": string,
    "resources": {
      "eventTicketClasses": [ eventTicketClass resource, ... ],
      "eventTicketObjects": [ eventTicketObject resource, ... ],
      "flightClasses": [ flightClass resource, ... ],
      "flightObjects": [ flightObject resource, ... ],
      "giftCardClasses": [ giftCardClass resource, ... ],
      "giftCardObjects": [ giftCardObject resource, ... ],
      "loyaltyClasses": [ loyaltyClass resource, ... ],
      "loyaltyObjects": [ loyaltyObject resource, ... ],
      "offerClasses": [ offerClass resource, ... ],
      "offerObjects": [ offerObject resource, ... ],
      "transitClasses": [ transitClass resource, ... ],
      "transitObjects": [ transitObject resource, ... ]
    }
}

Um saveUri é um URI que, quando aberto, permite que o usuário final salve os objetos identificados no JWT na própria Conta do Google. Esse URI é válido apenas uma semana após o retorno.

Consulte a documentação de referência do endpoint do JWT para ver mais detalhes.

Diagramas de fluxo

Veja os fluxos comuns da API para diagramas de fluxo.