/// <summary> /// Sets an item to 'deselected' in the database. /// </summary> public static void SetToDeselected(IAPItem item) { //pass argument to DBManager and invoke deselect event DBManager.SetDeselected(item.productId); if (itemDeselectedEvent != null) { itemDeselectedEvent(item.productId); } }
/// <summary> /// Sets an item to 'selected' in the database. /// </summary> public static void SetToSelected(IAPItem item) { //check if the item allows for single or multi selection, //this depends on whether the item has a deselect button bool single = item.deselectButton ? false : true; //pass arguments to DBManager and invoke select event bool changed = DBManager.SetSelected(item.productId, single); if (changed && itemSelectedEvent != null) { itemSelectedEvent(item.productId); } }
/// <summary> /// Unlocks items if the requirement for them has been met. You can /// call this method at runtime whenever the player made some /// progress, to ensure your shop items reflect the current state. /// </summary> public static void UnlockItems() { //this method is based on data from the database, //so if we don't have a DBManager instance don't continue if (!DBManager.GetInstance()) { return; } //get list of all shop groups from IAPManager List <IAPGroup> list = IAPManager.GetInstance().IAPs; //loop over groups for (int i = 0; i < list.Count; i++) { //cache current group IAPGroup group = list[i]; //loop over items for (int j = 0; j < group.items.Count; j++) { //cache IAP object IAPObject obj = group.items[j]; if (obj.req == null) { continue; } //cache reference to IAP item instance IAPItem item = GetIAPItem(obj.id); //check if the item reference is empty or set to purchased already if (item == null || DBManager.isPurchased(obj.id)) { continue; } //check if a requirement is set up for this item, //then unlock if the requirement has been met if (!string.IsNullOrEmpty(obj.req.entry) && DBManager.isRequirementMet(obj.req)) { if (IAPManager.isDebug) { Debug.Log("requirement met for: " + obj.id); } item.Unlock(); } } } }
/// <summary> /// Refreshes the visual representation of a specific shop item. /// This is called automatically because of subscribing to the DBManager update event. /// It also means saving performance due to not refreshing all items every time. /// </summary> public void Refresh(string id) { //this method is based on data from the database, //so if we don't have a DBManager instance don't continue if (!DBManager.GetInstance()) { return; } IAPObject obj = IAPManager.GetIAPObject(id); IAPItem item = instance.IAPItems.ContainsKey(id) ? instance.IAPItems[id] : null; if (obj == null || item == null || item.productId != id) { return; } bool isSelected = DBManager.GetSelected(id); bool isPurchased = DBManager.GetPurchase(id) > 0; //double check that selected items are actually owned //if not, correct the entry by setting it to deselected if (isSelected && !isPurchased) { DBManager.SetDeselected(id); isSelected = false; } if (isPurchased) { item.Purchased(true); //in case the item has been selected before, but also auto-select one item per group //more items per group can be pre-selected manually e.g. on app launch if (isSelected || (item.selectButton && !item.deselectButton && DBManager.GetSelectedGroup(IAPManager.GetIAPObjectGroupName(id)).Count == 0)) { item.IsSelected(true); } } else if (!string.IsNullOrEmpty(obj.req.entry) && DBManager.isRequirementMet(obj.req)) { //check if a requirement is set up for this item, //then unlock if the requirement has been met if (IAPManager.isDebug) { Debug.Log("requirement met for: " + obj.id); } item.Unlock(); } }
// check for purchases on online servers at billing initialization. // If a purchase is not registered, set local purchase state back to false private void VerifyReceipts() { //get list of old purchases: on iOS the saved transactions, //on Android we use the old purchases list received from Google if (inventory == null || inventory.GetAllPurchases().Count == 0) { return; } //loop over all IAP items to check if a valid receipt exists for (int i = 0; i < ids.Length; i++) { //cache IAP id, //only verify purchased items string localId = ids[i]; string globalId = GetIAPIdentifier(localId); if (DBManager.isPurchased(globalId)) { //initialize item as faked and loop over receipts bool faked = true; Purchase receipt = inventory.GetPurchase(localId); if (receipt != null) { //we found a receipt for this product on the device, //unset fake purchase and let our external //server decide what happens with this transaction faked = false; MakeRequest(receipt); break; } //we haven't found a receipt for this item, yet it is //set to purchased. This can't be, maybe our external server //response or the database has been hacked with fake data if (faked) { IAPItem item = null; if (ShopManager.GetInstance()) { item = ShopManager.GetIAPItem(globalId); } if (item) { item.Purchased(false); } DBManager.RemovePurchased(globalId); } } } }
/// <summary> /// sets an item to 'selected' in the database /// </summary> public static void SetToSelected(IAPItem item) { //check if the item allows for single or multi selection, //this depends on whether the item has a deselect button bool single = item.deselectButton ? false : true; //pass arguments to DBManager and invoke select event bool changed = DBManager.SetToSelected(item.productId, single); if(changed && itemSelectedEvent != null) itemSelectedEvent(item.productId); }
/// <summary> /// sets an item to 'deselected' in the database /// </summary> public static void SetToDeselected(IAPItem item) { //pass argument to DBManager and invoke deselect event DBManager.SetToDeselected(item.productId); if(itemDeselectedEvent != null) itemDeselectedEvent(item.productId); }
/// <summary> /// Initializes all IAPItem in the scene and instantiates them with their correct state. /// Called by IAPManager. /// </summary> public void Init() { instance = this; IAPItems.Clear(); DBManager.updatedDataEvent += Refresh; //get manually placed items in the scene IAPItem[] sceneItems = Resources.FindObjectsOfTypeAll(typeof(IAPItem)) as IAPItem[]; for (int i = 0; i < sceneItems.Length; i++) { if (string.IsNullOrEmpty(sceneItems[i].productId)) { continue; } #if UNITY_EDITOR if (UnityEditor.EditorUtility.IsPersistent(sceneItems[i].gameObject)) { continue; } #endif IAPItems.Add(sceneItems[i].productId, sceneItems[i]); } //get list of all shop groups from IAPManager List <IAPGroup> list = IAPManager.GetInstance().IAPs; int index = 0; //loop over groups for (int i = 0; i < list.Count; i++) { //cache current group IAPGroup group = list[i]; ShopContainer container = GetContainer(group.id); //skip group if prefab or parent wasn't set if (container == null || container.prefab == null || container.parent == null) { continue; } //loop over items for (int j = 0; j < group.items.Count; j++) { //cache item IAPObject obj = group.items[j]; //the item has already been placed in the scene manually //dont instantiate it in a container then if (IAPItems.ContainsKey(obj.id)) { continue; } //instantiate shop item in the scene and attach it to the defined parent transform GameObject newItem = (GameObject)Instantiate(container.prefab); newItem.transform.SetParent(container.parent.transform, false); newItem.GetComponent <RectTransform>().anchoredPosition = Vector2.zero; //rename item to force ordering as set in the IAP Settings editor newItem.name = "IAPItem " + string.Format("{0:000}", index + j); //get IAPItem component of the instantiated item IAPItem item = newItem.GetComponent <IAPItem>(); if (item == null) { continue; } //add IAPItem to dictionary for later lookup IAPItems.Add(obj.id, item); //upgrades overwrite, an IAP Item gets replaced with its current level List <string> upgrades = IAPManager.GetIAPUpgrades(obj.id); if (upgrades != null && upgrades.Count > 0) { for (int k = 0; k < upgrades.Count; k++) { IAPItems.Add(upgrades[k], item); } string currentUpgrade = IAPManager.GetNextUpgrade(obj.id); if (!string.IsNullOrEmpty(currentUpgrade)) { obj = IAPManager.GetIAPObject(currentUpgrade); } } //initialize and set up item properties based on the associated IAPObject //they could get overwritten by online data later item.Init(obj); } index += group.items.Count; } //refresh all products initially RefreshAll(); }
//instantiates shop item prefabs void InitShop() { //reset IAPItems.Clear(); //get list of all shop groups from IAPManager List <IAPGroup> list = IAPManager.GetInstance().IAPs; int index = 0; //loop over groups for (int i = 0; i < list.Count; i++) { //cache current group IAPGroup group = list[i]; ShopContainer container = GetContainer(group.id); //skip group if prefab or parent wasn't set if (container == null || container.prefab == null || container.parent == null) { if (IAPManager.isDebug) { Debug.LogWarning("Setting up Shop, but prefab or parent of Group: '" + group.name + "' isn't set. Skipping group."); } continue; } //loop over items for (int j = 0; j < group.items.Count; j++) { //cache item IAPObject obj = group.items[j]; //instantiate shop item in the scene and attach it to the defined parent transform GameObject newItem = (GameObject)Instantiate(container.prefab); newItem.transform.SetParent(container.parent.transform, false); newItem.GetComponent <RectTransform>().anchoredPosition = Vector2.zero; //rename item to force ordering as set in the IAP Settings editor newItem.name = "IAPItem " + string.Format("{0:000}", index + j); //get IAPItem component of the instantiated item IAPItem item = newItem.GetComponent <IAPItem>(); if (item == null) { continue; } //add IAPItem to dictionary for later lookup IAPItems.Add(obj.id, item); //upgrades overwrite, an IAP Item gets replaced with its current level List <string> upgrades = IAPManager.GetIAPUpgrades(obj.id); if (upgrades != null && upgrades.Count > 0) { for (int k = 0; k < upgrades.Count; k++) { IAPItems.Add(upgrades[k], item); } string currentUpgrade = IAPManager.GetNextUpgrade(obj.id); if (!string.IsNullOrEmpty(currentUpgrade)) { obj = IAPManager.GetIAPObject(currentUpgrade); } } //initialize and set up item properties based on the associated IAPObject //they could get overwritten by online data later item.Init(obj); } index += group.items.Count; } }
/// <summary> /// handle purchases, for real money or ingame currency /// </summary> public void HandleSuccessfulPurchase(string id) { //differ between ids set in the IAP Settings editor if (debug) { Debug.Log("HandleSuccessfulPurchase: " + id); } //get instantiated shop item based on the IAP id IAPItem item = null; if (ShopManager.GetInstance()) { item = ShopManager.GetIAPItem(id); } //if the purchased item was non-consumable, //additionally block further purchase of the shop item if (item != null && (item.type == IAPType.nonConsumable || item.type == IAPType.nonConsumableVirtual || item.type == IAPType.subscription)) { item.Purchased(true); } switch (id) { //section for in app purchases case "bann_coins": gameParameters.currentCharacter = "Bann"; PlayerPrefs.SetString("currentCharacter", gameParameters.currentCharacter); ShowMessage("You can now play with Bann!"); break; case "bella_coins": gameParameters.currentCharacter = "Bella"; PlayerPrefs.SetString("currentCharacter", gameParameters.currentCharacter); ShowMessage("You can now play with Bella!"); break; case "blane_coins": gameParameters.currentCharacter = "Blane"; PlayerPrefs.SetString("currentCharacter", gameParameters.currentCharacter); ShowMessage("You can now play with Blane!"); break; case "damien_coins": gameParameters.currentCharacter = "Damien"; PlayerPrefs.SetString("currentCharacter", gameParameters.currentCharacter); ShowMessage("You can now play with Damien!"); break; case "finnikin_coins": gameParameters.currentCharacter = "Finnikin"; PlayerPrefs.SetString("currentCharacter", gameParameters.currentCharacter); ShowMessage("You can now play with Finnikin!"); break; case "gwyn_coins": gameParameters.currentCharacter = "Gwyn"; PlayerPrefs.SetString("currentCharacter", gameParameters.currentCharacter); ShowMessage("You can now play with Gwyn!"); break; case "hector_coins": gameParameters.currentCharacter = "Hector"; PlayerPrefs.SetString("currentCharacter", gameParameters.currentCharacter); ShowMessage("You can now play with Hector!"); break; case "katsa_coins": gameParameters.currentCharacter = "Katsa"; PlayerPrefs.SetString("currentCharacter", gameParameters.currentCharacter); ShowMessage("You can now play with Katsa!"); break; case "mather_coins": gameParameters.currentCharacter = "Mather"; PlayerPrefs.SetString("currentCharacter", gameParameters.currentCharacter); ShowMessage("You can now play with Mather!"); break; case "pifoo_coins": gameParameters.currentCharacter = "Pifoo"; PlayerPrefs.SetString("currentCharacter", gameParameters.currentCharacter); ShowMessage("You can now play with Pifoo!"); break; case "rylan_coins": gameParameters.currentCharacter = "Rylan"; PlayerPrefs.SetString("currentCharacter", gameParameters.currentCharacter); ShowMessage("You can now play with Rylan!"); break; case "sothe_coins": gameParameters.currentCharacter = "Sothe"; PlayerPrefs.SetString("currentCharacter", gameParameters.currentCharacter); ShowMessage("You can now play with Sothe!"); break; case "wesker_coins": gameParameters.currentCharacter = "Wesker"; PlayerPrefs.SetString("currentCharacter", gameParameters.currentCharacter); ShowMessage("You can now play with Wesker!"); break; case "yuki_coins": gameParameters.currentCharacter = "Yuki"; PlayerPrefs.SetString("currentCharacter", gameParameters.currentCharacter); ShowMessage("You can now play with Yuki!"); break; } }
/// <summary> /// handle purchases, for real money or ingame currency /// </summary> public void HandleSuccessfulPurchase(string id) { //differ between ids set in the IAP Settings editor if (IAPManager.isDebug) { Debug.Log("HandleSuccessfulPurchase: " + id); } IAPObject obj = IAPManager.GetIAPObject(id); //get instantiated shop item based on the IAP id IAPItem item = null; if (ShopManager.GetInstance()) { item = ShopManager.GetIAPItem(id); } //if the purchased item was non-consumable, //additionally block further purchase of the shop item if (item != null && obj != null && obj.type != ProductType.Consumable) { item.Purchased(true); } switch (id) { //section for in app purchases case "coins": //the user bought the item "coins", //increase coins by 1000 and show appropriate feedback DBManager.IncreaseFunds("coins", 1000); ShowMessage("1000 coins were added to your balance!"); break; case "coin_pack": DBManager.IncreaseFunds("coins", 2500); ShowMessage("2500 coins were added to your balance!"); break; case "big_coin_pack": DBManager.IncreaseFunds("coins", 6000); ShowMessage("6000 coins were added to your balance!"); break; case "huge_coin_pack": DBManager.IncreaseFunds("coins", 12000); ShowMessage("12000 coins were added to your balance!"); break; case "no_ads": //no_ads purchased. You can now check DBManager.isPurchased("no_ads") //before showing ads and block them ShowMessage("Ads disabled!"); break; case "abo_monthly": //same here - your code to unlock subscription content ShowMessage("Subscribed to monthly abo!"); break; case "restore": //nothing else to call here, //the actual restore is handled by IAPManager ShowMessage("Restored transactions!"); break; //section for in game content case "bullets": //for virtual items, you could use DBManager's custom data option in order //to save amounts of virtual products. E.g. increasing bullets by 100: //int bullets = DBManager.GetPlayerData("bullets").AsInt; //DBManager.SetPlayerData("bullets", new SimpleJSON.JSONData(bullets + 100)); ShowMessage("Bullets were added to your inventory!"); break; case "health": ShowMessage("Medikits were added to your inventory!"); break; case "energy": ShowMessage("Energy was added to your inventory!"); break; case "speed": ShowMessage("Speed boost unlocked!"); break; case "speed_1": case "speed_2": case "speed_3": ShowMessage("Speed boost upgraded!"); break; case "bonus": ShowMessage("Bonus level unlocked!"); break; case "uzi": ShowMessage("Uzi unlocked!"); break; case "ak47": ShowMessage("AK47 unlocked!"); break; case "m4": ShowMessage("M4 unlocked!"); break; case "hat": ShowMessage("Hat unlocked!"); break; case "backpack": ShowMessage("Backpack unlocked!"); break; case "belt": ShowMessage("Ammo belt unlocked!"); break; case "jetpack": ShowMessage("Jetpack unlocked!"); break; case "booster": ShowMessage("Double XP unlocked!"); break; } }
// handles an online verification request and response from // our external server. www.text can returns true or false. // true: purchase verified, false: not verified (fake?) purchase IEnumerator WaitForRequest(Dictionary <string, string> dic) { //cache requested product id string id = dic["pid"]; //build POST request with transaction data WWWForm form = new WWWForm(); foreach (string key in dic.Keys) { form.AddField(key, dic[key]); } //create URL and execute until we have a respone WWW www = new WWW(serverUrl + verificationFileName, form); yield return(www); //check for URL errors if (www.error == null) { //we have a successful response from our server, //but it returned false (fake purchase) if (!bool.Parse(www.text)) { inventory.ErasePurchase(id); if (debug) { Debug.Log("The receipt for '" + id + "' could not be verified: " + www.text); } //PurchaseFailed("The receipt for '" + id + "' could not be verified."); id = GetIAPIdentifier(id); //remove purchase from the database and update item state if (DBManager.isPurchased(id)) { IAPItem item = null; if (ShopManager.GetInstance()) { item = ShopManager.GetIAPItem(id); } if (item) { item.Purchased(false); } DBManager.RemovePurchased(id); } yield break; } else { //successful response, verified transaction if (debug) { Debug.Log(dic["pid"] + " verification success."); } } } else { //we can't reach our external server, do nothing: //in this case we handle the purchase as without verification if (debug) { Debug.Log("Verification URL error: " + www.text); } } id = GetIAPIdentifier(id); PurchaseVerified(id); }
// check for purchases on online servers at billing initialization. // If a purchase is not registered, set local purchase state back to false private void VerifyReceipts() { //get list of old purchases: on iOS the saved and filtered transactions (avoiding duplicates), //on Android we use the old purchases list received from Google #if UNITY_IPHONE List <StoreKitTransaction> prods = FilterTransactions(StoreKitBinding.getAllSavedTransactions()); #endif if (prods == null || prods.Count == 0) { return; } //loop over all IAP items to check if a valid receipt exists for (int i = 0; i < ids.Length; i++) { //cache IAP id, //only verify purchased items string localId = ids[i]; string globalId = GetIAPIdentifier(localId); if (DBManager.isPurchased(globalId)) { //initialize item as faked and loop over receipts bool faked = true; for (int j = 0; j < prods.Count; j++) { //find corresponding transaction class string identifier = ""; #if UNITY_IPHONE StoreKitTransaction purchase = prods[j]; identifier = purchase.productIdentifier; #elif UNITY_ANDROID GooglePurchase purchase = prods[j]; identifier = purchase.productId; #endif if (identifier == localId) { //we found a receipt for this product on the device, //unset fake purchase and let our external //server decide what happens with this transaction faked = false; MakeRequest(purchase); break; } } //we haven't found a receipt for this item, yet it is //set to purchased. This can't be, maybe our external server //response or the database has been hacked with fake data if (faked) { IAPItem item = null; if (ShopManager.GetInstance()) { item = ShopManager.GetIAPItem(globalId); } if (item) { item.Purchased(false); } DBManager.RemovePurchased(globalId); } } } }