デスクトップとモバイルデバイスでのアプリ内課金
デスクトップアプリケーションとモバイルアプリケーションで、消耗品と非消耗品のアプリ内課金を販売できます。macOS/iOSとAndroidデバイスの両方で、IAP製品のセットアップと支払い処理方法をご紹介します。
レベル:上級
プラットフォーム:macOS, iOS, Android
クラス: InAppPurchases::Listener,SoundPlayer,AsyncUpdater,ListBoxModel
このプロジェクトには、macOS/iOSではApple DeveloperアカウントとiTunes Connectアカウント、AndroidではGoogle Play Developerアカウントが必要です。ヘルプが必要な方はApple Developer,iTunes ConnectそしてGoogle Play Developerのウェブサイトで口座を開設する。
シミュレーターはIAPテストをサポートしていないため、このプロジェクトではアプリ内課金をテストするために物理的なデバイスが必要です。このためにデバイスを用意してください。
スタート
このチュートリアルのデモ・プロジェクトのダウンロードはこちらから:PIP|ZIP.プロジェクトを解凍し、最初のヘッダーファイルをProjucerで開く。
このプロジェクトのpipバージョンを使用する場合は、必ずResources
フォルダを生成されたProjucerプロジェクトに追加します。
このステップでヘルプが必要な場合は、以下を参照してください。Tutorial: Projucer Part 1: Getting started with the Projucer.
デモ・プロジェクト
このプロジェクトでは、音声ディクテーションを使用してフレーズを再生する際に、いくつかの聴覚的なフレーバーを提供するために、アプリ内課金で取得できるさまざまな音声を提供しています。デフォルトでは、ユーザーは一般的なロボット音声を利用できますが、必然的にJUCE開発者の音声を試したくなります。iOSシミュレータでモバイル・アプリケーションを実行すると、ウィンドウは次のようになります:

アプリケーションはシミュレータで実行されているので、商品がグレーアウトし、価格が無期限にフェッチされるのは予想された動作です。
これはチュートリアル・プロジェクトですが、サンドボックス・モードかテスト・ユーザーとしてログインしていない限り、商品を購入しようとするとクレジットカードに課金されます!
ここで紹介するコードは、大まかに以下のものと似ている。アプリ内購入JUCE Examplesより。
初期設定
このプロジェクトが適切に機能するためには、特定のデプロイメント・プラットフォームに適した開発者コンソールで、いくつかの初期設定手順を実行する必要があります。まず、Projucerでアプリ内課金の適切なパーミッションを許可しましょう。macOS/iOSではアプリ内課金機能のチェックボックスがオンになっていることを確認してください。Androidでアプリ内課金チェックボックスをオンにする。


プロジェクトを保存し、お気に入りのIDEで開くと、Projucerは自動的に必要なエンタイトルメントをデプロイメント・ターゲットに追加します。
アップル開発者
アンドロイド用に開発する場合は、次のセクションに進んでください。Google Play Developerをご覧ください。
macOSとiOSでは、アプリケーションに署名するために、Xcode内のApple Developerアカウントでサインインし、開発チームを選択する必要があります。あなたのプロジェクトのためのユニークなバンドルIDを選択します。以下のスクリーンショットのように、Xcodeは自動的に署名証明書とProvisioning Profileを提供します:

また、正しいアプリの機能がチェックされ、承認されていることも確認してください。能力設定ウィンドウを開きます。以下のように同じ情報が表示されるはずです:

iTunes Connect
iOSでアプリ内課金を正しく表示するには、iTunes ConnectでIAP商品を作成する必要があります。まず、ダッシュボードの私のアプリ.に移動すると特徴タブにアクセスできます。アプリ内課金機能を以下の画面で見ることができる:

をクリックしてください。**+**適切な名前と価格で、アプリのオプションに対応する6つの製品を作成するためのサイン。
Google Playデベロッパー
macOS/iOS用に開発する場合は、前のセクションにジャンプしてください。Apple Developerをご覧ください。
Androidでアプリ内課金を正しく表示するには、Google Play Consoleでアプリ内課金商品を作成する必要があります。まず、アプリのページをすべてのアプリケーションパネルを開きアプリ内商品ページ下店舗プレゼンス.の下にある項目にアクセスできます。マネージド製品タブをクリックする:

