Google Pay にパスを保存する

コレクションでコンテンツを整理 必要に応じて、コンテンツの保存と分類を行います。

パスはさまざまな用途で利用できますが、それらすべてに共通する使用方法があります。たとえば、ポイントカード、ギフトカード、クーポン、イベント チケット、搭乗券、乗車券はすべて、いくつかの方法で Google Pay アプリに追加できます。以下のいずれかの方法を選択して、詳細をご覧ください。


Android アプリから

[Google Pay に保存] ボタンは、次の方法で Android アプリに追加できます。

Android SDK を使用する

Android API を使用すると、Google Pay にパスを保存できます。[Google Pay に保存] ボタンをアプリに統合すると、お客様がパスを Google Pay に簡単に保存できるようになります。

次のステップは、ポイントパスの [Google Pay に保存] ボタンを追加する方法を示していますが、プロセスはすべてのパスで共通です。

1. クラスを作成する

最初に LoyaltyClass を定義します。次の例は、LoyaltyClass を表す JSON リソースを示しています。

{
  "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. オブジェクトを作成する

クラスを作成したら、次のスニペットに示すように LoyaltyObject を定義します。

{
  "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. 無署名の JWT をエンコードする

オブジェクトを作成したら、次のスニペットに示すように、LoyaltyClassLoyaltyObject を無署名の JWT にエンコードします。

{
  "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. 使用するリクエスト形式を選択する

Android SDK では、次のいずれかの形式でリクエストを行うことができます。

リクエストを行う方法について詳しくは、Android SDK を呼び出すをご覧ください。

savePasses

savePasses メソッドに対するリクエストでは、JSON 文字列ペイロードを使用します。つまり、ステップ 3 で作成したオブジェクトの JSON を直接使用できます。

すでに存在するクラスやオブジェクト、または保存プロセスの一部として挿入されたクラスやオブジェクトについて、「Google Pay に保存」をリクエストできます。1 回のリクエストで複数のパスを保存することもできます(パスカテゴリでサポートされている場合)。すでに存在するクラスとオブジェクトをアップサートすることはできません。

セキュリティ向上のため、機密扱いとみなされるフィールドが含まれる場合があります。このような場合、オブジェクト ID フィールドを指定するだけでは、すでに存在するパスを保存することはできません。リクエスト内の機密フィールドが、すでに存在するオブジェクトのフィールドと一致する場合にのみ、そのオブジェクトを保存できます。次のフィールドが機密扱いとみなされます。

  • object.barcode.value
  • object.smartTapRedemptionValue
savePassesJwt

savePassesJwt メソッドに対するリクエストでは、JWT 文字列トークン ペイロードを使用します。JWT を作成するには、OAuth 2.0 サービス アカウントの秘密鍵を使用して、ステップ 3 のオブジェクトに署名します。次のスニペットは、JWT をさまざまな言語でエンコードする方法を示しています。

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)

すでに存在するクラスやオブジェクト、または保存プロセスの一部として挿入されたクラスやオブジェクトについて、「Google Pay に保存」をリクエストできます。1 回のリクエストで複数のパスを保存することもできます(パスカテゴリでサポートされている場合)。すでに存在するクラスとオブジェクトをアップサートすることはできません。スリムな JWT は、クラスとオブジェクトがすでに存在する場合に使用できます。

5. Android SDK を呼び出す

まず、次の例で示すように、getPayApiAvailabilityStatus メソッドを使用して、savePasses メソッドまたは savePassesJwt メソッドが使用可能かどうかを確認します。

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

API が利用可能な場合は、ユーザーが [Google Pay に保存] ボタンをタップしたときに savePasses メソッドまたは savePassesJwt メソッドを呼び出します。

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

この呼び出しによって保存フローがトリガーされます。フローが終了すると、アプリは onActivityResult で結果を解決します。Activity では、このレシーバーを次のように定義する必要があります。

@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 [Google Pay に保存] ボタンを UI に追加する

Google Pay には、アプリに統合するための Android SDK ボタンが用意されています。ボタンアセットは、ブランド ガイドラインから入手できます。

このツールキットには、ボタンのベクター画像が含まれています。

ボタンをアプリに組み込むには、ツールキットのボタン画像をアプリケーションの res フォルダにコピーして、次のコードを Android レイアウト ファイルに追加します。src の正しい値に加えて、ボタンごとに固有の contentDescription 文字列と minWidth 値が必要になります。

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

ボタンの layout_height は 48 dp、minWidth は 200 dp にする必要があります。

次のステップに従って、アプリから Google Pay に Pass を保存します。

  1. [Google Pay に保存] ボタンをメールまたは SMS に追加するのステップを完了します。
  2. ACTION_VIEW インテントを使用して、[Google Pay に保存] ボタンからディープリンクを開きます。

    インテントをトリガーするボタンがブランドのガイドラインに沿っていることを確認してください。

以下の例は、フローの概要を示したものです。

  1. パスが保存される前のある時点で、バックエンドで REST API を使用してクラスが作成されます。
  2. エンドユーザーがパスの保存を要求すると、サーバーのバックエンドでオブジェクトを表す JWT が Android クライアント アプリに送信されます。
  3. Android クライアント アプリには、Google のブランド ガイドラインに沿った [Google Pay に保存] ボタンが含まれています。これをクリックすると ACTION_VIEW インテントが開きます。このインテントには、パスに JWT を含む URI が設定されています。次に例を示します。
    https://pay.google.com/gp/v/save/{jwt_generated}
    

JWT POST リクエスト メソッドを使用する

JWT の POST リクエスト メソッドは、Android アプリ用のフライトまたはイベント チケットのクラスおよびオブジェクトを作成するための代替メソッドです。このメソッドが使用されるのは、オブジェクトを保存する前にクラスの作成と挿入に必要なバックエンド作業を実装するのが難しい場合です。このメソッドは、イベント チケットや搭乗券(時間の経過とともに多くのクラスが作成される可能性があるパス)を扱う場合にとても便利です。このフローを簡単に説明すると、次のようになります。

  1. エンドユーザーがフライトのチェックインを行うか、またはイベント チケットを利用するときに、サーバーのバックエンドでクラスとオブジェクトの両方を含む JWT が Android クライアント アプリにレンダリングされます。
  2. Android クライアント アプリには、Google のブランド ガイドラインに沿った [Google に保存] ボタンが含まれています。ボタンをクリックすると、次のようになります。
    1. POST リクエストにより、JWT が HTTPS を通じて Google エンドポイントに送信されます。
    2. 作成された HTTP レスポンスの本文の URI が返されます。この URI を使用して ACTION_VIEW インテントを開く必要があります。

JWT POST リクエスト メソッドには、API キーも必要です。これは、REST API 呼び出しにクエリ パラメータとして追加されます。

クラスの作成

Google のバックエンドで新しいクラスが作成されるのは、過去に一度も保存されたことのない class.id が指定された場合のみです。したがって、JWT を介してクラスの詳細を Google に複数回渡した可能性がある場合、バックエンドではクラスがすでに保存されていることが認識され、搭乗券が保存されるたびに新しいクラスが作成されることはありません。

クラスの更新

最初の搭乗券の後、オブジェクトはクラスと一緒に保存されます。REST API で想定される class.id を使用して、ADDMESSAGEGETLISTPATCHUPDATE のオペレーションを想定どおりに実行できます。

クラスの詳細を変更するには、Class Update API を使用する必要があります。他のクラスの詳細をいくつか含む class.id=XYZ のクラスを作成してから、異なるクラスの詳細を含む class.id=XYZ のクラスを作成しようとすると、元のクラスが維持され、変更は適用されません。

JWT の形式

送信する JWT の形式は、Google Pay API for Passes JWT に関するリファレンス ドキュメントで詳しく説明されています。この payload では、作成するオブジェクトを表すオブジェクトのエントリを 1 つと、作成したクラスが含まれるクラスのエントリを 1 つ渡します。

HTTP リクエスト

INSERT メソッドを使用して、JWT で指定されたクラスやオブジェクトを挿入できます。API キーはクエリ パラメータとして設定する必要があります。

JWT INSERT メソッド

JWT が指定された場合、INSERT メソッドを実行すると、JWT で指定されたクラスとオブジェクトが挿入されます。挿入が成功すると、200 HTTP レスポンスが返されます。

HTTP リクエスト
POST https://walletobjects.googleapis.com/walletobjects/v1/jwt/

承認

このリクエストに承認は不要です。ただし、JWT は RSA-SHA256 で署名する必要があります。署名鍵は OAuth サービス アカウントで生成された鍵です。

リクエストの本文

リクエストの本文には、以下の構造を使用してデータを指定してください。

{ “jwt” : string }

レスポンスの本文

このメソッドが成功すると、次の構造を含むレスポンスの本文が返されます。

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

saveUri は、エンドユーザーが JWT で識別されたオブジェクトを自分の Google アカウントに保存するための URI です。この URI は、返された時点から 1 週間のみ有効です。

詳細については、JWT エンドポイントのリファレンスをご覧ください。

フロー図

フロー図については、典型的な API フローをご覧ください。