in_app_purchase を用いたアプリ内課金について

アプリ内課金に使用しているパッケージ

https://pub.dev/packages/in_app_purchase

実装例

https://github.com/flutter/packages/blob/main/packages/in_app_purchase/in_app_purchase/example/lib/main.dart

Skimieでは、課金のメソッドをNotifierProviderで管理しています。

アプリ内課金を実装するためには、Google Play Console とApp Store Connect(Apple)の設定が必要です。

それぞれのプラットフォームの設定は、

Google :

https://developer.android.com/google/play/billing/getting-ready?hl=ja

https://www.revenuecat.com/docs/android-products
Apple:

https://www.revenuecat.com/docs/ios-products

アプリ内課金の流れ


  1. アプリ内購入が利用可能な状態なのか確認する

  2. 購入品の製品IDのSetを用意する。

  3. それぞれのストアに問い合わせて製品情報を取得する

  4. 上記で取得した製品情報を使って購入処理を行う

  5. 購入処理進行を監視して、購入が完了した後にバックエンドでレシートの検証を行う

  6. 検証が成功した場合、ジェムをユーザーに付与する(課金の対価を付与する)

  7. 返ってきた検証結果に応じて、通知を行う

  8. 購入のトランザクションを完了させる

NotifierProviderで持つ状態と上記の順番に沿って説明を進めます。

全体のコードを確認するには、/skimie/lib/services/app_purchase/app_purchase_provider.dart

を参照してください。

Stateとしては、下記の状態を定義しました。

@freezed
class AppPurchaseState with _$AppPurchaseState {
  const AppPurchaseState._();

  const factory AppPurchaseState({
    // 課金利用の可否
    @Default(false) bool isAvailable,

    // バックエンドから取得した課金商品情報(productIdや購入品Image)
    Items? purchaseItems,

    // AppleやGoogleのプラットフォームから取得した課金商品の情報
    @Default([]) List<ProductDetails> products,

    // 課金処理の状態
    @Default(false) bool purchasePending,
  }) = _AppPurchaseState;

  ProductDetails? getProductDetail(Item item) {
    final productId = item.code;
    return products.firstWhereOrNull((e) => e.id == productId);
  }
}


purchasePending 以外は、全てAPIから取得した値を定義しています。

getProductDetailsのメソッドは、OpenAPIで定義されたItemの型の中にある

製品IDに該当するproductDetailsをリストの中から取得するメソッドです。

単回購入処理のときに使います。

1. アプリ内購入が利用可能な状態なのか確認する

    // アプリ内購入が利用できるかを確認
    final isAvailable = await _inAppPurchase.isAvailable();

    // アプリ内購入が利用できない場合
    if (!isAvailable) {
      Log.e('アプリ内課金が利用できません。');
      state = state.copyWith(
        isAvailable: isAvailable,
      );
      return;
    }


ストアにアクセスできない、デバイスがGoogleにログインしていないなど、

利用不可の場合は、この先の処理には進むことはないです。

2. 購入品の製品IDのSetを用意する。

    // 課金商品の情報を取得
    final purchaseItems = await _purchaseService.getProductIds();
    final productIds = purchaseItems.items.map((p0) => p0.code).toSet();


バックエンドから、購入品のItemsのデータを取得し、製品IDのSetを作成します。

3. それぞれのストアに問い合わせて製品情報を取得する

    // 課金処理に使用する商品一覧を取得
    final response = await _inAppPurchase
        .queryProductDetails({...productIds, 'android.test.purchased'});


このAPIは、1. で作成した製品IDに該当する購入品の情報を Google Play Console や App Store Connect から List<ProductDetails> という型で取得することができます。

ProductDetailsからは、購入品のid や title、description、price などの情報が取得できます。

4. 上記で取得した製品情報を使って購入処理を行う