をクリックする。マネージド製品を作るアプリのオプションに対応する6つの商品を、適切な名前と価格で作成する。
Google PlayのプロダクトIDに関する制限により、プロダクトIDには数字(0~9)、小文字(a~z)、アンダースコア(_)、またはフルストップ(.)のみを使用することをお勧めします。こうすることで、AppStoreとPlayStoreの両方で同じプロダクトIDを使できますす。そうしないと、アプリは同じ商品でも異なるプロダクトIDを扱わなければならなくなります。
Android APKを生成する
Androidでアプリ内課金を正しく機能させるためには、Android版アプリに署名し、Google PlayストアAPIへのリクエストを認証する必要があります。まずAndroid Studioを起動し、メニューバーのビルド > 署名付きAPKの生成...:

すると、キーストア・ファイルの場所、エイリアス、パスワードを入力するようプロンプトが表示される:

次に、リリースビルドタイプの "release_"フレーバーを選択し、V1とV2の両方の署名がチェッ クされていることを確認する。

これでキーストア・ファイルが生成され、Projucerで参照できるようになる。Androidリリース設定で、キーストア・ファイルへの相対パス、エイリアス、パスワードを鍵の署名の分野だ:

アプリ内購入のセットアップはもう完了しているはずで、ようやくアプリにこれらの機能を実装し始めることができる。
購入タイプ
アプリ内課金は、アプリ内で直接顧客に追加コンテンツや機能を提供するのに便利です。プレミアム機能、限定アイテム、あるいはサブスクリプションなどがあります。一般的に、すべての関連プラットフォームで4つの主要なタイプの購入があります:
- 消耗品:複数回使用・購入可能なアイテム。
- 非消耗品:アプリの機能を永続的にアンロックする1回限りの購入。
- 自動更新サブスクリプション:定期的に更新されるコンテンツを、解約するまで定期的にお届けします。
- 更新されないサブスクリプション:手動更新が必要な期間限定コン テンツ。
このチュートリアルでは、非消費型のアプリ内課金を実装します。
プロジェクト体制
プロジェクトは、アプリケーションのさまざまな部分を処理するために、以下のクラスを使用して構成されています:
- MainContentComponent:画面上にGUIコンポーネントをレイアウトし、再生/停止ボタンクリック時のサウンドファイルの再生を処理する。
- フレーズモデル:ListBoxModel購入したボイスを使って演奏できるフレーズを説明する。
- ボイスモデルListBoxModelをクリックすると、フレーズを再生するために購入可能なボイスが表示されます。
- VoiceRow:カスタム行ComponentのVoiceModelクラスを使用して、特定の音声エントリーの画像と情報を表示します。
- VoicePurchases:VoiceProductの購入を管理するクラス。InAppPurchasesインスタンスだ。
- VoiceProduct:価格、名前、以前に購入したかどうかなどの情報を持つ、単一の購入可能な製品を記述するための構造。
見てわかるように、これはおおよそMVCデザイン・パターンを使って、アプリケーションの機能を別々のクラスに分離する。
VoiceProduct構造体には、消耗品以外の製品に役立つ情報、つまり以下の変数が含まれています:
- const char* identifier:iTunes ConnectやGoogle Playで参照される一意の識別子。
- const char* humanReadable:アプリに表示する、人間が読めるバージョンの識別子。
- bool isPurchased:ログインしているユーザがそのアイテムを購入したことがあるかどうか。
- bool priceIsKnown:価格が読み込まれ、表示されるように取得されているかどうか。
- bool purchaseInProgress:ユーザーが現在進行中の購入を開始したかどうか。
- String購入価格:商品価格を現地通貨で表示するローカライズされた文字列。
消耗品の購入にはどのような情報が必要だろうか? 定期購入についてはどうだろう? 何が追加されるべきか、何が削除されるべきかを考えよう。
製品の表示
販売したいIAP商品をユーザー・インタフェースに表示することから始めましょう。VoicePurchasesコンストラクタのVoicePurchases
クラスでは、VoiceProductオブジェクトを上記のような構造で初期化しています:
VoicePurchases()
{
voiceProducts = juce::Array(
{ VoiceProduct {"robot", "Robot", true, true, false, "Free" },
VoiceProduct {"jules", "Jules", false, false, false, "Retrieving price..." },
VoiceProduct {"fabian", "Fabian", false, false, false, "Retrieving price..." },
VoiceProduct {"ed", "Ed", false, false, false, "Retrieving price..." },
VoiceProduct {"lukasz", "Lukasz", false, false, false, "Retrieving price..." },
VoiceProduct {"jb", "JB", false, false, false, "Retrieving price..." } });
}
これらのプロダクトIDは、iTunes ConnectやGoogle Play Consoleの値と正確に一致しなければ正しく動作しません。
すべての製品について、人間が読めるバージョンを簡単に取得できるように、私たちはヘルパー関数を実装しました。StringArrayすべて大文字の名前:
juce::StringArray getVoiceNames() const
{
juce::StringArray names;
for (auto& voiceProduct : voiceProducts)
names.add (voiceProduct.humanReadable);
return names;
}
同じクラス内にもうひとつ、次のようなヘルパー関数を作ってみよう。findVoiceIndexFromIdentifier()
を使用して、識別子を渡されたVoiceProductのインデックスを取得します。このプライベート関数は、後でInAppPurchasesオブジェクトがある:
int findVoiceIndexFromIdentifier (juce::String identifier) const
{
identifier = identifier.toLowerCase();
for (auto i = 0; i < voiceProducts.size(); ++i)
if (juce::String (voiceProducts.getReference (i).identifier) == identifier)
return i;
return -1;
}
についてInAppPurchasesクラスはブロードキャスターとして機能する。InAppPurchases::Listenerこのクラスのリスナーになり、IAPサーバーからのコールバックを受け取る。[1]:
class VoicePurchases : private juce::InAppPurchases::Listener // [1]
{
public:
これでInAppPurchasesコールバック関数を実装します。まずproductsInfoReturned()
関数を呼び出します。この関数はInAppPurchases::getProductsInformation().
void productsInfoReturned (const juce::Array& products) override
{
if (! juce::InAppPurchases::getInstance()->isInAppPurchasesSupported()) // [2]
{
for (auto idx = 1; idx < voiceProducts.size(); ++idx) // [3]
{
auto& voiceProduct = voiceProducts.getReference (idx);
voiceProduct.isPurchased = false;
voiceProduct.priceIsKnown = false;
voiceProduct.purchasePrice = "In-App purchases unavailable";
}
juce::AlertWindow::showMessageBoxAsync (juce::AlertWindow::WarningIcon, // [4]
"In-app purchase is unavailable!",
"In-App purchases are not available. This either means you are trying "
"to use IAP on a platform that does not support IAP or you haven't setup "
"your app correctly to work with IAP.",
"OK");
}
else
{
for (auto product : products)
{
auto idx = findVoiceIndexFromIdentifier (product.identifier); // [5]
if (juce::isPositiveAndBelow (idx, voiceProducts.size())) // [6]
{
auto& voiceProduct = voiceProducts.getReference (idx);
voiceProduct.priceIsKnown = true;
voiceProduct.purchasePrice = product.price;
}
}
juce::AlertWindow::showMessageBoxAsync (juce::AlertWindow::WarningIcon, // [7]
"Your credit card will be charged!",
"You are running the sample code for JUCE In-App purchases. "
"Although this is only sample code, it will still CHARGE YOUR CREDIT CARD!",
"Understood!");
}
guiUpdater.triggerAsyncUpdate();
}
- [2]まず、アプリ内課金があなたのプラットフォームでサポートされているか、セットアップが正しく行われているかを確認します。
- [3]: 最初に空いているVoiceProductオブジェクトを除くすべてのVoiceProductオブジェクトについて、商品が入手できないことを反映するように変数を設定する。
- [4]オプションで非同期にメッセージボックスを表示し、ユーザーに問題を説明します。
- [5]: アプリ内課金が可能な場合、すべてのVoiceProductオブジェクトについて、先に実装したヘルパー関数で識別子からインデックスを取得します。
- [6]インデックスが有効であれば、商品変数に商品の在庫状況を反映させます。
- [7]オプションで、非同期にユーザーに課金されることを説明するメッセージボックスを表示できます。
購入品の検索
このセクションは物理的なデバイス上でのみ機能します。シミュレーターでは正しく機能しないので、試さないでください。
アプリ内課金を扱う場合、アプリケーションが最初にチェックすべきなのは、ユーザーがサインインしたときに過去の購入履歴があるかどうかです。顧客として最もイライラすることの1つは、別のデバイスや同じアプリの以前のバージョンで購入したものを失うことです。そこで、アプリ起動時にできるだけ早く、ユーザーが 商品ページの最新ビューを取得できるようにしましょう。
まずVoicePurchases
アプリの一時的な状態を保存するクラス[1]:
bool havePurchasesBeenRestored = false, havePricesBeenFetched = false, purchaseInProgress = false; // [1]
juce::Array voiceProducts;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VoicePurchases)
};
という新しいヘルパー関数も必要だ。getPurchase()
インデックスからVoiceProductを取得します。この関数には、この関数が最初に呼ばれたときのための初期化コードも挿入します:
VoiceProduct getPurchase (int voiceIndex)
{
if (! havePurchasesBeenRestored)
{
havePurchasesBeenRestored = true; // [2]
juce::InAppPurchases::getInstance()->addListener (this); // [3]
juce::InAppPurchases::getInstance()->restoreProductsBoughtList (true); // [4]
}
return voiceProducts[voiceIndex]; // [5]
}
- [2]この関数が初めて呼び出される場合は、このコードセグメントが再び呼び出されないようにしてください。
- [3]このクラスをInAppPurchasesインスタンスでコールバックを受け取ります。
- [4]を呼び出すことができる。
restoreProductsBoughtList()
関数を使用して復元処理をトリガーし、その後のコールバックを待つ。 - [5]最後に、指定されたインデックスのVoiceProductを返します。これは、この関数の通常の動作として、毎回呼び出されます。
VoicePurchasesクラスは、上記の関数が最初に呼ばれたときにリスナーとして登録されるので、このクラスが破棄されたときに登録を解除する必要があります。このクラスをInAppPurchasesリスナーをクラスのデストラクタに追加する:
~VoicePurchases() override
{
juce::InAppPurchases::getInstance()->removeListener (this);
}
先に定義したgetPurchase()
関数を呼び出した。restoreProductsBoughtList()
関数を使用してコールバックをトリガーし、購入した商品を復元します。では、そのコールバック関数を実装してみましょう。purchasesListRestored()
:
void purchasesListRestored (const juce::Array& infos, bool success, const juce::String&) override
{
if (success)
{
for (auto& info : infos)
{
for (const auto& productId : info.purchase.productIds)
{
auto idx = findVoiceIndexFromIdentifier (productId); // [6]
if (juce::isPositiveAndBelow (idx, voiceProducts.size()))
{
auto& voiceProduct = voiceProducts.getReference (idx); // [7]
voiceProduct.isPurchased = true;
}
}
}
guiUpdater.triggerAsyncUpdate();
}
if (! havePricesBeenFetched)
{
havePricesBeenFetched = true; // [8]
juce::StringArray identifiers;
for (auto& voiceProduct : voiceProducts)
identifiers.add (voiceProduct.identifier);
juce::InAppPurchases::getInstance()->getProductsInformation (identifiers); // [9]
}
}
- [6]サーバーからのレスポンスが正常であれば、購入リストをできますます。ヘルパー関数を使用して、識別子から音声のインデックスを見つけます。
- [7]インデックスが有効であれば、VoiceProductオブジェクトの適切な変数を設定し、購入状態を反映させます。
- [8]このコールバック関数が最初に呼ばれたときは、購入価格を初期化する必要があります。このコードセグメントが2回目に呼ばれないようにしてください。
- [9]の商品価格を取得するリクエストを開始する。InAppPurchasesインスタンスを呼び出す。
getProductsInformation()
関数である。
これらのアプリ内課金商品のGUIを更新するためには、購入状態を保存する一時変数があると便利です。VoiceRowのサブクラスで、プライベートなメンバー変数として宣言しましょう。VoiceModel
クラスである:
bool isSelected = false, hasBeenPurchased = false, purchaseInProgress = false;
int rowSelected = -1;
juce::Image avatar;
現時点では、アプリ内課金商品はすべて同じビジュアルに見え、視覚的に印象的な方法で利用可能であることを明確に示していません。購入可能な商品には、異なるルック&フィールを適用してみましょう。アプリ内のpaint()
関数が利用可能な場合は、商品画像に白い背景を適用するようにコードを修正する。[10]利用できない場合は、白い半透明のオーバーレイとなる[11]:
void paint (juce::Graphics& g) override
{
auto r = getLocalBounds().reduced (4);
{
auto voiceIconBounds = r.removeFromLeft (r.getHeight());
g.setColour (juce::Colours::black);
g.drawRect (voiceIconBounds);
voiceIconBounds.reduce (1, 1);
g.setColour (hasBeenPurchased ? juce::Colours::white : juce::Colours::grey); // [10]
g.fillRect (voiceIconBounds);
g.drawImage (avatar, voiceIconBounds.toFloat());
if (! hasBeenPurchased)
{
g.setColour (juce::Colours::white.withAlpha (0.8f)); // [11]
g.fillRect (voiceIconBounds);
またupdate()
関数を使用して、名前と価格のラベルに購入ステータスを反映させます。先に定義したヘルパー関数を使用して、まず行インデックスからVoiceProductを取得します。[12]そして、その商品が購入されたかどうかを示す一時変数を設定する。[13].その後、以下のようにGUIを更新する:
void update (int rowNumber, bool rowIsSelected)
{
isSelected = rowIsSelected;
rowSelected = rowNumber;
if (juce::isPositiveAndBelow (rowNumber, voices.size()))
{
auto imageResourceName = voices[rowNumber] + ".png";
nameLabel.setText (voices[rowNumber], juce::NotificationType::dontSendNotification);
auto purchase = purchases.getPurchase (rowNumber); // [12]
hasBeenPurchased = purchase.isPurchased; // [13]
if (rowNumber == 0)
{
purchaseButton.setButtonText ("Internal");
purchaseButton.setEnabled (false);
}
else
{
purchaseButton.setButtonText (hasBeenPurchased ? "Purchased" : "Purchase");
purchaseButton.setEnabled (! hasBeenPurchased && purchase.priceIsKnown);
}
setInterceptsMouseClicks (! hasBeenPurchased, ! hasBeenPurchased); // [14]
このコードでは、主に、商品が購入されたときに名前と価格ラベルのフォントスタイルがデフォルトのままになるように更新し、色を白に変更しています。さらに、対応する商品が購入可能な場合、購入ボタンを有効にします。また、顧客への請求時のエラーを避けるために、購入後にこれらのボタンをマウスでクリックできないようにする必要があります。[14]。
商品の購入
このセクションは物理的なデバイス上でのみ機能します。シミュレーターでは正しく機能しませんので、試さないでください。
まだアプリに購買行動を実装していないので、実装しましょう。アプリにpurchaseVoice()
パブリック関数VoicePurchases
クラスに転送します。InAppPurchasesインスタンスだ:
void purchaseVoice (int voiceIndex)
{
if (havePricesBeenFetched && juce::isPositiveAndBelow (voiceIndex, voiceProducts.size()))
{
auto& product = voiceProducts.getReference (voiceIndex); // [1]
if (! product.isPurchased)
{
purchaseInProgress = true;
product.purchaseInProgress = true; // [2]
juce::InAppPurchases::getInstance()->purchaseProduct (product.identifier); // [3]
- [1]まず、インデックスを使ってVoiceProductを取得する前に、価格が取得されているかどうかをチェックします。
- [2]未購入の場合は、購入状態を変数に設定する。
- [3]これで安心してInAppPurchasesインスタンスで、正しい識別子を指定して製品を購入する。
これは、購入が終了し、レスポンスを受信したときに、サーバーからのコールバックをトリガーします。これを実装します。productPurchaseFinished()
コールバックはここにある:
void productPurchaseFinished (const PurchaseInfo& info, bool success, const juce::String&) override
{
purchaseInProgress = false;
for (const auto& productId : info.purchase.productIds)
{
auto idx = findVoiceIndexFromIdentifier (productId); // [4]
if (juce::isPositiveAndBelow (idx, voiceProducts.size()))
{
auto& voiceProduct = voiceProducts.getReference (idx); // [5]
voiceProduct.isPurchased = success;
voiceProduct.purchaseInProgress = false;
}
else
{
// On failure Play Store will not tell us which purchase failed
for (auto& voiceProduct : voiceProducts)
voiceProduct.purchaseInProgress = false;
}
}
guiUpdater.triggerAsyncUpdate();
}
先ほどと同じヘルパー関数を使って、VoiceProductの識別子からインデックスを取得します。[4]そして、購入が成功したかどうかに応じて、当該オブジェクトに適切な変数を設定し、購入状態を終了する。[5]。
サーバーからの応答を待つ間、ユーザーに購入状態を示すために、回転するアニメーションをpaint()
の機能である。VoiceModel
クラス[6]:
if (! hasBeenPurchased)
{
g.setColour (juce::Colours::white.withAlpha (0.8f)); // [11]
g.fillRect (voiceIconBounds);
if (purchaseInProgress) // [6]
getLookAndFeel().drawSpinningWaitAnimation (g, juce::Colours::darkgrey,
voiceIconBounds.getX(),
voiceIconBounds.getY(),
voiceIconBounds.getWidth(),
voiceIconBounds.getHeight());
}
}
}
ユーザーがUIとインタラクトしたときに購入プロセスを開始するために、clickPurchase()関数を実装してみましょう:
void clickPurchase()
{
if (rowSelected >= 0)
{
if (! hasBeenPurchased)
{
purchases.purchaseVoice (rowSelected); // [7]
purchaseInProgress = true;
startTimer (1000 / 50); // [8]
}
}
}
void timerCallback() override { repaint(); } // [9]
- [7]もし行インデックスが有効で、以前に購入されたことがなければ
purchaseVoice()
関数を使用し、購買状態に入る。 - [8]タイマーをスタートさせ、購入中に回転ホイー ルのアニメーションを更新します。
- [9]アニメーションの画面を再描画するためのタイマー・コールバックを実装する。
最後にupdate()
関数で、行固有の商品の購買状態を取得する。[10]を呼び出し、購入が完了したら回転アニメーションを停止する。stopTimer()
機能[11]:
purchaseInProgress = purchase.purchaseInProgress; // [10]
if (purchaseInProgress)
startTimer (1000 / 50);
else
stopTimer(); // [11]
チュートリアルのこの時点で、商品の購入と商品情報の取得を処理する仕組みはすべて実装されていますが、GUIに更新のタイミングを指示する必要があります。
非同期アップデートの処理
購入とIAPサーバーとの同期は別のスレッドで実行されるため、レスポンスを非同期で処理する必要がある。そのためMainContentComponent
クラスを継承しよう。AsyncUpdaterクラス[1]このクラスの参照をVoicePurchasesのインスタンスに渡す。[2]:
class MainContentComponent : public juce::Component,
private juce::AsyncUpdater // [1]
{
juce::SoundPlayer player;
VoicePurchases purchases { *this }; // [2]
juce::AudioDeviceManager dm;
VoicePurchasesコンストラクタとメンバー初期化リストで、VoicePurchasesへの参照をAsyncUpdaterインスタンスをプライベート変数[3]:
VoicePurchases (juce::AsyncUpdater& asyncUpdater) // [3]
: guiUpdater (asyncUpdater)
{
を参照できるように、その変数をプライベート・メンバーとして宣言する。AsyncUpdater後に[4]:
//==============================================================================
juce::AsyncUpdater& guiUpdater; // [4]
現在はpurchaseVoice()
関数とInAppPurchasesすなわちproductsInfoReturned()
,purchasesListRestored()
そしてproductPurchaseFinished()
対応するコードセグメントへの最後のステップとして、GUIへの非同期更新をトリガーする。[5]:
void purchaseVoice (int voiceIndex)
{
if (havePricesBeenFetched && juce::isPositiveAndBelow (voiceIndex, voiceProducts.size()))
{
//...
if (! product.isPurchased)
{
//...
guiUpdater.triggerAsyncUpdate(); // [5.1]
}
}
}
//...
void productsInfoReturned (const juce::Array& products) override
{
//...
guiUpdater.triggerAsyncUpdate(); // [5.2]
}
//...
void productPurchaseFinished (const PurchaseInfo& info, bool success, const juce::String&) override
{
//...
guiUpdater.triggerAsyncUpdate(); // [5.3]
}
//...
void purchasesListRestored (const juce::Array& infos, bool success, const juce::String&) override
{
if (success)
{
//...
guiUpdater.triggerAsyncUpdate(); // [5.4]
}
//...
}
これで、いつでもtriggerAsyncUpdate()
関数が呼び出される。AsyncUpdaterインスタンスでコールバックを処理できます。handleAsyncUpdate()
古いGUIコンポーネントを更新する関数[6]:
void handleAsyncUpdate() override
{
voiceListBox.updateContent();
voiceListBox.setEnabled (! purchases.isPurchaseInProgress());
voiceListBox.repaint();
}
以下のゲッターをVoicePurchases
クラスをパブリック・ヘルパー関数として使用する:
bool isPurchaseInProgress() const noexcept { return purchaseInProgress; }
これにより、購入が行われたり取得されたりするたびにGUIが更新されるようになります。アプリを再度起動すると、以前に購入したアプリ内課金が表示されるようになります。
ユーザーが特定の音声でフレーズを再生するたびにトークンを消費することで、これらのIAP製品の消費可能バージョンを実装します。