👛

Google Wallet API + Flutterでパスを追加する

2024/03/06に公開

Google Walletはいくつかの支払い機能を一つにまとめることができるシステムです。そのため、クレジットカードでの支払いや、航空券の搭乗券、ポイントカードの利用などもGoogleWalletにまとめることができ、該当のアプリを開くことなく決済を行えるようになります。

今回GoogleWalletを使ったアプリケーションを実装したので、概要と実装手順を記事にします。

GoogleWalletの概要

GoogleWalletにはパスという概念があります。これはWalletに追加できるカードのようなもので、我々開発者はこのパスを発行することで、自分のアプリにおける決済機能をGoogleWalletに追加できるようにできます。

パスはGoogleが標準で用意してくれている7種類のタイプと、独自にカスタムできる汎用パスタイプが利用できます。

※7種類のパス:ポイントカード、クーポン、ギフトカード、イベントチケット、乗車券、搭乗券、ワクチンカード

GoogleWalletで利用できる決済手段としてはNFCタップとバーコードスキャンが利用できます。
また、決済だけに限らず、会員のロイヤリティを示すカードとして利用するなど、汎用パスを用いることで、開発者の発想によってはいろいろなことができそうです。
※ 主にこの記事で対象とするのは汎用パスタイプとします。

GoogleWallet APIを使った開発について

開発者がやることは大きく分けて3つです。

  1. GoogleWalletの発行者アカウントを用意する。
  2. ジェネリッククラスを用意する。
  3. ジェネリックオブジェクトを作成するプログラムを実装する。

発行者アカウントの用意

まず重要な点として発行者アカウントが必要になります。とはいえ他のSaaSなどと同じような容量でアカウントは簡単に発行できます。
特に難しいところはなかったので下記の公式サイトを参考にアカウントを発行します。
GoogleWalletAPI発行者アカウントの設定方法について

ジェネリッククラスとジェネリックオブジェクト

次に重要なのがGoogleWalletのパスにはジェネリッククラスとジェネリックオブジェクトという概念が存在します。
オブジェクト指向プログラミングのクラスとインスタンスの関係に似ているのですが、ジェネリッククラスはそのパスの大枠を決めるクラスでジェネリックオブジェクトはパスの実態と考えれます。
ジェネリッククラスを1つ定義するとその大枠に従ったジェネリックオブジェクトは複数発行することができます。
ジェネリッククラスの定義はGoogleWallet APIを用いて行う方法とGoogleWalletのコンソールから定義する方法があります。

ジェネリックオブジェクトの作成

最後にジェネリックオブジェクトの作成なのですが、これはいくつかの方法があります。これは実際にユーザが触るアプリケーションにおいて発行するものなので、主にはAndroidのSDKを使って発行するか、バックエンドからGoogleWallet APIを叩くことで発行する方法があります。
今回私はFlutterでアプリを開発していたので、AndroidのSDKを組み込んだFlutterPluginを利用することにしました。
今回私が利用したのはflutter_google_walletというパッケージです。Likes数が若干心許ないですが、自分が利用する範囲では問題なく動きました。(と言いつつ、用意されてたGoogleWalletButtonといWidgetが使いにくかったのでWidgetは自分で書きました。)

ジェネリックオブジェクトを発行する際には簡単に3つの手順があります。

  1. 作成したいPassのパラメータを設定したJsonを作成する。
  2. GoogleWalletAPIの権限を付与したGoogleServiceAccountの秘密鍵で署名してJwtを作成する。
  3. Jwtをリクエストに乗せてパッケージのsavePassesJwtを呼ぶ。

ジェネリックオブジェクト作成の詳しい話は次の章でします。

ジェネリックオブジェクト作成の実装

上で述べた通り、ジェネリックオブジェクトを作成する際にはGoogleServiceAccountにGoogleWalletAPIの権限を付与して、そのアカウントの秘密鍵で署名してJwtを生成する必要があるので、今回はGoogleCloudRunにjavascriptのサーバーをデプロイしてそこでJwtに署名することにしました。

1. 作成したいPassのパラメータを設定したJsonを作成する。

設定できるパラメータがたくさんある&綺麗にまとめられているドキュメントが見つけられなかったので手探りで動かしながらどのパラメータを変えるとパスのどこに反映されるのか確認しながら開発していました。

