/// <summary> /// restore already purchased user's transactions for non consumable iaps. /// For Android/WP8 we use the received list for detecting previous purchases /// </summary> public static void RestoreTransactions() { #if UNITY_IPHONE OpenIAB.restoreTransactions(); #elif UNITY_ANDROID || UNITY_WP8 if (inventory == null) { RestoreFailed("Restoring transactions failed. Please try again."); return; } List <string> prods = inventory.GetAllOwnedSkus(); for (int i = 0; i < prods.Count; i++) { string id = GetIAPIdentifier(prods[i]); if (!DBManager.isPurchased(id)) { DBManager.SetToPurchased(id); } } RestoreSucceeded(); #endif //update ShopManager GUI items if (ShopManager.GetInstance()) { ShopManager.SetItemState(); } }
//just shows a message via our ShopManager component, //but checks for an instance of it first public void ShowMessage(string text) { if (ShopManager.GetInstance()) { ShopManager.ShowMessage(text); } }
//called when an purchaseFailedEvent happens, here we forward //the error message to ShopManager's error window (if present) void HandleFailedInventory(string error) { if (ShopManager.GetInstance()) { ShopManager.ShowMessage(error); } }
//called when an purchaseFailedEvent happens, //we do the same here void HandleFailedPurchase(string error) { if (ShopManager.GetInstance()) { ShopManager.ShowMessage(error); } }
/// <summary> /// restore already purchased user's transactions for non consumable iaps. /// For Android we use the received list for detecting previous purchases /// </summary> public static void RestoreTransactions() { #if UNITY_IPHONE StoreKitBinding.restoreCompletedTransactions(); #elif UNITY_ANDROID if (prods == null) { RestoreFailed("Restoring transactions failed. Please try again."); return; } for (int i = 0; i < prods.Count; i++) { string id = GetIAPIdentifier(prods[i].productId); if (!DBManager.isPurchased(id)) { DBManager.SetToPurchased(id); } } RestoreSucceeded(); #endif //update ShopManager GUI items if (ShopManager.GetInstance()) { ShopManager.SetItemState(); } }
/// <summary> /// Restore already purchased user's transactions for non consumable IAPs. /// For Android we use the received list for detecting previous purchases. /// </summary> public static void RestoreTransactions() { #if UNITY_IOS extensions.GetExtension <IAppleExtensions>().RestoreTransactions(OnTransactionsRestored); #else Product[] purchasedProducts = controller.products.all; foreach (Product product in purchasedProducts) { string id = product.definition.id; if (DBManager.isPurchased(id) || product.definition.type == ProductType.Consumable || !product.hasReceipt || String.IsNullOrEmpty(product.receipt)) { continue; } DBManager.SetToPurchased(id); } OnTransactionsRestored(true); #endif //update ShopManager GUI items if (ShopManager.GetInstance()) { ShopManager.SetItemState(); } }
// 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); } } } }
// Optionally: verify old purchases online. // Once we've received the product list, we overwrite // the existing shop item values with this online data public void OnInitialized(IStoreController ctrl, IExtensionProvider ext) { controller = ctrl; extensions = ext; if (validator && validator.shouldValidate(VerificationType.onStart)) { validator.Validate(); } if (ShopManager.GetInstance()) { ShopManager.OverwriteWithFetch(controller.products.all); } }
/// <summary> /// Increases player level by 1 which unlocks new shop items. /// <summary> public void LevelUp() { if (DBManager.GetInstance()) { int level = DBManager.IncreasePlayerData("level", 1); if (ShopManager.GetInstance()) { ShopManager.RefreshAll(); } Debug.Log("Leveled up to level: " + level + "! Shop Manager tried to unlock new items."); } }
//when the buy button has been clicked, here we try to purchase this item //maps to the corresponding purchase methods of IAPManager //only works on an actual mobile device public void Purchase() { #if UNITY_EDITOR if (type == IAPType.consumable || type == IAPType.nonConsumable || type == IAPType.subscription) { string editorText = "Calling purchase ID: " + this.productId + ".\nYou are not on a mobile device, nothing will happen."; if (ShopManager.GetInstance()) { ShopManager.ShowMessage(editorText); } Debug.Log(editorText); return; } #endif //differ between IAP type switch (type) { case IAPType.consumable: IAPManager.PurchaseConsumableProduct(this.productId); break; case IAPType.nonConsumable: IAPManager.PurchaseNonconsumableProduct(this.productId); break; case IAPType.subscription: IAPManager.PurchaseSubscription(this.productId); break; case IAPType.consumableVirtual: IAPManager.PurchaseConsumableVirtualProduct(this.productId); break; case IAPType.nonConsumableVirtual: IAPManager.PurchaseNonconsumableVirtualProduct(this.productId); break; } //hide buy button once a purchase was made //only when an additional buy trigger was set if (buyTrigger) { ConfirmPurchase(false); } }
// iOS version: receive StoreKitProducts. // Optionally: verify old purchases online. // Once we've received the productList, we create a // cross-platform version of it and overwrite // the existing shop item values with this online data private void ProductDataReceived(List <StoreKitProduct> list) { if ((verificationType == VerificationType.onStart || verificationType == VerificationType.both) && !string.IsNullOrEmpty(serverUrl)) { VerifyReceipts(); } productCache = new List <IAPArticle>(); for (int i = 0; i < list.Count; i++) { productCache.Add(new IAPArticle(list[i])); } if (ShopManager.GetInstance()) { ShopManager.OverwriteWithFetch(productCache); } }
/* * public void CloudMoolahButton() * { * extensions.GetExtension<IMoolahExtension>().FastRegister(DBManager.GetDeviceId(), (string moolahName) => * { * //register succeeded * extensions.GetExtension<IMoolahExtension>().Login(moolahName, DBManager.GetDeviceId(), (LoginResultState loginResult, string error) => * { * //login succeeded? * Debug.Log("Moolah Login State: " + loginResult + ", " + error); * }); * }, (FastRegisterError registerResult, string error) => * { * //register failed * Debug.Log("Moolah Register Error: " + registerResult + ", " + error); * }); * } */ /// <summary> /// Purchase product based on its product id. If the productId matches "restore", we restore transactions instead. /// Our delegates then fire the appropriate succeeded/fail/restore event. /// </summary> public static void PurchaseProduct(string productId) { #if UNITY_PURCHASING if (productId == "restore") { RestoreTransactions(); return; } #endif IAPObject obj = GetIAPObject(productId); if (obj == null) { if (isDebug) { Debug.LogError("Product " + productId + " not found in IAP Settings."); } return; } //distinguish between virtual and real products if (obj.editorType == IAPType.Virtual) { PurchaseProduct(obj); return; } #if UNITY_PURCHASING if (controller == null) { if (ShopManager.GetInstance()) { ShopManager.ShowMessage("Billing is not available."); } Debug.LogError("Unity IAP is not initialized correctly! Please check your billing settings."); return; } controller.InitiatePurchase(controller.products.WithID(productId)); #endif }
// Once we've received the productList, we create a // cross-platform version of it and overwrite // the existing shop item values with this online data // Optional: verify old purchases online. private void ProductDataReceived(Inventory inv) { //store fetched inventory for later access inventory = inv; //check for non-consumed consumables List <Purchase> prods = inv.GetAllPurchases(); for (int i = 0; i < prods.Count; i++) { IAPObject obj = GetIAPObject(prods[i].Sku); if (obj != null && obj.type == IAPType.consumable) { OpenIAB.consumeProduct(prods[i]); } } #if UNITY_ANDROID if ((verificationType == VerificationType.onStart || verificationType == VerificationType.both) && !string.IsNullOrEmpty(serverUrl)) { VerifyReceipts(); } #endif //build live cache productCache = new List <IAPArticle>(); for (int i = 0; i < ids.Length; i++) { if (inv.GetSkuDetails(ids[i]) != null) { productCache.Add(new IAPArticle(inv.GetSkuDetails(ids[i]))); } } if (ShopManager.GetInstance()) { ShopManager.OverwriteWithFetch(productCache); } }
// Android version: receive GoogleSkuInfos. // Optionally: verify old purchases online. // Once we've received the productList, we create a // cross-platform version of it and overwrite // the existing shop item values with this online data void ProductDataReceived(List <GooglePurchase> purchases, List <GoogleSkuInfo> list) { //store old purchases for verification purposes prods = purchases; if ((verificationType == VerificationType.onStart || verificationType == VerificationType.both) && !string.IsNullOrEmpty(serverUrl)) { VerifyReceipts(); } productCache = new List <IAPArticle>(); for (int i = 0; i < list.Count; i++) { productCache.Add(new IAPArticle(list[i])); } if (ShopManager.GetInstance()) { ShopManager.OverwriteWithFetch(productCache); } }
/// <summary> /// Manually triggering purchase confirmation after a PayPal payment has been made. /// This is so that the transaction gets finished and PayPal actually substracts funds. /// </summary> public void ConfirmPurchase() { if (string.IsNullOrEmpty(orderId)) { return; } ConfirmPurchaseRequest request = new ConfirmPurchaseRequest() { OrderId = orderId }; PlayFabClientAPI.ConfirmPurchase(request, (result) => { if (ShopManager.GetInstance() != null && ShopManager.GetInstance().confirmWindow != null) { ShopManager.GetInstance().confirmWindow.SetActive(false); } OnPurchaseSucceeded(result); }, OnPurchaseFailed); }
/// <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; } }
// 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); } } } }
/// <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; } }
// 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); }