I have implemented auto-renewal subscription in iOS by swift language. The plan is for 1 year and has 3 days trial, the purchase is working perfectly. For checking if user has already buy a subscription or not, for that I am restoring purchases every time when app will open, and that also works. but problem is even subscription is expired or i have canceled from Settings > App Store > Sandbox account > Manage > Subscription then also restored the purchase successfully. I think should be not work right?
I am using SwiftyStoreKit package for handle in-app purchase.
How should I restore purchases?
SwiftyStoreKit.restorePurchases(atomically: true) { results in
if results.restoreFailedPurchases.count > 0 {
print("Restore Failed: \(results.restoreFailedPurchases)")
}
else if results.restoredPurchases.count > 0 {
print("Restore Success: \(results.restoredPurchases)")
}
else {
print("Nothing to Restore")
}
}
Related
I have one non-consumable IAP in iOS app (premium update) currently purchase ad restore are performed without receipt verification , i come along some articles that say that an IAP can be hacked in a jailbroken devices ,and i need to avoid this issue so
1- Should I call this verify method every time the user opens the app SwiftyStoreKit
let appleValidator = AppleReceiptValidator(service: .production, sharedSecret: "xxxxxxxxxxxx")
SwiftyStoreKit.verifyReceipt(using: appleValidator, forceRefresh: true) { result in
switch result {
case .success(let receipt):
print("success: \(receipt)")
case .error(let error):
print("Verify receipt failed: \(error)")
}
}
To make sure app is truly paid and IAP not hacked ?
2- Also inside the success block i should open the app premium features , while inside failure i should lock them ?
So I have a really basic concept of auto-renewable subscriptions in the app which unlocks PRO functionality.
I also have a non-consumable purchase which is a "one-time purchase subscription" which unlocks the PRO functionality once and forever.
What I can't wrap my head around is, when I call restorePurchases (using SwiftyStoreKit) it brings in all purchases that were ever made by a user. Those may include multiple purchases of the same subscription which were expired long ago.
What I do next is I call a verifyPurchase method on each of those restored purchases and that method checks expiration date of each subscription that was ever purchased and if it is expired - it takes away the PRO from the user by clearing keychain (as it thinks that the current subscription got expired):
case .expired(let expiryDate, let items):
log.info("Subscription has expired on \(expiryDate)")
log.debug(items)
self.activeSubscription = nil
self.clearStore()
seal.reject(SubscriptionServiceError.expired(on: expiryDate))
}
So what currently happens in my app is even if a user has an active subscription, if he or she tries to restorePurchases, there is a chance that verifyPurchase last purchase to verify would be an expired subscription, resulting in a canceled PRO version of the app for the user, even with an active subscription on board.
What would be the best practice to avoid this mistake and always verify the one and only right subscription?
From the SwiftyStoreKit docs:
let purchaseResult = SwiftyStoreKit.verifySubscription(
ofType: .autoRenewable, // or .nonRenewing (see below)
productId: productId,
inReceipt: receipt)
switch purchaseResult {
case .purchased(let expiryDate, let items):
print("\(productId) is valid until \(expiryDate)\n\(items)\n")
case .expired(let expiryDate, let items):
print("\(productId) is expired since \(expiryDate)\n\(items)\n")
case .notPurchased:
print("The user has never purchased \(productId)")
}
It looks like you would pass in the product Id to determine if a specific subscription is still active.
You probably don't want to do this on every app launch so you should cache the result somewhere - all you need is probably the expiration date.
I have implemented auto renewing subscription in my app. As per documentation, this should auto renew every few minutes up to six times a day. But it's not happening for my app.
I look for the subscription expiration date in the app receipt, and this works the first time, but it doesn't work afterwards. Is the app receipt not being automatically updated in the sandbox environment? It was my understanding that it should.
For anyone dealing with this problem in 2019 or later.
I also experienced subscriptions not being automatically renewed.
In my case auto renewals stopped working after buying 6 times within 8 hours.
Details:
A one month subscription in the Sandbox lasts 5 minutes and is automatically renewed 6 times (the entire purchase expires in 30 minutes). After that you have to buy again and the same process starts over.
For automatic renewals there is however a limit of buying 6 times for every 8 hour period.
This limit is per test user per 8 hours, so you can simply use a new test users to get around this.
Under
Settings -> iTunes & App Store
there is now a new option for Sandbox Account where you can log out and log in for a new test user. This took me a long time to figure out.
The following can be used to get the receipt from apple
static func getReceipt() -> String? {
guard let url = Bundle.main.appStoreReceiptURL,
let _ = try? Data(contentsOf: url) else {
print("no receipt exists")
return nil
}
do {
let receipt = try Data(contentsOf: url)
print("receipt-data: \(receipt.base64EncodedString(options:[]))")
return receipt.base64EncodedString(options: [])
}
catch {
print("catch error")
return nil
}
}
Instead of attempting to 'Build and Run' your App each time, try to reopen the App by reopening it on the device directly. I found this way, I can refresh the subscription receipts, based upon the prior sandbox receipt.
It appears that each time you 'Build and Run' via Xcode that any pending subscription renewals are reset. Potentially rebuilding your App is the reason that the subscription auto-renew is reset.
In a previous version of my app I had in app purchases(IAPs) working fine. In my latest release I am getting some strange inconsistencies reported to me by users.
The IAP simply grants access to the "Pro" version of the app that removes ads and allows users to play audio files while the app is backgrounded.
The Issue:
Users can successfully purchases the IAP within the app. They receive the message stating that the purchase was successful. However when they restart the app (as instructed to do by the successful purchase UIAlert) the Pro features are not unlocked.
When the users then use the "Restore Purchases" button, they receive an error message stating that "Nothing to restore" basically tell them that they have not purchased anything.
The interesting things is if they press the purchase button again, then they are told that they have already purchased the IAP and can re-download it for free. Again this doesn't unlock the Pro features.
Reported Cases
I have had numerous cases of this issue being reported to me. Users on both iOS 10 and 11 have encountered this issue. However, other users on both iOS 11 and 10 have also been able to purchase the upgrade without any problem. So I am a little confused on what could of caused the issue.
Tools
I am using SwiftyStoreKit 0.10.5 to manage my IAPs. I am on Xcode 9.0 using Swift 4.
Below is the code I use to purchase and restore purchases.
I have covered my bundleID with *** for safety reasons as I don't know what can be done with it.
// Purchase Product
func purchase(purchase: RegisteredPurchases) {
NetworkActivityIndicatorManager.NetworkOperationStarted()
SwiftyStoreKit.purchaseProduct(bundleID + "." + purchase.rawValue, completion: {
result in
NetworkActivityIndicatorManager.NetworkOperationFinished()
if case .success(let product) = result {
if product.productId == "com.************.RemoveAds" {
self.defaults.set(true, forKey: "NoAds")
}
if product.needsFinishTransaction {
SwiftyStoreKit.finishTransaction(product.transaction)
}
self.showAlert(alert: self.alertForPurchaseResult(result: result))
}
})
}
// Restore Purchases
func restorePurchases() {
NetworkActivityIndicatorManager.NetworkOperationStarted()
SwiftyStoreKit.restorePurchases(atomically: true, completion: {
result in
NetworkActivityIndicatorManager.NetworkOperationFinished()
for product in result.restoredPurchases {
if product.needsFinishTransaction {
SwiftyStoreKit.finishTransaction(product.transaction)
}
if product.productId == "com.************.RemoveAds" {
self.defaults.set(true, forKey: "NoAds")
}
print(product.productId)
self.defaults.set(true, forKey: "\(product.productId)")
}
self.showAlert(alert: self.alertForRestorePurchases(result: result))
})
}
Any information on what could be the cause of this I would really appreciate it. As I am at a bit of a lose at the moment on what the cause could be.
Thanks!
I am using the library RMStore - here's what I have currently.
1) Purchase auto renewable subscription & verify the returned receipt.
[[RMStore defaultStore]addPayment:[Environment environment].premiumProductIAPId success:^(SKPaymentTransaction *transaction) {
[[RMStore defaultStore].receiptVerificator verifyTransaction:transaction success:^{
//enable premium service
} failure:^(NSError *error) {
}];
} failure:^(SKPaymentTransaction *transaction, NSError *error) {
}];
2) On each app launch check the subscription is active for the date and enable the premium service if it is
RMAppReceipt *appReceipt = [RMAppReceipt bundleReceipt];
if (appReceipt){
NSInteger isActive = [appReceipt containsActiveAutoRenewableSubscriptionOfProductIdentifier:[Environment environment].premiumProductIAPId forDate:[NSDate date]];
//enable premium service if active
}
3) If user launches app on another device allow them to restore purchases by refreshing the receipt if it exists and checking if there is an active subscription in the purchases.
"In most cases, all your app needs to do is refresh its receipt and deliver the products in its receipt."
- That's from the guide. Here's the code:
[[RMStore defaultStore]refreshReceiptOnSuccess:^{
if ([receipt containsActiveAutoRenewableSubscriptionOfProductIdentifier:[Environment environment].premiumProductIAPId forDate:[NSDate date]]){
//enable
}else{
//no longer active
}
} failure:^(NSError *error) {
}];
My questions:
When RMStore checks if the subscription is active it can return no, I look in the receipt and it is correct and I am assuming it hasn't been auto renewed. When I go to purchase another subscription I get a message from itunes saying I'm already subscribed. On subsequent launch I see the new receipt. This indicates the receipt needed to be refreshed on launch, but I don't want to refresh it as it brings up the username & password pop up which is unnecessary. What is the best practice here?
Am I restoring the subscriptions for another device the right way? It seems to sometimes take more than one attempt to restore the subscriptions.
Is there any need apart from record keeping to store the subscriptions on my server?
I'm going to try and answer my question.
There may be a renewal which is not detected first thing on launch hence the subscription appears inactive.
I added an observer to listen for finished transactions (RMStore extends this StoreKit functionality).
Each time I receive this notification I check the (now updated) receipt for an active subscription and enable the premium service if there is one.
- (void)storePaymentTransactionFinished:(NSNotification*)notification
{
BOOL isActive = [[RMAppReceipt bundleReceipt] containsActiveAutoRenewableSubscriptionOfProductIdentifier:[Environment environment].premiumProductIAPId forDate:[NSDate date]];
if (isActive){
//enable premium
}
}
This seems to be working. If anyone has any other suggestions let me know.