この処理はUI側で行われる処理です。

    // アプリ内購入品のリスト
    List<Widget> purchaseItemList = [];
    final purchaseItems = ref.watch(appPurchaseServiceProvider
            .select((s) => s.purchaseItems?.items.toList())) ??
        [];
    final isAvailable =
        ref.watch(appPurchaseServiceProvider.select((s) => s.isAvailable));

    for (final item in purchaseItems) {
      final product =
          ref.watch(appPurchaseServiceProvider).getProductDetail(item);
      if (product == null) continue;

      purchaseItemList.add(
        ActionBtn(
          label: "",
          disabled: !isAvailable,
          onPressed: () async {
            await ref.read(appPurchaseServiceProvider.notifier).buyProduct(
                  context,
                  product: product,
                );
          },
          color: Colors.amber,
          width: (width - marginValue * 2 - 80) / 3,
          height: (width - marginValue * 2 - 80) / 3,
          image: ProductImage(
            item: item,
            displayCount: product.title.getNumInTarget,
          ),
        ),
      );
    }


appPurchaseServiceProviderのStateから、purchaseItemsを取得し、for in で回します。

各item に該当するProductDetailsを取得して、notifierのbuyProductに渡します。

AppPurchaseServiceクラス内の単回購入のメソッド、

  // 単回購入
  Future<void> buyProduct(
    BuildContext context, {
    required ProductDetails product,
  }) async {
    _context = context;
    state = state.copyWith(purchasePending: true);

    final purchaseParam = PurchaseParam(productDetails: product);
    await _inAppPurchase.buyConsumable(
      purchaseParam: purchaseParam,
      autoConsume: _kAutoConsume,
    );
  }


各プラットフォームのアプリ内購入が実行されます。

※ autoConsume について、デフォルトがtrueになっており、

消耗型の購入は、購入と同時にトランザクションが閉じられます。

https://techblog.booklista.co.jp/entry/2023/09/29/130633#%E8%B3%BC%E5%85%A5%E5%87%A6%E7%90%86%E3%81%AE%E6%9B%B4%E6%96%B0%E3%82%92listen%E3%81%99%E3%82%8B:~:text=%E3%81%99%E3%81%B9%E3%81%8D%E7%82%B9-,Android,-%E3%81%A7%E3%81%AF%E8%B3%BC%E5%85%A5%E3%82%A2%E3%82%A4%E3%83%86%E3%83%A0

5. 購入処理進行を監視して、購入が完了した後にバックエンドでレシートの検証を行う

  /// 購入の処理を監視する
  Future<void> listenPurchaseUpdated() async {
    final Stream<List<PurchaseDetails>> purchaseUpdated =
        _inAppPurchase.purchaseStream;
    _subs = purchaseUpdated.listen(
      (List<PurchaseDetails> purchaseDetailsList) {
        // 購入などが行われた場合の処理
        _listenToPurchaseUpdated(purchaseDetailsList);
      },
      onError: (Object error) {
        Log.e('Error: ${error.toString()}');
        errorNotification(msg: _l10n.errorPurchase);
      },
    );
  }


Streamにて、購入が行われるとpurchaseDetailsList が自動で返される処理になります。

