円建で XEM 払いできる QR コードを作ってみる


nem Advent Calendar 2018、11日目を担当します はまの@hmn と申します。
今回は、以前 Twitter でつぶやいた内容を検証してみたいと思います。

NEM に詳しい方であれば、このツイートだけで何がやりたいのかすぐ分かると思いますが、せっかくの Advent Calendar ですので、順を追って説明していきたいと思います。

はじめに

XEM を送金する際、いちいちアドレスや送金額を手打ちするのは現実的ではありません。その為、多くの場合、支払い情報を QR コードに埋め込んで、それをウォレットアプリのカメラで読み込むことになると思います。

参考: NEM決済用QRコードの作り方 | ALIS

この QR コードの仕様は、公式の iOS/Android 向けウォレット NEMWallet で採用されており、気鋭のウォレット RaccoonWallet でも利用可能なため、今後とも標準的に使われていくことになると思います。

しかし当然ながら、ここで指定できる amount (送金額)は、XEM 単位となります。仮想通貨の相場は大きく変動することがあるため、ある時点の XEM/JPY の相場で計算した XEM の量が、別の時点では大きく異なることがあるわけです。

その為、店舗などのサービス提供者が安定した価格を請求したいなら、QR コードを動的に表示(PC で周期的に計算してモニタに表示するなど)する必要があり、静的な QR コードを印刷した紙のメニューを支払いに利用するケースは、なかなか普及していません。

また、動的にコロコロ変化する QR コードを読み込んで支払いをするのは、ユーザには抵抗感があります。私も物販で参加した 5月に開催された NEMFest では、それが理由で動的な QR コードは NG というルールがありました。

その為、物販前日の相場で計算したもの、プラスマイナス 5 円で計算したもの、計 3 種類を印刷し、現地で付け替えて運用するという方法を採りました。(こうした細かなところにもユーザへの配慮が見られ、素晴らしいイベントだったと思います)

そうした状況の中、RaccoonWallet が Deeplink に対応しました。Deeplink とは、アプリから別アプリを呼び出す機能のことで、Web を見てる時に [GoogleMap で開きますか?] や [食べログで開きますか?] などが表示され、そのアプリが開くお馴染みの機能です。これにより、大きく可能性が広がりました。

Web ページ上のリンクから RaccoonWallet を呼び出せるということは、何かしらの処理を Web にやらせてから、送金画面に遷移することができます。円から XEM への変換が、このタイミングでできればベストです。

また一方で、あまり知られてないですが iOS 11 から標準カメラで QR コードの読み取りが可能になりました。JSON などで構造化する必要はなく、http://〜 から始まる文字列を QR コードにエンコードし、それを標準カメラで読み込むことで Safari でその URL を開くことができます。

参考: 【iOS11】標準カメラアプリでQRコードを読み取る方法 - iPhone Mania

Android については、標準カメラアプリがメーカーごとの実装になるため、QR コードの読み取り可能かは機種によるようです。(日本製の機種では、読み取り可能なことが多いようです)

これにより準備が整いました。

  • 日本円をパラメータに含めた Web サイトの URL を QR コードにする
  • Web サイトで 日本円から XEM に変換する
  • RaccoonWallet を呼び出す

これで1つの静的な QR コードから、相場に追従した動的な価格を請求することができます。

API サーバの作成

まずは API サーバを作成します。

今回は AWS の APIGateway と Lambda で構築したいと思います。
ほとんど下記サイトと同様の手順ですので、詳しくはそちらをご覧ください。ここでは要所のみ記したいと思います。

参考: 初めてのサーバーレスアプリケーション開発 ~API GatewayからLambdaを呼び出す~ | DevelopersIO

Lambda の作成

Lambda 関数で基本の処理部分を作っていきます。

Node.js 8.10 で XEMConverterFunction という名前で一から関数を作成します。

適当な名前でテストイベントの JSON を作成します。address は適宜変更して使用してください。

{
  "address": "Nxxxx",
  "jpy": 500,
  "message": "thisismessage",
  "name": "thisisname"
}

インラインでコードを書いていきます。


const API_URL = "https://api.zaif.jp/api/1/ticker/xem_jpy";

exports.handler = (event, context) => {

    const https = require("https");

    // zaif の API から、相場情報を取得
    https.get(API_URL, (res) => {

        var body = "";
        res.on("data", (chunk) => {
            body += chunk;
        });

        res.on("end", () => {
            var json = JSON.parse(body);
            // 日本円に変換
            var xem = event.jpy / json.last;
            var microXem = parseInt(xem * 1000000);

            // RaccoonWallet の Deeplink 文字列を作成
            var query = escape("https://raccoonwallet.com/payment"
                        + "?addr=" + event.address
                        + "&amount=" + microXem
                        + "&msg=" + event.message
                        + "&name=" + event.name);
            var url = "https://raccoonwallet.page.link/"
                        + "?link=" + query
                        + "&apn=wacode.yamada.yuki.nempaymentapp";
            context.done(null, {"url": url});
        });

    });

};

あくまで検証用のコードなので、例外処理や実数の取り扱いが適当なのでご注意ください。
Tips: コードを編集した後は [保存] を押下しないと、テストに反映されません。

実行すると以下のような出力を得ます。

{
  "url": "https://raccoonwallet.page.link/?link=https%3A//raccoonwallet.com/payment%3Faddr%3DNxxxx%26amount%3D59869484%26msg%3Dthisismessage%26name%3Dthisisname&apn=wacode.yamada.yuki.nempaymentapp"
}

