Zapisywanie kart w Google Pay

Przypadki użycia są takie same we wszystkich kategoriach kart. Na przykład wszystkie karty lojalnościowe, karty podarunkowe, oferty specjalne, bilety na wydarzenia, lotnicze karty pokładowe i bilety na przejazdy można dodać do aplikacji Google Pay na kilka sposobów. Aby dowiedzieć się więcej, wybierz jedną z tych metod:


Z aplikacji na Androida

Aby dodać przycisk Zapisz w Google Pay do aplikacji na Androida, zastosuj jedną z tych metod:

Użycie pakietu SDK na Androida

Interfejs Android API umożliwia zapisywanie kart w Google Pay. Integracja przycisku Zapisz w Google Pay z Twoją aplikacją ułatwia klientom zapisywanie kart w Google Pay.

Aby dodać przycisk Zapisz w Google Pay dla karty lojalnościowej, wykonaj te czynności. Pamiętaj jednak, że proces dodawania wygląda tak samo w przypadku wszystkich kart.

1. Utwórz klasę

Najpierw określ LoyaltyClass. Poniższy przykład przedstawia zasób JSON reprezentujący 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. Utwórz obiekt

Po utworzeniu klasy zajęć zdefiniuj LoyaltyObject zgodnie z tym fragmentem:

{
  "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. Zakoduj nieprzypisany token JWT

Po utworzeniu obiektu zakoduj LoyaltyClass i LoyaltyObject do nieprzypisanego tokena JWT zgodnie z tym fragmentem:

{
  "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. Wybierz format żądania, którego chcesz użyć

Pakiet Android SDK pozwala wysyłać żądania w jednym z tych formatów:

Więcej informacji o tym, jak przesłać żądanie, znajdziesz w artykule Wywołanie pakietu Android SDK.

savePasses

Żądania wysyłane do metody savePasses mają ładunek ciągu JSON. Oznacza to, że możesz bezpośrednio użyć JSON dla obiektu utworzonego w kroku 3.

Możesz wysyłać żądania Zapisz w Google Pay dla istniejących klas i obiektów lub takich, które są częścią procesu zapisywania. Możesz też zapisać wiele kart w tym samym żądaniu, jeśli kategoria karty obsługuje tę funkcję. Nie możesz aktualizować ani dodawać istniejących klas i obiektów.

Ze względów bezpieczeństwa niektóre pola są uznawane za wrażliwe. W takich przypadkach nie możesz zapisać istniejących kart jedynie przez określenie pola identyfikatora obiektu. Możesz zapisywać obiekty, które już istnieją, tylko wtedy, gdy wrażliwe pola w żądaniu są zgodne z polami istniejących obiektów. Te pola są uznawane za wrażliwe:

  • object.barcode.value
  • object.smartTapRedemptionValue
savePassesJwt

Żądania wysyłane do metody savePassesJwt mają ładunek tokena ciągu JWT. Aby utworzyć token JWT, podpisz obiekt z kroku 3 przy użyciu prywatnego klucza konta usługi OAuth 2.0. Poniżej znajdziesz fragmenty pokazujące, jak zakodować token JWT w różnych językach:

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)

Możesz wysyłać żądania Zapisz w Google Pay dla istniejących klas i obiektów lub takich, które są częścią procesu zapisywania. Możesz też zapisać wiele kart w tym samym żądaniu, jeśli kategoria karty obsługuje tę funkcję. Nie możesz aktualizować ani dodawać istniejących klas i obiektów. Skróconych tokenów JWT można używać w przypadku istniejących klas i obiektów.

5. Wywołaj pakiet Android SDK

Najpierw użyj metody getPayApiAvailabilityStatus, by sprawdzić, czy są dostępne metody savePasses i savePassesJwt, jak pokazano w poniższym przykładzie:

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

Jeśli interfejs API jest dostępny, wywołaj metodę savePasses lub savePassesJwt, gdy użytkownik kliknie przycisk Zapisz w 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);

To wywołanie uruchamia proces zapisywania. Po zakończeniu procesu Twoja aplikacja wyświetli wynik w onActivityResult. W działaniu odbiorca musi być zdefiniowany w podobny sposób:

@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. Dodaj przycisk Zapisz w Google Pay do UI

W pakiecie Android SDK dostępny jest przycisk Google Pay, który można zintegrować z aplikacją. Zasoby przycisku znajdziesz we wskazówkach dotyczących marki.

Ten pakiet narzędzi zawiera obrazy wektorowe przycisków.

Aby dodać przycisk do swojej aplikacji, skopiuj jego obraz z pakietu narzędzi do folderu res aplikacji i dodaj podany poniżej kod do pliku układu na Androida. Każdy przycisk musi mieć unikalny ciąg znaków contentDescription, wartość minWidth i poprawną wartość src.

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

Parametr layout_height przycisku to 48 dp. Parametr minWidth musi mieć wartość 200 dp.

