かばちんのエンジニアブログ

日々の経験の中で培った内容を備忘録も兼ねて記録していくブログです。少しでも誰かの役に立つために頑張って続けていけたらなと思います。

UnityIAP Android におけるコンビニ決済への対応方法

Google Play Billing Library 3.0 からコンビニ決済への対応が必須になっている

コンビニ決済(遅延決済)とは?

  • アプリから課金しようとした時にコンビニ支払いが選択可能になった
  • 支払いコードが発行され指定の手順でコンビニのレジにて支払い
  • 対応コンビニは今のところ次の3つ「ファミリーマート」「デイリーヤマザキ」「セイコーマート
  • レジでの支払い後一定時間(数分)待つと Google 側での決済が完了しアプリ側での購入処理が動く

結論、コンビニ決済への対応方法

課金処理が実装されているアプリには IStoreListener を継承しているクラスが存在するはずなので、
そのクラスに OnPurchaseDeferred メソッドを追加してイベントを処理する必要がある

// 遅延決済が開始されたことを通知
private readonly Subject<HogeHoge> pendingSubject = new Subject<HogeHoge>();
public IObservable<HogeHoge> OnPurchasePending => pendingSubject;

// 遅延決済が行われた通知を処理する
void OnPurchaseDeferred(Product product)
{
    // 支払いが保留中であることをアプリに伝える
    pendingSubject.OnNext(HogeHoge);
}

// UnityIAP 初期化時に IGooglePlayStoreExtensions を取得しハンドラを指定
public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
{
   〜〜〜省略〜〜〜

   // GooglePlayStore の拡張クラス
   m_GoogleExtensions = extensions.GetExtension<IGooglePlayStoreExtensions>();
   m_GoogleExtensions?.SetDeferredPurchaseListener(OnPurchaseDeferred);

   〜〜〜省略〜〜〜
}

■OnPurchaseDeferred が担えること

遅延決済が開始されたことの通知

コンビニ決済が開始されると通知されるがこの時点ではレシート情報が存在しないため、
基本的には遅延決済が開始されたという事実のみをこのメソッドでは扱うことが可能。


遅延決済が存在しなかった時には基本的に決済開始したら、即座に成功またはキャンセルを含め失敗が返って来るため、
このレスポンスを以て通信中の保護ダイアログを出していたりするケースが多いと思う。


しかし、遅延決済を行うとこの OnPurchaseDeferred が呼ばれてから、実際にコンビニ決済が完了するまでの
約数分間の間何もレスポンスがないため、その間ずっと通信中状態になってしまうなどの弊害が起きる。


さらに厄介な点が、支払い手続きを進めたあと GooglePlay 側でキャンセルを行なってしまったりすると、
それ以降 Google 側からの通知は一切無くなってしまう。


その為、OnPurchaseDeferred が実行された時には通信中状態を解除せざるを得ない。

通信中状態を解除した場合の弊害について

通信中状態を解除するとさらなる問題が浮上してくる。


今までは1つの課金アイテムを購入しようとした場合、成功か失敗するまでシーケンシャルに行えていたが、
OnPurchaseDeferred で通信状態を解除するということはアプリが通常通り操作可能な状態に戻るため、
支払いが保留状態のものがあるにも関わらず他の課金アイテムをさらに購入することが出来てしまう。


なので決済が完了した通知である ProcessPurchase メソッドが非同期に何個も同時に来る可能性がある。
その点に配慮して処理を実装する必要がある。

レシートに含まれる purchaseState に Pending (4) 状態が追加

遅延決済が実装されたことにより GooglePurchaseState に Pending が追加された。


実際の enum 定義を見てみよう。

public enum GooglePurchaseState
{
    Purchased,
    Cancelled,
    Refunded,
}


...はい。ありません。無いんです...。だけど Pending 状態を表す (4) が来ます。
なので ProcessPurchase 内で purchaseState をチェックする if 文はこう書くしかありません。

var validator = new CrossPlatformValidator(GooglePlayTangle.Data(), AppleTangle.Data(), Application.identifier);
try
{
    var result = validator.Validate(product.receipt);
    foreach (IPurchaseReceipt productReceipt in result)
    {
        var google = productReceipt as GooglePlayReceipt;
        if (google != null)
        {
            if ((int)google.purchaseState == 4) // ←ココ
            {
                return PurchaseProcessingResult.Pending;
            }
        }
    }
}
catch (IAPSecurityException)
{
    return PurchaseProcessingResult.Pending;
}

このチェックをしっかりしないと、まだ決済処理が完了していない購入情報が、
購入が完了したものとして処理されてしまい実際にアイテム付与処理が走ります。


実際にはおそらくサーバ側でレシート検証などをしていると思うので、
そこでレシートの検証に失敗して弾かれるとは思いますが、しっかりと対応しておくべきです。

purchaseState が Pending (4) 状態で来るケース

コンビニ支払いの手続きを開始して OnPurchaseDeferred が呼ばれた以降、
実際に支払っていようが支払っていまいがアプリを再起動したり、UnityIAP が初期化されるタイミングになると
ProcessPurchase が呼ばれて purchaseState が Pending (4) 状態のレシートが付与された状態で飛んでくる。

■まとめ

他にもいろいろと細かい問題があったような気がしますが、ひとまず覚えている範囲でメモとして残しました。


課金周りの実装はアプリごとに異なるため、OnPurchaseDeferred を実装してその通知をどう扱うかなどは、
それぞれのアプリの設計によるため、おそらくすべての課金部分開発者に関係するであろう部分のみを抜粋し、
今後コンビニ決済対応を行う方に役立つ情報となるように書いたつもりです。


コンビニ決済は日本とメキシコしか対応していないらしいです。
Google に申し立てをしてコンビニ決済を無効にしてもらっているアプリも多く見受けられます。


コンビニ決済への対応はかなり工数がかかるため、いっそのこと無効にしてしまったほうがコスパは良さそう


というわけで、誰かのお役に少しでもなれば幸いです。