// set product to purchased after successful verification (or without) // For non consumable IAPs or subscriptions, alter database entry public void PurchaseVerified(string id) { if (!IAPObjects.ContainsKey(id)) { id = GetIAPIdentifier(id); } if (!IAPObjects.ContainsKey(id)) { return; } IAPObject obj = IAPObjects[id]; //don't continue if the product is already purchased, //for example if we just want to verify an existing product again if (DBManager.isPurchased(id)) { return; } if (obj.type != ProductType.Consumable) { DBManager.SetToPurchased(id); } purchaseSucceededEvent(id); }
/// <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(); } }
/// <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(); } }
/// <summary> /// Overload for purchasing virtual product based on its product identifier. /// </summary> public static void PurchaseProduct(IAPObject obj) { string productId = obj.id; //product is set to already owned, do nothing if (DBManager.isPurchased(productId)) { OnPurchaseFailed("Product already purchased."); return; } //check whether the player has enough funds bool didSucceed = DBManager.VerifyVirtualPurchase(obj); if (isDebug) { Debug.Log("Purchasing virtual product " + productId + ", result: " + didSucceed); } //on success, non-consumables are saved to the database. This automatically //saves the new substracted fund value, then and fire the succeeded event if (didSucceed) { if (obj.type != ProductType.Consumable) { DBManager.SetToPurchased(productId); } purchaseSucceededEvent(productId); } else { OnPurchaseFailed("Insufficient funds."); } }
/// <summary> /// purchase non-consumable virtual product based on its product id /// </summary> public static void PurchaseNonconsumableVirtualProduct(string productId) { //if already owned, do nothing if (DBManager.isPurchased(productId)) { PurchaseFailed("Product already purchased."); return; } //check whether the player has enough funds bool didSucceed = DBManager.VerifyVirtualPurchase(GetIAPObject(productId)); if (instance.debug) { Debug.Log("purchasing non-consumable virtual product " + productId + " result: " + didSucceed); } //on success, non-consumables are being saved to the database //this automatically saves the new substracted fund value to the database //and fire the succeeded event if (didSucceed) { DBManager.SetToPurchased(productId); purchaseSucceededEvent(productId); } //otherwise show fail message else { PurchaseFailed("Insufficient funds."); } }
// set product to purchased after successful verification (or without) // Consumable IAPs must be consumed // For non consumable IAPs or subscriptions, alter database entry private void PurchaseVerified(string id) { if (!IAPObjects.ContainsKey(id)) { return; } IAPObject obj = IAPObjects[id]; if (obj.type == IAPType.consumable) { OpenIAB.consumeProduct(inventory.GetPurchase(obj.GetIdentifier())); //local id return; } //don't continue if the product is already purchased, //for example if we just want to verify an existing product again if (DBManager.isPurchased(id)) { return; } if (obj.type == IAPType.nonConsumable || obj.type == IAPType.subscription) { DBManager.SetToPurchased(id); } purchaseSucceededEvent(id); }
/// <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> /// 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(); } } } }
// 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> /// Returns the next unpurchased upgrade id of a product. /// </summary> public static string GetNextUpgrade(string productId) { string id = GetCurrentUpgrade(productId); IAPObject obj = GetIAPObject(id); if (!DBManager.isPurchased(id) || obj == null || string.IsNullOrEmpty(obj.req.nextId)) { return(id); } else { return(obj.req.nextId); } }
/// <summary> /// Returns the last purchased upgrade id of a product, /// or the main product itself if it hasn't been purchased yet. /// </summary> public static string GetCurrentUpgrade(string productId) { if (!DBManager.isPurchased(productId)) { return(productId); } string id = productId; List <string> upgrades = GetIAPUpgrades(productId); for (int i = upgrades.Count - 1; i >= 0; i--) { if (DBManager.isPurchased(upgrades[i])) { id = upgrades[i]; break; } } return(id); }
private void OnProductsRetrieved(BillingResult result) { if (!result.IsSuccess) { OnSetupFailed(result.Response + ", " + result.Message); return; } AndroidInventory inventory = AndroidInAppPurchaseManager.Client.Inventory; List <GoogleProductTemplate> list = inventory.Products; List <GooglePurchaseTemplate> purchases = inventory.Purchases; string globalId = null; string storeId = null; for (int i = 0; i < list.Count; i++) { storeId = list[i].SKU; globalId = IAPManager.GetIAPIdentifier(storeId); if (!products.ContainsKey(globalId)) { products.Add(globalId, new ProductDescription(storeId, new ProductMetadata(list[i].LocalizedPrice, list[i].Title, list[i].Description, list[i].PriceCurrencyCode, (decimal)list[i].Price))); } //check for non-consumed consumables if (storeId in purchases) { IAPObject obj = IAPManager.GetIAPObject(IAPManager.GetIAPIdentifier(purchases[i].SKU)); if (obj != null && obj.type == ProductType.Consumable) { AndroidInAppPurchaseManager.Client.Consume(purchases[i].SKU); continue; } } bool storeResult = inventory.IsProductPurchased(storeId); if (storeResult == true) { products[globalId] = new ProductDescription(storeId, products[globalId].metadata, inventory.GetPurchaseDetails(storeId).Token, inventory.GetPurchaseDetails(storeId).OrderId); } #if !UNITY_EDITOR //auto restore products in case database does not match if (storeResult != DBManager.isPurchased(globalId)) { if (storeResult) { DBManager.SetToPurchased(globalId); } else { DBManager.RemovePurchased(globalId); } } #endif } callback.OnProductsRetrieved(products.Values.ToList()); }
public void OnPurchaseSucceeded(IOSStoreKitResult item) { switch (item.State) { case InAppPurchaseState.Deferred: return; case InAppPurchaseState.Restored: ProductDefinition definition = definitions.FirstOrDefault(x => x.storeSpecificId == item.ProductIdentifier); if (definition == null || definition.type == ProductType.Subscription || DBManager.isPurchased(definition.id)) { return; } break; case InAppPurchaseState.Failed: OnPurchaseFailed(item.Error.Message, item.Error.Code); return; } string transactionId = item.TransactionIdentifier; #if UNITY_EDITOR //allow for multiple test purchases with unique transactions transactionId = (System.DateTime.UtcNow - new System.DateTime(1970, 1, 1, 0, 0, 0, System.DateTimeKind.Utc)).TotalSeconds.ToString(); #endif callback.OnPurchaseSucceeded(item.ProductIdentifier, item.Receipt, transactionId); }
// 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); } } } }