Aby zapisać kartę w Google Pay z aplikacji, wykonaj te czynności:

  1. Wykonaj czynności opisane w artykule na temat dodawania przycisku Zapisz w Google Pay do e-maila lub SMS-a.
  2. Otwórz precyzyjny link z przycisku Zapisz w Google Pay za pomocą intencji ACTION_VIEW.

    Przycisk wyświetlający intencję musi być zgodny ze wskazówkami dotyczącymi marki.

Oto przykładowe podsumowanie przepływu:

  1. Zanim zostanie zapisana karta, w backendzie tworzona jest klasa za pomocą interfejsu API REST.
  2. Kiedy użytkownik chce zapisać kartę, backend serwera wysyła token JWT, który reprezentuje obiekt, do klienckiej aplikacji na Androida.
  3. Kliencka aplikacja na Androida zawiera przycisk Zapisz w Google Pay, który jest zgodny ze wskazówkami dotyczącymi marki. Kliknięcie go otwiera intencję ACTION_VIEW kierującą do identyfikatora URI, którego ścieżka zawiera token JWT. Oto przykład:
    https://pay.google.com/gp/v/save/{jwt_generated}

Użycie metody żądania POST tokena JWT

Metoda żądania POST tokena JWT pozwala w inny sposób tworzyć klasy i obiekty do aplikacji na Androida dla lotów lub biletów na wydarzenia. Używa się jej, gdy trudno jest wdrożyć backend wymagany do utworzenia i wstawienia klasy przed zapisem obiektu. Ta metoda najlepiej sprawdza się przy biletach na wydarzenia i kartach pokładowych, ponieważ dla tych kart można tworzyć wiele klas. Przepływ ten wygląda tak:

  1. Gdy użytkownik dokonuje odprawy przed lotem lub wykorzystuje bilet na wydarzenie, backend serwera renderuje w klienckiej aplikacji na Androida token JWT, który zawiera klasę oraz obiekt.
  2. Kliencka aplikacja na Androida zawiera przycisk Zapisz w Google, który jest zgodny ze wskazówkami dotyczącymi marki. Gdy go klikniesz:
    1. Żądanie POST wyśle token JWT przez HTTPS do punktu końcowego Google.
    2. Zwrócony zostanie identyfikator URI z odpowiedzi HTTP, za pomocą którego należy otworzyć intencję ACTION_VIEW.

Metoda żądania POST tokena JWT wymaga również klucza interfejsu API. Jest on dołączany jako parametr zapytania do wywołania interfejsu API REST.

Tworzenie klasy

Nowa klasa tworzy się w naszym backendzie tylko po przedstawieniu identyfikatora class.id, który nie został wcześniej zapisany. Można więc przekazać dane klasy do Google za pomocą tokena JWT, ale backend wykrywa, że klasa jest już zapisana, i nie tworzy nowych klas po każdym zapisaniu karty pokładowej.

Aktualizacje klasy

Po utworzeniu pierwszej karty pokładowej razem z klasą jest zapisywany obiekt. Możesz normalnie używać class.id w interfejsie API REST, aby wykonywać operacje ADDMESSAGE, GET, LIST, PATCH i UPDATE.

Aby zmienić dane klasy, musisz użyć interfejsu API aktualizacji klasy. Jeśli utworzysz klasę z class.id=XYZ i innymi informacjami, a później spróbujesz utworzyć klasę z class.id=XYZ i zmienionymi danymi klasy, zachowamy pierwszą klasę i nie wprowadzimy żadnych zmian.

Format tokena JWT

Format wysyłanego tokena JWT opisaliśmy dokładnie w dokumentacji tokenów JWT Google Pay API for Passes. Ten payload przekazuje 1 wpis obiektów (odpowiada obiektowi, który chcesz utworzyć) i 1 wpis klas (zawiera utworzoną klasę).

Żądanie HTTP

Za pomocą metody INSERT można wstawiać klasy i obiekty określone w JWT. Klucz API musi być ustawiony jako parametr zapytania.

Metoda INSERT tokena JWT

Przy podanym tokenie JWT metoda INSERT wstawia klasy i obiekty podane w tym tokenie. Jeśli operacja się uda, zostanie zwrócona odpowiedź HTTP 200.

Żądanie HTTP
POST https://walletobjects.googleapis.com/walletobjects/v1/jwt/

Autoryzacja

To żądanie nie wymaga autoryzacji. Token JWT musi być podpisany kluczem RSA-SHA256. Klucz podpisywania jest tworzony przez konto usługi OAuth.

Treść żądania

Dane w treści żądania muszą mieć poniższy format:

{ “jwt” : string }

Treść odpowiedzi

Jeśli operacja się uda, metoda zwróci odpowiedź w poniższym formacie:

{
    "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, ... ]
    }
}

Ciąg saveUri to identyfikator URI, który po otwarciu pozwala użytkownikowi zapisać obiekty podane w tokenie JWT na jego koncie Google. Jest on ważny tylko przez tydzień po zwróceniu.

Więcej informacji znajdziesz w dokumentacji punktu końcowego JWT.

Diagramy przepływu

Diagramy przepływu znajdziesz w opisie typowych przepływów interfejsu API.