入力 JSON の jpy を、そのタイミングの zaif の相場情報で XEM に変換し、出力として RaccoonWallet の Deeplink 文字列が得られます。これをブラウザで開くことで、RaccoonWallet を開くことができます。
注意: iOS では、この url をブラウザに 直打ちした場合は Deeplink が開きません。

APIGateway の作成

先ほど作成した Lambda 関数を、APIGateway に設定し、API として呼び出せるようにします。

XEMConverterAPI という名前で API を作成します。

次に新しいリソースを作成します。
リソース名: XEMConverter-Resource
リソースパス: xemconverter

このリソースに GET メソッドを追加し、先ほど作成した XEMConverterFunction を設定します。

メソッドリクエストの設定

URL クエリ文字列パラメータを以下のように設定します。

名前 必須 キャッシュ
address YES
jpy YES
message
name

これにより、このエンドポイントに対して /xemconverter?address=Nxxx&jpy=500&message=thisismessage&name=thisisname のようにクエリパラメータを与えることができます。

統合リクエストの設定

[マッピングテンプレート]、[マッピングテンプレートの追加] から application/json を指定します。[パススルー動作の変更] について聞かれますので、[はい、この統合を保護します] を選択します。

するとページ下部にフォームが表示されますので、以下を入力します。

{
    "address" : "$input.params('address')",
    "jpy" : $input.params('jpy'),
    "message" : "$input.params('message')",
    "name" : "$input.params('name')"
}

これにより、クエリパラメータで取得した値を JSON の形にマッピングした上で、Lambda に渡すことができます。

メソッドレスポンスの設定

まず、既存の 200 のレスポンスを削除します。

[レスポンスの追加] から 303 を入力します。303 は See Other を意味するステータスコードなので、ブラウザは Location ヘッダに指定されたリソースへリダイレクトしてくれます。

[ヘッダーの追加] から Locaion を入力します。

これにより、このエンドポイントのレスポンスヘッダに Location が付与されます。

統合レスポンスの設定

こちらも、既存の 200 のレスポンスを削除します。

[統合レスポンスの追加] から [メソッドレスポンスのステータス] で 303 を選択し、保存します。

[ヘッダーのマッピング] を開き [マッピングの値] を次のように入力します。

integration.response.body.url

これにより、Lambda の出力を Location ヘッダの値にマッピングしてくれます。

リソースのデプロイ

リソース画面に戻り [アクション] から [API のデプロイ] を選択します。ここでステージングを設定できますので、production なり development なり、適宜指定してください。

これにより、以下のような URL が割り振られます。

https://97weebxiof.execute-api.ap-northeast-1.amazonaws.com/production

このエンドポイントが、今回の記事の成果物になります。実際に運用する場合は、ドメインを振った方が良いでしょう。

Tips: APIGateway の設定を変えた後は、再度 [API のデプロイ] をしないと反映されません。
Tips: APIGateway の設定値の input はクセがあって、Enter では反映されず、わざわざチェックのアイコンを押下しないと反映されません。

動作検証

一通り作業が完了したので、以下のような URL を叩けば動作が検証できるはずです。

https://97weebxiof.execute-api.ap-northeast-1.amazonaws.com/production/xemconverter?address=Nxxx&message=aiueo&jpy=1000&name=abcde

しかし先述した通り、アドレスを直打ちしても Deeplink は起動しません。

ですので Google Chart API を使って QR コードを作成します。

参考: Google Chart APIを使ってQRコードを作る:Tech TIPS - @IT

アドレスがダミーの Nxxx となっているので、実際に送金しないで下さい。 QRコードを読み込むのは問題ありません。

時間をおいて何度か試してみると、RaccoonWallet で表示される金額(単位: XEM)が変わっていることが分かります。これこそまさに、最新の相場を取得して XEM に変換している証です。

これでタイトルの『円建で XEM 払いできる QR コード』の完成です。

デモ

今回作成したものを試せるようにデモサイトを用意しました。それぞれの項目を入力して [QR コード作成] を押下すると、QR コードが表示されます。実際に送金に用いる場合は、くれぐれも自己責任でお願いいたします。

少なくとも今月いっぱい(2018年12月)は公開しておこうと思いますので、ぜひ試してみてください。ただ何百万回もリクエストするのはやめてくださいしんでしまいます^o^

改良案

今回は検証のために簡単な構成をとったので、実際に運用する上で必要そうな改良案を挙げておきます。

・QRコード読み込み後、確認画面を表示する
現在は、すぐに RaccoonWallet に遷移するため Safari の白い画面が一瞬表示がされます。この挙動が怪しみがあるので、計算に使用した相場や計算式を表示し、[RaccoonWallet で開く] といったリンクを用意してあげたほうが親切かもしれません。それも confirm=true のように設定できるとさらに良さそうです。

・リクエストの度に zaif の API を叩いている
大量のリクエストが予想される場合や、レスポンス速度がシビアである場合は、毎度 zaif に問い合わせるのではなく、DynamoDB など自前で用意した DB から値を取得するのが良いでしょう。そのためには、別途 zaif の API をスケジュールで叩く処理が必要になります。

おわりに

今回の記事の内容は RaccoonWallet が Deeplink に対応したことから始まりました。NEM を使ったサービス開発者にとって、とても素晴らしい機能であり、アイデア次第でもっと色々なことができると思っています。私が開発しているサービス ガチャもぐ でも、Deeplink のおかげでユーザビリティが格段に向上しました。

何かアイデアがあれば、ぜひコメント欄やツイッターに書き込みよろしくお願いします!