返されたpurchaseDetailsListを_listenToPurchaseUpdatedに渡して、検証する流れになります。

  // 購入処理が行われた場合にそのトランザクションを処理する
  void _listenToPurchaseUpdated(
    List<PurchaseDetails> purchaseDetailsList,
  ) {
    purchaseDetailsList.forEach(
      (PurchaseDetails purchaseDetails) async {
        if (purchaseDetails.status == PurchaseStatus.pending) {
          state = state.copyWith(purchasePending: true);
        } else {
          // 購入処理がエラーの場合
          if (purchaseDetails.status == PurchaseStatus.error) {
            Log.e('Error: ${purchaseDetails.error}');
            errorNotification(msg: _l10n.errorPurchase);
          }

          // 購入がキャンセルされた場合
          if (purchaseDetails.status == PurchaseStatus.canceled) {
            successNotification(msg: _l10n.canceledPurchase);
          }

          // 購入処理が完了、または復元された場合
          if (purchaseDetails.status == PurchaseStatus.purchased ||
              purchaseDetails.status == PurchaseStatus.restored) {
            // レシートの検証をする
            final receipt =
                purchaseDetails.verificationData.localVerificationData;

            final valid = await _purchaseService.postPurchaseReceipt(
              receipt: base64Encode(utf8.encode(json.encode(receipt))),
              itemMasterCode: purchaseDetails.productID,
            );

            if (valid) {
              // レシートの検証が成功時の処理
              successNotification();

              // 購入完了の処理
              if (purchaseDetails.pendingCompletePurchase) {
                await _inAppPurchase.completePurchase(purchaseDetails);
              }
            } else {
              // レシートの検証が失敗時の処理
              errorNotification(msg: _l10n.errorPurchase);
            }

            state = state.copyWith(purchasePending: false);
          }
        }
      },
    );
  }


受け取った、purchaseDetailsListは、forEachで回され、個別の状態に応じて処理をする。

purchaseDetails.status == PurchaseStatus.purchased


購入済みの状態であれば、

  // レシートの検証をする
  final receipt = purchaseDetails.verificationData.localVerificationData;


レシートを取得することができるので、バックエンドに渡して、レシート検証をしてもらう。

6. 検証が成功した場合、ジェムをユーザーに付与する(課金の対価を付与する)

バックエンドの処理。

7. 返ってきた検証結果に応じて、通知を行う

レシート検証から返ってきた値(valid)によって、成功、失敗の通知を行う。

8. 購入のトランザクションを完了させる

  // 購入完了の処理
    if (purchaseDetails.pendingCompletePurchase) {
       await _inAppPurchase.completePurchase(purchaseDetails);
      }


トランザクションを完了していないpurchaseDetailsは、完了の処理を行う。

バックエンドの処理が失敗した時の対策


https://techblog.booklista.co.jp/entry/2023/09/29/130633

こちらの記事で紹介されている方法で実装。

  // 未完了のレシートを検出して再検証する
  Future<void> reCheckPendingTransaction() async {
    // 未完了のレシートを格納するリスト
    final List<PurchaseDetails> pendingReceiptList = [];

    // iOSの場合
    if (Platform.isIOS) {
      final paymentQueueWrapper = SKPaymentQueueWrapper();
      final transactions = await paymentQueueWrapper.transactions();
      for (final transaction in transactions) {
        // TODO:transactionから未完了のPurchaseDetailsを取得する
      }
    }

    // Androidの場合
    if (Platform.isAndroid) {
      final androidAddition = _inAppPurchase
          .getPlatformAddition<InAppPurchaseAndroidPlatformAddition>();
      // 過去の購入履歴を取得
      final response = await androidAddition.queryPastPurchases();
      for (final purchaseDetails in response.pastPurchases) {
        if (purchaseDetails.pendingCompletePurchase) {
          pendingReceiptList.add(purchaseDetails);
        }
      }
    }

    // 未完了の購入トランザクションがあったときにはレシートを再検証する
    if (pendingReceiptList.isNotEmpty) {
      pendingReceiptList.forEach(
        (PurchaseDetails purchaseDetails) async {
          // 購入処理が完了、または復元された場合
          if (purchaseDetails.status == PurchaseStatus.purchased ||
              purchaseDetails.status == PurchaseStatus.restored) {
            // レシートの検証をする
            final receipt =
                purchaseDetails.verificationData.localVerificationData;

            final valid = await _purchaseService.postPurchaseReceipt(
              receipt: base64Encode(utf8.encode(json.encode(receipt))),
              itemMasterCode: purchaseDetails.productID,
            );

            if (valid) {
              // 購入完了の処理
              if (purchaseDetails.pendingCompletePurchase) {
                await _inAppPurchase.completePurchase(purchaseDetails);
              }
            }
          }
        },
      );
    }
  }

