現在も試行錯誤中ですが、ある程度理解できてきたので備忘録も兼ねて掲載します。
私の環境は、ionicです。ionicには、ngcordovaというプラグイン郡がありますが、課金に関してはngcordovaに存在しないので、cordovaで動くものを使います。
使用するプラグインはj3k0さんのcordova-plugin-purchaseです。
基本的な導入方法については、以前記事を書いていたのでCordova 5 + cordova-plugin-purchaseの記事を参考にして下さい。
課金の種類
Androidで使える課金アイテムの種類は次のとおりです。
ios版も同じです
Non-consumable :
非消費型アイテム。一度購入したら永続的に購入者のものになります。
例えばレースゲームでおまけコースを利用可能にするなど
consumable :
消費型アイテム。使う度に無くなるアイテムです。
今流行のガチャチケみたいなのはこれに分類されます。
subscription:
今回のメインテーマ。毎月、毎年など、周期を決めて課金されます。
携帯の電話料金(定額)みたいなイメージ
まずsubscriptionですが、購読という意味です。
月額定額制で、毎月や毎年など、サイクルを決めて、周期的に課金を実行してくれる仕組みです。
売り切りであるアプリ内課金(Non-ConsumableやConsumable)と異なり、毎周期毎に収益が発生します。
subscriptionの処理は、結構日本語で解説しているサイトが少ないので、ちょっとトラブルになると英語のサイトをさまよう事になります
subscription処理(Android)
ハイブリッドアプリなのでiOSでも同じようにできるはずですが、まだ試していないのでAndroid限定ということにします。
課金処理の仕組みについてはAndroidのサイトを見てください。ここでは触れません。
使用するプラグインの説明書きが書かれています(英語)
10回くらい読み返して、試行錯誤しながらやっと想定した動きになってきた。語学という壁は厄介です。
cordova-plugin-purchaseで月額課金
Step1 商品の登録
consumableやnon-consumableも同じですが、商品の登録が必要です。
Google play developer consoleから追加して下さい。
Step2 商品の登録(src)
ここから本番です。
Step1で追加した商品を、JavaScriptに追記していきます
// デバイスの準備が出来てからじゃないとだめなので、必ず$ionicPlatform.ready()の中で書きます $ionicPlatform.ready(function() { // アプリ内課金 store.register({ id: "hogehoge", // Step1で追加したidを指定。jp.xxxといった頭文字は不要です。 alias: "mysubscription", //別名をつけることもできる。今回は使いませんので好きにどうぞ type: store.PAID_SUBSCRIPTION //月額定額制の場合は、←のように記載します。 }); store.ready(); //準備完了 store.refresh(); })
これ以降$ionicPlatform.readyは省略しますが、
以下のソースは全て$ionicPlatform.redayの中に書いて下さい。
Step3 課金処理のウインドウ表示
$scope.subscription = function(){ store.order("hogehoge"); }
ボタンとかにイベント紐付ければよし。課金ボタンを押したら、store.orderが呼ばれます。カッコ内の引数には、Step2でかいたidを指定します。
Step4 課金処理開始直後の処理 approved
ここからsubscription独特の処理が入ってきます。
store.when("hogehoge").approved(function(product) { //alert(JSON.stringify(product)); //例えば、receiptの中身を確認したいときは左記のように書くとよい product.verify(); //この処理は必須。receiptが本物か確認する処理。詳しくはstep5で });
課金処理に成功すると、store.when().approvedが実行されます。
この処理は購入完了後と、アプリの起動直後に呼ばれます。
課金には、receiptという購入情報が記載されますが、cordovaではproductという引数がそれを表しています。
Step5 store.validatorでチェックする
store.validate この処理が一番難しかった。解読にまる2日くらい使ったんじゃなかろうか。
store.validateは、receiptが本物かどうかを確認する処理です。
receiptはユーザ(スマホ)の中に保存されているので、改ざんできてしまいます。なのでreceiptを自分のサーバに転送させて、そこで認証が正しいかどうか、チェックする流れになります。チェックの結果をサーバから受け取り、正しければ
store.verified処理が実行され、証明書が不正やチェック失敗であれば
store.unverified処理が実行されます。
※store.verifiedは次のステップで紹介
とりあえず面倒なら次の通りにかけばOKです
store.validator = function(product, callback) { callback(true, product); }
store.validatorの処理を通さないと、次のstore.verifiedが呼び出されません。なのでとりあえずチェックはスルーして次のstore.verifiedを呼びたい時には、callback(true , product)とかくだけでも良いのです。
Step5-1 store.validatorで真面目にチェックする
ちゃんとreceiptをチェックしたいときは、次のようになります。
スマホのreceiptをサーバに転送する。
サーバで、receiptの整合性をチェックする。
サーバで結果を返す。(1なら成功 0なら失敗)
スマホでサーバの結果を元に処理を分岐する
スマホのreceiptをサーバに転送する方法ですが、次のようになります
store.validator = function(product, callback) { $http({ method : 'POST', url:url+'validate.php', data: product }).success(function(data, status, headers, config) { if(data['res'] == "1"){ alert("認証OK..."); callback(true, product); }else{ //認証失敗時は、何度か自動でリトライするらしい alert("認証失敗"); callback(false , "Impossible to proceed with validation"); } }).error(function(data, status, headers, config) { callback(false , "Impossible to proceed with validation"); }); };
まぁ特に特別なことはやってないです。
サーバー側はPHPの例ですが、次の通り
<?php header("Access-Control-Allow-Origin: *"); header('Content-type:application/json; charset=UTF-8'); @$data = json_decode(file_get_contents('php://input'),true); $res = array("res"=>0); //証明の確認書類準備 $signed_data = $data['transaction']['receipt']; $signature = $data['transaction']['signature']; $public_key_base64 = "MIIB******"; //MIIBで始まる長い文字列。google developer consoleからどうぞ $key = "-----BEGIN PUBLIC KEY-----\n".chunk_split($public_key_base64, 64,"\n").'-----END PUBLIC KEY-----'; $key = openssl_get_publickey($key); $signature = base64_decode($signature); $result = openssl_verify($signed_data, $signature, $key); if (0 === $result) { $res = array("res"=>0); }else if (1 !== $result){ $res = array("res"=>0); }else{ $res = array("res"=>1); } die(json_encode($res)); ?>
暗号の知識はあまりないのでよくわからないのですが、このように記述すれば動きました。
参考にさせていただいたサイト1
参考にさせていただいたサイト2
Step 6 verified / unverified
store.validatorの処理後に、callbackでtrueを返すと
store.validateが実行されます。逆にcallbackでfalseを返すと
store.unvalidateが実行されます。
store.when("hogehoge").unverified(function(p) { //store.validatorで callback(false)時に実行される。認証の失敗 alert("認証に失敗しました"); //認証に失敗した時の処理を書く }); store.when("hogehoge").verified(function(product) { product.finish();// storeの購入情報をfinishedにする });
Step7 update処理
Storeの情報が変わると実行されるstore.updatedが用意されているので、こちらを使います。
注意:store.updatedは変わった時以外にも、アプリ起動直後にも何度か呼ばれます。
購入情報が課金済みになっていれば、ロックを解除する などの処理を書く
store.when("hogehoge").updated(function(product) { if(product.owned){ //課金が継続されています }else{ //課金が確認されていません } });
アプリ起動直後はproduct.ownedがfalseの状態です。
その後approvedやvalidateやらを経て、store.finishが実行されるとownedがtureに変わるようです。
この処理(store.updated)はアプリ起動直後にも実行されるので、最初は無課金、のちに課金ユーザ という流れになるようです。
おまけ 期限切れ expired
最初すごく混同して考えていたのですが、
「アプリの継続課金をやめて、期限が切れる」ときと
「アプリの継続課金を継続しているが、支払に失敗して期限が切れる」とき
違うみたいですね(当然といえば当然ですが)
「アプリの継続課金をやめて、期限が切れる」ときは、
approvedが呼ばれなくなるみたいです。実際に期限切れを起こすには、デバッグ用端末で課金すると期限が1日になるので、これで確認するしかないようです。しかし、updatedは呼ばれるみたい。なので課金を脱退した人の権限を剥奪するには、updatedでやらないとだめみたいですね。
(unverifiedではダメ。unverifiedはapproved>validator>失敗時に呼ばれるが、approved自体が動かないので)
最後の
「アプリの継続課金を継続しているが、支払に失敗して期限が切れる」とき
ですが、確認が出来ません。ただ、おそらくですが、マニュアルによると
store.when("hogehoge").expired(function(p){
という命令があるようなので、おそらくこれではないのかなぁと。
公式マニュアルのstore.validateはこちら