In-app Purchases

1. Introduction

This is a step-by-step instruction how to implement in-app purchases (for Android and iOS) using CGE CastleInAppPurchases unit and TInAppPurchases class.

2. Set up purchases in the stores (GooglePlay, AppStore)

You need to define the products you want sell in the

The internal names of products you define there will be then used with CGE API, like in methods TInAppPurchases.SetAvailableProducts and TInAppPurchases.Product.

3. Use services

You need to add a "service" to include the necessary integration code on Android and iOS.

4. Implement and use TGameInAppPurchases class

  1. Make a descendant of TInAppPurchases class in your project, e.g. called TGameInAppPurchases:

     TGameInAppPurchases = class(TInAppPurchases)
     protected
       procedure RefreshedPrices; override;
       procedure Owns(const AProduct: TInAppProduct); override;
     public
       constructor Create(AOwner: TComponent); override;
     end;
  2. Create an instance of this class. Usually just in TCastleApplication.OnInitialize, like this:

     InAppPurchases := TGameInAppPurchases.Create(Application);
  3. Call TInAppPurchases.SetAvailableProducts. Usually just from TGameInAppPurchases.Create.

    In the simplest case, the typical implementation of TGameInAppPurchases constructor may look like this:

     constructor TGameInAppPurchases.Create(AOwner: TComponent);
     begin
       inherited;
       SetAvailableProducts(['xxx']);
       // DebugMockupBuying := true; // for debugging, to fake purchases
     end;
  4. On iOS, the state of items currently owned is not automatically available.

    If you need to know whether user owns some non-consumable product, according to the AppStore, then you need to call TInAppPurchases.RefreshPurchases explicitly. Like

     InAppPurchases.RefreshPurchases;

    In return, it will ask user to login to the AppStore, and if all is successful, it will set TInAppProduct.Owns of all products and call TInAppPurchases.OnRefreshedPrices.

    Strictly speaking, you don’t need to call RefreshPurchases at all, during normal application usage. This way user will be asked to login only once user decides to make a purchase, not earlier. If you need to know whether user owns some non-consumable, just store the successful purchase in UserConfig, like UserConfig.SetValue('owns_some_product', true);, and do not depend on TInAppProduct.Owns always reflecting the actual ownership.

    However, you should also provide an explicit "Restore Purchases" button, in case user wants to restore purchases made on another device. To implement such button, call the InAppPurchases.RefreshPurchases.

  5. It is usually comfortable to define shortcuts for your in-app purchases products, like this:

     function TGameInAppPurchases.Xxx: TInAppProduct;
     begin
       Result := Product('xxx');
     end;

    This way you can use InAppPurchases.Xxx instead of InAppPurchases.Product('xxx') later. This minimizes the chance of possible typos in the product name.

  6. Override TInAppPurchases.RefreshedPrices virtual method, or wait for TInAppPurchases.OnRefreshedPrices event, to know the prices of your products (in user’s local currency).

    Use these prices to update the UI of your game. Like this:

     procedure TGameInAppPurchases.RefreshedPrices;
     begin
       inherited;
       if (StateBuyItems <> nil) and StateBuyItems.Active then
         StateBuyItems.UpdatePrices;
     end;
    
     // and in GameStateBuyItems unit you implement actual UI refreshing:
    
     procedure TStateBuyItems.UpdatePrices;
     begin
       MyLabel.Caption := 'Price of XXX: ' + InAppPurchases.Xxx.Price;
     end;
    
     // remember to also call UpdatePrices from TStateBuyItems.Start
  7. Buy products by calling TInAppPurchases.Purchase, like InAppPurchases.Purchase(InAppPurchases.Xxx).

    • For non-consumable items (that user buys once, for lifetime, e.g. "act 2 of the game", "no ads", "badge in user profile") override the TInAppPurchases.Owns to react to user buying this item. Like this:

        procedure TGameInAppPurchases.Owns(const AProduct: TInAppProduct);
        begin
          inherited;
          if AProduct = Xxx then
          begin
            UserConfig.SetValue('owns_xxx', true);
            UserConfig.Save;
            // update whatever is necessary, e.g. give user some reward, update the UI etc.
          end;
        end;
    • For consumable items (that user can buy multiple times, like "add 100 gold coins"), override the TInAppPurchases.Owns to react to user buying this item by calling TInAppPurchases.Consume. Then override TInAppPurchases.SuccessfullyConsumed react to this consumption:

       procedure TGameInAppPurchases.Owns(const AProduct: TInAppProduct);
       begin
         inherited;
         if AProduct = Xxx then
           Consume(Xxx);
       end;
      
       procedure TGameInAppPurchases.SuccessfullyConsumed(const AProduct: TInAppProduct);
       begin
         inherited;
         Player.Gold := Player.Gold + 100;
         UserConfig.SetValue('gold', Player.Gold); // save the new gold value
         UserConfig.Save;
       end;

    Note that in both cases, we save the rewards from this purchase as soon as possible to UserConfig, and we save it to disk (so it is not lost if the application will be killed soon). This is especially important for saving rewards from consumable items, since, once you call Consume, you have only one chance to react to TGameInAppPurchases.SuccessfullyConsumed. Once TGameInAppPurchases.SuccessfullyConsumed has been called, the store assumes you have given user the necessary reward. User has all the rights to expect the reward, as (s)he paid real money for it. So be sure that the code recording these rewards is reliable.

5. Testing Notes


To improve this documentation just edit the source of this page in AsciiDoctor (simple wiki-like syntax) and create a pull request to Castle Game Engine WWW (cge-www) repository.