リチェックの処理で参考になるレポジトリ:

https://github.com/erase2004/InstantCoffee/blob/9278973eb13f70e04653076a2cf651dc13b0cce5/lib/helpers/iap_subscription_helper.dart#L33

0

Game Up Your Life.

Skimieアプリをダウンロードして、タスク管理をゲーム化しよう

Skimieは、仕事や勉強にゲーミフィケーションを取り入れ、モチベーションアップをサポートする情報管理サービスです。

新着記事

『ワードカスケード』って知ってますか?

ブックマークの整理をしていた際に、以下のサイトが目に留まりました。だいぶ前になるのですが、気になってブックマークしたのを思い出しました。https://river.tango-gacha.com/Cascade(カスケード)とは小滝・階段状に連続する滝のことを意味するようです。アイディア出しや頭の中を整理する時に、しばらく眺めるのは効果ありそうですね。クリックすると背景色が変わったり、しばらくすると左下にXへ遷移できるアイコンが出てきたりとする楽しい動きも良いですね!

Stable Diffusion オススメアプリと使い方

初心者から上級者まで、無料でもアニメ系のイラストを描きやすい、個人的にオススメなStable Diffusionベースアプリです。 【DaysAI】 レベルアップ報酬が手に入るようになったことで、無料〜ゴールド課金で充分使えるアプリです。 (プレミアム課金は費用対効果的によほど使い倒さない限り他のアプリの年額と同時に使った方が良いです) 「うちの子」と付くアプリだけあり、作成したキャラをベースに描いてくれるので、髪型、表情、服装の一部を修正もできます。 日本語、ポジティブプロンプトやしきい値対応。有料会員になればネガティブプロンプトにも対応します。 「(全身)、上から、ビューアを見る」とかでいけます。

Skimie のバトルのお気に入りパーティ

