前回の記事では、Firebase についての前提知識とユーザー識別・管理を行いました。
今回の記事では、やっとこさレシート検証を実装します。
が、もう一つ記事またぎます。笑(レシート検証は終わります)
今回は前より盛りだくさんのくせに、これ終わってやっと頂上って感じなんで、ゆるーくやっていきまそう。
開発環境(前提条件)
前記事からの引用みたいなもんですが、一応記載。
・macOS BigSur
・Homebrew
・Xcode12.3
・Swift5
・Node.js
・課金実装や Firebase 連携は済んでいるものとします
・対象プロジェクトの Firebase 料金プランを Blaze に変更しているものとします
Cloud Functions でレシート検証を行う
Cloud Functions は、関数を作成してバックエンドで実行できるサービスで、Firebase Authentication などの Firebase の各種サービスで生成されたイベントにも応答できます。
悪意丸出しユーザーから完全に隔離されるので、安全に動作するってわけです。
レシート検証に最適。
最初の記事で設定した Node.js や firebase-tools を使って構築していきます。
と、公式でアナウンスされているので、定期的に以下のコマンドを実行しておきましょう。
npm install firebase-functions@latest firebase-admin@latest –save
npm install -g firebase-tools
プロジェクトの初期化
Functions を使えるようにするために、ターミナルで以下を実行します。
最初の質問で、JavaScript か TypeScript のどちらかを選べますが、ここでは JavaScript を選択します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
% mkdir YourProject & $_ % firebase init functions //----- 以下のような質問をされるので、適宜答えてください -----// ? What language would you like to use to write Cloud Functions? // 私は JavaScript を選択 ? Do you want to use ESLint to catch probable bugs and enforce style? . . . ? Do you want to install dependencies with npm now? //----- 終わって作業が完了したら、以下のコメントが出ます -----// Firebase initialization complete! |
作成した YourProject フォルダに、以下のようなファイルが作られていたら成功です。
※ TypeScript を選択した方は違う構成になってます
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
YourProject +- .firebaserc # Hidden file that helps you quickly switch between | # projects with `firebase use` | +- firebase.json # Describes properties for your project | +- functions/ # Directory containing all your functions code | +- .eslintrc.json # Optional file containing rules for JavaScript linting. | +- package.json # npm package file describing your Cloud Functions code | +- index.js # main source file for your Cloud Functions code | +- node_modules/ # directory where your dependencies (declared in # package.json) are installed |
レシート検証のコードを実装
お好きなエディタで YourProject/functions/index.js を開いて、そこにコードを記述していきます。
ちなみに私は Node.js 初心者同然なので、経験者のみなさま、ご教授いただけると幸いです ( ˃̶⺫˂̶。)
(ラビリンス入りしているのが、axios で通信処理を記述すると必ずデプロイできまっせーんw)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
const functions = require("firebase-functions"); const https = require("https"); const RECEIPT_HOST = "buy.itunes.apple.com"; const RECEIPT_SANDBOX_HOST = "sandbox.itunes.apple.com"; const VERIFY_RECEIPT_PATH = "/verifyReceipt"; exports.verifyReceipt = functions.https.onCall(async (data, context) => { let result = {}; const productId = data.product_id; const receiptString = JSON.stringify(data); // ★1 await post(RECEIPT_HOST, VERIFY_RECEIPT_PATH, receiptString).then(async (body) => { // ★2 // ★4 }).catch((error) => { }); if (result.status == 21007) { // ★3 await post(RECEIPT_SANDBOX_HOST, VERIFY_RECEIPT_PATH, receiptString).then(async (sandboxBody) => { // ★4 }).catch((error) => { }); } return result; }) //--------------- ★4では検証必須です ------------------// // body.receipt.bundle_id でアプリのバンドルを比較したり、 // body.receipt.in_app で購入内容の確認ができるので、 // それぞれのアプリの仕様に合わせて、必ず検証してください。 //--------------------------------------------------// |
まず、exports.verifyReceipt の verifyReceipt が呼び出す関数名となります。
後々アプリ側からも呼び出すので、わかりやすい名前にしましょう。
★1:data にはレシート情報が Dictionary で入っており、それを String に変換しています。内容は後述。
★2:まずは本番環境でレシート検証します。
★3:レスポンスのステータスが 21007 の場合は、開発用のレシートということを示している。
★4:最後のコメントにありますが、検証必須です!
続いて、Apple のサーバーと通信する post() を記述します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
function post(host, path, verificationString) { return new Promise((resolve, reject) => { // ★6 const options = { host: host, path: path, headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(verificationString), }, method: "POST", json: true, }; const request = https.request(options, (response) => { const body = []; response.on("data", (chunk) => { body.push(chunk); // ★7 }).on("end", () => { resolve(JSON.parse(Buffer.concat(body))); // ★8 }).on("error", (error) => { reject(error); }); }).on("error", (error) => { reject(error); }); request.write(verificationString); request.end(); }); } |
★6:Promise を使ってレスポンスがあるまで待ち。Swift でいうとこのクロージャ的な感覚。
★7:リクエスト中のデータを配列に追加していきます。
★8:リクエスト完了したら JSON 形式に変換して resolve に返します。エラーの際は reject です。
ざっくりですが、こんな感じで AppStore へのレシート検証ができます。
細かい説明をすると膨大な量になるので省きますが、いつか別記事にできるといいなぁ(しない可能性が高いやーつ)
レシート検証をテスト
テストの準備
上記で作ったコードをテストしていきますが、Xcode でいう ⌘ + R 的なノリでは出来ません。笑
先ほどまで作業していた index.js が置かれているフォルダでテストするので、ターミナルで以下を行います。
1 2 3 4 5 6 7 8 9 10 11 12 |
% cd YourProject/functions % npm install --save firebase-functions@latest % npm install -g firebase-tools % firebase functions:shell //----- 以下のような対話型シェルが起動 -----// firebase > |
上記のような対話型シェルが起動できたら準備完了です。
テストを実行
先ほど作成した関数 exports.verifyReceipt を呼び出しますが、テストとは言ってもレシート情報(レシートをBase64エンコードしたデータ)も送らないといけないので、アプリ側で準備してください。
準備できたら、以下のようにしてデータを渡します。
1 |
firebase > verifyReceipt({"receipt-data": "eyJzaWduYXR1cmUiID0gIkFoaHEw..."}) |
ここでは receipt-data のみ記載しましたが、公式ドキュメントを見て、アプリの仕様に合わせて変更してください。
とりあえず上記を実行したら何かしらのデータが返ってくると思います。(成功したら以下のようなデータかと)
1 2 3 4 5 6 7 8 9 10 11 12 13 |
RESPONSE RECEIVED FROM FUNCTION: 200, { "result": { "receipt": { "original_purchase_date_pst": "2020-1-1 09:45:00 America/Los_Angeles", "purchase_date_ms": "1448296347138", // ... // ... // ... "original_purchase_date_ms": "1448296347138" }, "status": 0 } } |
“receipt-data” を消してみたりして、いろいろと試してみてください。
ちゃんと意図した通りのレスポンスがあれば問題ないです。問題あっても気合いで解決しましょう(テキトゥー)
デプロイする
ここまででローカルでのレシート検証はできたので、次はサーバーでレシート検証できるようにしましょう。
引き続き、ターミナルでの作業となりますが、コマンド自体はシンプル野郎です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// ローカル検証の時にいた YourProject/functions/ ではなく、一つ上のフォルダに戻ります % cd ../ % firebase deploy --only "functions:verifyReceipt" //---------- 最後の1行が出たらOKです ----------// deploying functions functions: Finished running predeploy script. functions: ensuring required API cloudfunctions.googleapis.com is enabled... // ... // ... // ... Deploy complete! |
数分かかりますので、気長に待ちましょう。
完了したら、ブラウザで Firebase を開いて対象プロジェクトに移動し、左のメニューから Functions を選択してください。
下のような関数が表示されてたらオケです。
そして検証…といきたいのですが、アクセス権限とかもいじる必要があって歯切れが悪いので、今回は終わりぜよ ( ˘ω˘ )
さいごに
記事にすると短く感じる…ここに辿り着くまで結構かかった感あるのに。笑
次の記事では、アプリ側からの実行やらアクセス権限やら書き込みやらやります。
コメント