この記事、本当は Firebase Advent Calendar 2017 に投稿しようとしたものです。
直前になってコードを書き換えたくなってコードに修正を入れてたら間に合わなくなり、「 GoogleAppEngineとFirestoreの機能を利用してGolangとかVue.jsで動く2chみたいな掲示板を作ってGitLabCIで自動デプロイさせて一人DevOptsしてみた 」を投稿しました。が、多分こちらのほうがFirebase色が強いと思います。
はじめに
画面上部にもバナーをつけているのですが、iPhoneでGrafanaのグラフを閲覧できるアプリを作ってます。
AppStoreで公開したりプロダクトページのドメイン代など、アプリを公開するというのはそれなりにお金がかかります。趣味で作ってるので儲けとかまでは全然考えていないのですが、維持費については少しでも回収できたら良いなと思ってアプリ内課金を採用しています。殆ど売れないのですが、おかげさまで2年目にしてようやく回収の見込みがたってきました。
そんなかわいい状況なのですが、数日に一回くらいのペースで売上げがあるとなんとなく嬉しいもので、以前はログインしてからの動線が長いAppStoreの売上げをこまめにチェックしてました。殆ど売れてないのですがw(しつこい
売上に関しては、AppleのiTunesConnectよりもGoogleのFirebaseで確認するほうが楽です。Dashboard画面で広告も含めた収益を確認出来ます。
(しかしほとんど売れて(ry
でも、iTunesConnectよりFirebaseのほうが楽になったと言ってもやっぱり面倒なものは面倒なので、どうにか自動化させたいと常々考えていました。
そんな時にFirebaseのFunctionsを使えばアプリ内課金の購入をトリガに出来ることがわかったので、アプリが売れる度に手元のiPhoneに通知を飛ばす方法を検討してみました。
仕様
今回は仕様として以下の二点を考えてみました。
- 課金のタイミング
in_app_purchase
と新規ユーザーの使い始めfirst_open
のログをSpreadsheetに記録する - 課金のタイミングでIFTTTのWebhookを利用して日本円に変換した金額を通知する
通知だけあれば良いのですが、売れた履歴とか新規ユーザーの購入割合とかを後からグラフ化できたらもっとニヤニヤ出来るだろうなと思って、Spreadsheetへの記録も追加しています。
どちらについてもFirebaseFunctionsだけで開発出来ます。しかしSpreadsheetに追加するところでNodeJSなAPIを使うとOAuthなどで面倒なことになるのでHTTP経由にして、GASにて書き込むようにしています。
この方法はURLさえわかれば誰でも書き込めるようになるので、気になる人は↓のURLを参考にしてAPI経由で書き換えてみてください。僕もこの通り実装して一時期動かしてましたが、ソースが長くなって見通しが悪くなったので、今回紹介している認証なしでシンプルな方式を採用しました(ここを書き換えてたら締切日まで間に合わなかった...)。まあ大丈夫でしょう。
実装
上記の仕様では、Functions側とAppScript側の2つにコードが必要になります。また、IFTTTにも少し設定が必要になります。
Functions
利用する前に2つのURLを書き換えて利用してください。
見ての通り、円ドルの為替レートを取得してSpreadsheetに書き込み、必要に応じてIFTTTへWebhookしてるだけです。
気になる場所といえば、FunctionsはPromiseベースで実装する必要があるという点でしょうか?
あまりPromiseのことを詳しくわからなかったので、直列処理の時に値を引きずり回すところで少し躓いちゃいました。
今回は request-promise
を使って Options.Transform
で値を再利用を引きずり回すようにして対応したのですが、こういうもんなんでしょうかね?
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const request = require('request-promise')
admin.initializeApp(functions.config().firebase);
const kawaseURL = "http://api.aoikujira.com/kawase/get.php?format=json&code=usd&to=JPY";
const spreadURL = "https://script.google.com/macros/s/XXXXXXXXXXXXXXXXXXXXX/exec"; // 書き込むシートのURL
const iftttURL = "https://maker.ifttt.com/trigger/XXXXXXXXXXXXX/with/key/XXXXXXXXXXXXX"; // WebhookのURL
exports.writePurchase = functions.analytics.event("in_app_purchase").onLog(event => {
var params = {
"dataName": event.data.name,
"purchaseValue": event.data.valueInUSD,
"country": event.data.user.geoInfo.country
};
return Promise.resolve(params)
.then(toJPY)
.then(write)
.then(notify);
});
exports.writeFirstOpen = functions.analytics.event("first_open").onLog(event => {
var params = {
"dataName": event.data.name,
"purchaseValue": 0,
"country": event.data.user.geoInfo.country
};
return Promise.resolve(params)
.then(toJPY)
.then(write);
});
exports.testNotify = functions.https.onRequest((req, res) => {
var params = {
"dataName": "test",
"purchaseValue": 4.140365,
"country": "Japan"
};
Promise.resolve(params)
.then(toJPY)
.then(write)
.then(notify);
res.status(200).send("notify");
});
function toJPY(params) {
return new Promise((resolve, reject) => {
var options = {
uri: kawaseURL,
transform: function (_body) {
let body = JSON.parse(_body);
params.rate = body["JPY"];
params.jpy = params.purchaseValue * body["JPY"];
return params;
}
};
request(options)
.then(resolve);
});
}
function write(params, cb) {
return new Promise((resolve, reject) => {
var options = {
uri: spreadURL + "?" + Object.keys(params).map(d => {
return d + "=" + params[d];
}).join("&"),
transform: function (body) {
return params;
}
};
request(options)
.then(resolve);
});
}
function notify(params) {
return new Promise((resolve, reject) => {
const options = {
method: "POST",
uri: iftttURL,
json: {
value1: "💰 Grafanizerで " + Math.floor(params.jpy) + "円 の売上げがあったよ! 💰"
},
transform: function (body) {
return params;
}
};
request(options)
.then(resolve);
});
}
AppScript
こちらも使う前にsheetIDを指定して利用してください。
こちらも特に難しいところはありません。Webhookがやってきたらタイトル行下に空行をインサートして、その行をやってきた値で埋めているだけです。
var sheetId = "XXXXXXXXXXXXXXXXXXXx";
function doGet(e) {
Logger.log(e.parameter);
var spread = SpreadsheetApp.openById(sheetId);
var sheet = spread.getSheets()[0];
sheet.insertRowAfter(1);
var row = new Row(sheet, 2);
row.val(1, new Date());
row.val(2, e.parameter.dataName);
row.val(3, e.parameter.purchaseValue);
row.val(4, e.parameter.country);
row.val(5, e.parameter.rate);
row.val(6, e.parameter.jpy);
return ContentService.createTextOutput(JSON.stringify(e));
}
Row = function(sheet, i) {
this.sheet = sheet;
this.row = i;
};
Row.prototype.val = function(col, value) {
if (value == undefined) {
return this.sheet.getRange(this.row, col).getValue();
} else {
this.sheet.getRange(this.row, col).setValue(value);
}
}
IFTTT
IFTTTでWebhookを受信する方法は 以前の記事 や、その他たくさんGoogle検索結果を参考にしてください。
結果
うまく動けば、売上がある度に下記画像のような通知が届きます☺
また、Spreadsheetの first_open*
の値を使ってこんな感じでグラフ化させることも出来るようになります。さすがドイツ人は仕事真面目ですね!
まとめ
- AppStoreの売上げもFirebaseで取得できる
- Firebaseをトリガにすれば売上げの度に処理を走らすことができる
- Spreadsheetにデータを貯めとけば自由にビジュアライズできる
- IFTTTを利用すればiPohne等にプッシュ通知を送ることが出来る
売上があったと言っても、Appleから30%ひかれるので利益表示にしたほうが現実味があるかもしれない。
「ウザいので通知を切った」という日が訪れるのを待ってる。