みなさんのお気に入りのキャラクターはなんですか? 自分は ・タダシ ・ヴァール ・羽柴シバ です。 ![](https://skimie.com/uploads/u/IEozBO1uEfWNYUwdTgAeADBUU4F3/img/090938062c2ca448d931523f898d4b4a229dc0f3833b087fe.png)

Days AI 7/27 挑戦分

使用アプリ:Days AI テーマ:「夏の温泉」 テーマに組み込まれているはずの浴衣が吹っ飛びました。多分、表情や髪型描き込み過ぎました…。 このアプリの仕様上、キャラの雛形はあるしので、髪の纏め方も「花の髪飾り」と「まとめ上げたポニーテール」くらいで良かったんだろうと反省…。 夏の温泉で足湯&冷酒呑んで酔っ払ってる、オッサン寄りな亜人種イケオジを描きたかっただけなんです…。

使い方がわからない!助けて!

使う練習であげてみました。 このアプリまだ使い方が わからないので ヒントをもらえると嬉しいです。

本日も毎日1筆です♪

![](https://skimie.com/uploads/u/8N0K1D3KdpeWs9IUaBbNTtqmjmv2/img/072628348c2ca448d931523f898d4b4a229dc0f3833b087fe.png) ゲーム、OMORIのファンアートです。

生成AI検索『Perplexity』とは?

『Perplexity』というサービスを耳にして、とても気になったので調べてみました。検索に利用できるサービスで、Google検索よりも便利とか、、、実際に利用してみた感じ、とても便利そうです!『Perplexity』とは?AI チャットボットを活用した調査および会話型検索エンジンのこと。「Perplexity AI」は、元Google AI研究者らによって提供されるAIアシスタントです。参考ページhttps://www.sbbit.jp/article/cont1/134482https://meet.acesinc.co.jp/blog/perplexityai/運営元:Perplexity AI, Inc.設立:2022年8月所在地: アメリカ合衆国に拠点事業内容:会話型人工知能(AI)を専門とするソフトウェア会社会話型AIを用いた回答エンジンを提供。Perplexity AIの特徴情報源がわかる自動で情報源が表示される。検索範囲を絞ることができる「YouTubeから」「学術論文から」など、検索範囲をプラットフォームやカテゴリに絞ってリサーチができる。目的により最適な情報源を選ぶことが可能です。自動で要約してくれる返答の文章を箇条書き形式で端的にまとめて生成がされる。最新データを基にした回答が可能インターネット上の情報から回答を生成するブラウジング機能が搭載されている。日本語にも対応しているスマホアプリで利用ができる『Perplexity』のURLhttps://www.perplexity.ai/

色覚検定3級クイズについて

何問かは伏せますが、「敵を警戒させる色」で、誤入力かと思いますが、同じ回答が2つあります。 正解は上の方です。これは問題側にミスがあるのでネタバレしていいかと思い描きました。 普段私は、短編小説(SS)、小説、AI生成イラストで自分のキャラクターの可視化などしています。

お気に入りの謎解きイベント

西武線沿線の駅周辺を散策しながら謎を解き明かしていく周遊型の謎解き宝探しイベントがやっていて、ここ3年くらい参加しています。Webページを確認すると、2019年より毎年開催しているとのことです。こちらイベントですが参加は無料!※ただし、西武線沿線でたくさん駅を乗り降りするため、西武線全線を自由に乗り降りできる切符を購入する必要があります。【2024年】WEST CODE 第六弾 見知らぬ絵日記と約束の列車期間:2024.4.26~9.16https://www.seiburailway.jp/newsroom/news/20240417_WESTCODE/西武線の色々な駅を知れる良い機会で、子供を含めて家族で参加しています。駅で冊子が配られているので、シーズンの休日は多くの参加者を目にするイベントです。謎のレベルは高めで、去年はゴールできなかったので、今年こそはクリアしたいです!

LINE新機能「スタンプアレンジ機能」とは?

5/8より、複数のLINEスタンプを組み合わせて送信できる機能がスタートしました!簡単に利用が出来て楽しいです。参考ページhttps://news.yahoo.co.jp/articles/695c6ea72b6a7a6746a0af4fd8376c8f34dfdd43やり方はとても簡単で、スタンプを長押しすると複数を選択できるモードになります。組み合わせたいスタンプを選択し、後は自由にレイアウトして送るだけです。以下、注意点です。スタンプアレンジできる物とできない物があるパソコンは、送信も表示もできない

私なりの Skimie の使い方。ゲーミフィケーションで楽しく日々のタスク管理や備忘録。

Skimie というこのサービス。まだ、色々な機能が成熟してないようだがゲーミフィケーションとタスク管理の組み合わせは面白いなと思って使ってみています。主な使い方は・タスク管理機能日々のタスクを管理するのはもちろん。買い物リストなどとしても活用しています。iPhone でいうリマンイダーみたいな使い方。・ナレッジ機能思いついたことなど備忘録をつらつらとメモするのに活用しています。基本的には非公開にして、自分用のメモです。今回はみなさんが活用するヒントになればと思い、ナレッジを一般公開してみます。タスクをこなしたり、ナレッジを書き溜めるとコインがもらえるので、それを使ってとりあえずキャラクターを育てるために日々コツコツと続けてます。Skimie を使うヒントになれば幸いです。

複数の写真を1つにする方法が知りたい

Xで投稿する際に、やりたいと思ったので調べてみました。意外と簡単な方法が見つからなかったので、もう少し調べたり、周りに聞こうと思います。とりあえずは、Androidユーザーの私が一番簡単にできそうな方法をご紹介します。参考サイトhttps://support.google.com/photos/answer/12637115?hl=ja&co=GENIE.Platform%3DAndroidツール「Googleフォト」アプリ機能「コラージュ」という機能を利用します。手順は参考サイトに記載ありますが、とても簡単です。テンプレートは無料なのは4つほどです。私のスマホにあるラーメン画像だとこんな感じ。

イオンクレジットカードの付帯保険「ショッピングセーフティ保険」が気になる

最近、イオンクレジットカードの広告を見て、気になる内容があったのでピックアップします。それは、イオンクレジットカードの付帯保険で「ショッピングセーフティ保険」のサービスです。とてもお得なサービスなので、他のクレジットカードなどにも同様のサービスがないか調べたいです。また、スマホやパソコンなどWeb購入する機会もあるので、ぜひ利用したいと思います。▶公式サイトhttps://www.aeon.co.jp/service/safety/イオンクレジットカードの「ショッピングセーフティ保険」はどんなサービスこちらの記事の最後に公式サイトのスクショも添付します。約半年間になりますが、偶然による事故の補償するサービスです。【対象】イオン銀行が発行したクレジットカードでクレジット決済する5,000円以上の商品【対象範囲】偶然による事故破損事故火災事故盗難事故【保証期間】購入日から180日以内

複数のPDFファイルを結合したい

最近、複数のPDFの資料を確認する作業があり、PDFを結合するサービスを探していました。検索すると、Webサービスを利用してオンラインでやる方法とツールをインストールする方法の2つがあるようです。今回は、ツールをインストールしてみました。見つけたツールCubePDF Pagehttps://www.cube-soft.jp/cubepdfpage/評判以下のサイトを参考にしました。ざっと読んでみると評価は良さそうな感じでした。https://www.itreview.jp/products/cubepdf-page/reviews感想利用方法がとてもシンプルで、特にマニュアルことなく利用ができました。注意点こちらのソフトをインストールすると、一緒に「CubeWidet」というソフトもインストールされるようです。こちらは、最小化してニュースが表示するようになり気になったのでアンインストールしました。補足こちら、pdfファイルの他にpngファイルでも同様の事ができないか試してみたところ対応できました!

ウェブサイトのパスワードをスマートに管理したい

色々なWebサービスを利用していると、パスワードの管理は困りませんか?最近、Googleで「Google パスワード マネージャー」というサービスがあることを知りました。パスワードを考えたり、メモしたり、管理したり、思い出したりするストレスから解放されるので、とても便利なサービスなので、ぜひ活用しましょう。公式サイトhttps://support.google.com/chrome/answer/95606?hl=JA&ref_topic=7438325サービス概要Chromeを利用して様々なサイトのパスワードを保存することができますパスワードの保存方法Chromeにて、ウェブサイトで新しいパスワードを入力すると、Chromeでパスワードを保存するかダイアログが表示されますので、こちらで [保存] をクリックするだけです。保存したパスワードの利用方法パスワード保存したウェブサイトのログインフォームを表示すると、自動的に入力されます。便利な点パスワードを覚える必要がないGoogleアカウントに連携しているため、スマホ・パソコンのどちらでもパスワード管理ができる操作がとても簡単

利用したECTの料金をすぐに確認する方法

利用したETCの料金をすぐに確認する方法を知っていますか?ETCのカードにもよるかもしれませんが、私の持っているETCカードではすぐに利用料金を確認することができません。そこで検索してみたところ、こんなサービスがありました。ETC利用照会サービスhttps://www.etc-meisai.jp/【サービス内容】こちらはETCカードで利用した走行の利用明細をネット上で確認することができます。【対象】ETCクレジットカード、ETCパーソナルカード及びETCコーポレートカードを持っている方サービス登録に必要な情報ETCカード番号メールアドレス過去のご利用年月日車両番号車載器管理番号【注意点】私の場合、「車載器管理番号」という物に馴染みがありませんでした。こちら、車検証と一緒に「ETC車載器セットアップ証明書」という紙を保管していて、そちらに記載がありました。