今回はfreezedを使ってWalletPassクラスを定義しました。



class WalletPass with _$WalletPass {
  const factory WalletPass({
    ('3388000000022312255.codelab_object') String id,
    ('3388000000022312255.codelab_class') String classId,
    ('GENERIC_TYPE_UNSPECIFIED') String genericType,
    ('#4285f4') String hexBackgroundColor,
    (Logo()) Logo logo,
    (
      CardTitle(
        defaultValue: DefaultValue(value: 'Are you ready?'),
      ),
    )
    CardTitle cardTitle,
    (
      Subheader(
        defaultValue: DefaultValue(value: 'Save the earth!'),
      ),
    )
    Subheader subheader,
    (
      Header(
        defaultValue: DefaultValue(value: 'Global Gamers Challenge!!'),
      ),
    )
    Header header,
    Barcode? barcode,
    (HeroImage()) HeroImage heroImage,
    ([
      TextModulesData(header: 'POINTS', body: '1234', id: 'points'),
      TextModulesData(header: 'CONTACTS', body: '20', id: 'contacts'),
    ])
    List<TextModulesData> textModulesData,
  }) = _WalletPass;

  factory WalletPass.fromJson(Map<String, dynamic> json) =>
      _$WalletPassFromJson(json);
}

若干詰まったのが、WalletPathクラスのidが同じ場合一度取得したパスを削除して再度取得する際にキャッシュのように元のパスを参照してしまい、他の変更が反映されないという仕様があります。
そのため、今回はidはuuidを用いて毎回ランダムなidを発行して、発行するパスごとに別のパスとして利用することにしました!
参考ページ

2. JsonからJwtを生成する。

上で述べた通り今回はjavascriptでサーバサイドのコードを書いてGoogleCloudRunに上げました。
Jwtの生成周りはCodelabにあったので、それを参考してAPIで叩けるようにした感じです。

Codelabでは生成したJwtをアプリにそのまま埋め込んで使ってましたが、実際には動的にパスを生成したいのでAPIで叩けるようにGoogleCloudRunに上げた感じです。

Jwtを生成するためにはGoogleSercieAccountの秘密鍵を利用する必要があるので、SecretManagerを用いて秘密鍵を安全に取得できるようにしました。
ローカルで動かす時は.envファイルに保存した秘密鍵を直接読み込むようにしています。
https://github.com/miyasic/ggc/blob/main/backend/index.js

dockerファイルはGPT先生が出力してくれたものを雑に使っています。

3. Jwtをリクエストに乗せてパッケージのsavePassesJwtを呼ぶ。

これは至って簡単で、2で作成したAPIのレスポンスをそのままライブラリのsavePassesJwtメソッドに渡したらできました。

  final jwt = await googleWalletPassRepository.generateJwt(
    _createWalletPass().toJson(),
  );
  if (jwt != null) {
    await googleWallet.savePassesJwt(
      jsonPass: jwt,
      addToGoogleWalletRequestCode: 1000,
    );
  }

https://github.com/miyasic/ggc/blob/main/lib/presentation/component/google_wallet_button.dart

これを呼び出すことで下記のようにGoogleWalletアプリが開きパスを追加できるようになりました。

Jwtを生成するためのJsonのパラメータを色々と変えることで、Passの色や画像、テキストを差し替えることもできます。

まとめ

今回はGoogleWalletAPIを使ってGoogleWalletに汎用パスを追加する流れを記事にしてみました。
特にオブジェクトとパスの概念を理解してないと何から実装していいかつまづいたので、この記事を参考にGoogleWallet周りの実装が楽になる方がいれば幸いです。

今回はGlobal Gamers ChallengeというFlutterでゲームを開発するハッカソンに出すために作っていたゲームアプリのコンテンツとして汎用パスを追加することにしたのでリンクを貼っておきます。

https://globalgamers.devpost.com/

ハッカソンに出すアプリも公開できたのでAndroidユーザの方は触ってみてください。
https://play.google.com/store/apps/details?id=com.gmail.sepak.kou.ggc

おわりに

2024年は技術発信も頑張ろうと思っているので、記事が参考になった方は記事とGitHubのいいね(スター)とフォローをしていただけると励みになります!
よろしくお願いします!

https://github.com/miyasic/turtle-escape

Discussion