MKStoreKit -isSubscriptionActive always return False - iphone

I am Using MKStoreKit for autorenewable subscription.
On Button's TouchUpInside method I am using following code..
if([[MKStoreManager sharedManager] isSubscriptionActive:kSubscriptionMonthlyIdentifier]){
//access to subscription feature
}else{
//ask user to buy
}
It always returns me false. And ask user to buy.
When I tried to buy again it shows me that You are already subscribed.
Is there something that I am doing wrong?
Is there any other method to check if subscription is active or not.
How should I know that subscription is renewed?
Any help would be appreciated.
Thanks in Advance.

1) You should initialize MKStoreKit by adding [MKStoreManager sharedManager]; in your applicationDidFinishLaunching.
2) You should check, that you correctly fill MKStoreKitConfigs.plist:
you must add your subscriptions in-app ids to Subscriptions dictionary (in format: "subs id" - "subs duration").
In apple documentation you could find info about auto-renewable subs:
every renewal of the auto-renewable subscription new transaction would be generated and send to your application. In theory, MKStoreKit must take care about all this stuff, and isSubscriptionActive should work.
Also, you could add button for restoring previous purchases. So, in your case, when user press buy button before calling -isSubscriptionActive call -restorePreviousTransactions, or you can add this button, so user can restore previous transaction^ if he already has bought subscription.
You could add observers for this notifications:
#define kSubscriptionsPurchasedNotification #"MKStoreKitSubscriptionsPurchased"
#define kSubscriptionsInvalidNotification #"MKStoreKitSubscriptionsInvalid"
First notification MKStoreKit generate, when Subscription was renewed (or purchased), second one - when renewing failed.

There is an open issue on the MKStoreKit GitHub that might be related to what you are experiencing. The people in that thread claim that after the app is initialized MKStoreKit always returns NO for isSubscriptionActive. It seems they were able to successfully use a previous release of MKStoreKit, though. You might want to try that.

Related

SwiftUI Auto-Renewable Subscription flow

I have implemented In-App Purchase Auto-Renewable Subscription into my app but I'm not sure if I have done it correctly as the app constantly asks for iTunes login.
In the user case: I am subscribed to monthly payments and my first month is over and expect it to auto-renew.
The flow I have at the moment is as follows...
(in App Delegate) Check receipt is valid
(if receipt IS valid) check all receipts for latest expiration date
(if expired - which seems to be the case after the first month is over) call SKReceiptRefreshRequest to get latest receipts. I have put a count check on this otherwise it gets stuck in a never ending loop.
Do final check to see if latest receipt is in-fact expired.
Is this the correct way to go about this? If not could you shine some light on this?
It all seems to be working fine apart from the annoyance of iTunes login. Which I guess would only be once a month outside of the Sandbox environment which isn't too bad but just want to be sure I'm doing this correctly.
Thanks
Don't call SKReceiptRefreshRequest. This request is usually only for the "restore purchases" mechanism. This is why you're getting the sign in dialogue. StoreKit will return to you as long as you an observer to the payment queue: https://developer.apple.com/documentation/storekit/skpaymentqueue/1506042-add so you should add yourself to the payment queue on app launch. Then keep that object aline to listen for changes for the entire app lifecycle. You will receive the renewals in the updatedTransactions callback https://developer.apple.com/documentation/storekit/skpaymenttransactionobserver/1506107-paymentqueue
Secondly, you should be doing receipt validation on your own server and not on the client as it will be susceptible to a MITM attack. You can also easily control the logic there and receive Server to server notifications which is best for managing subscriptions.

Unexpected behaviour with iAP on iOS

I make a fresh install of my App on my iOS device. (I had deleted it, signed off from the GC account).
After the App launch, 'paymentQueue: updatedTransactions:' gets called. It has one transaction with transactionState' == SKPaymentTransactionStatePurchased. (It's the only iAP my App has, in fact).
And then the GC Sandbox Login ViewController is presented.
Why does the iAP object get the Bought State, if I haven't logged in neither with an iTunes account or GC account?
I am not login on Game Center, that's for sure. But I'm not so sure about being being logged in with an iTunes Store Sandbox account. (Does that even exist?) If so, how do I log off?
I want to be able to test my App's 'Restore' button, but as the function already gets called, and the item is shown as bought, I can't... Why does even the function get called when I add the observer to the SKPaymentQueue? To check unfinished transactions? But it shouldn't do so as I'm not supposed to be logged into an iTunes Account...
Please, tell me any ideas you have, this is making me go crazy.
It sounds like you might be failing to call [SKPaymentQueue finishTransaction:] after your transaction is processed - the IAP system will keep attempting to deliver the transaction on every startup until you do that. So just add that call when you're done processing the transaction and you should be all set.

iPhone: in-App Purchase ,Buying once in sandbox

I Have a strange problem with iphone in app purchase! i created a product in the ituneconnect and then (using MKstorekit) i bought the product in sandbox environment .. and everything went will the first time... i removed the app from the device and created another test account.
and i found in the debugger mode that it sees the product as already purchased !!
tried everything from cleaning the project and all the tricks for the cache problems.. but still the problem occure .. is that normal?? or i'm doing something wrong?
please advice
since you are deleting the app in that case there is no chance that NSUserDefault will be storing this purchase so only thing that comes to my mind is the type of purchase... what type of purchase did you state in itunesconnect consumable , non-consumable or subscription? it could be because you stated subscription as purchase type(subscription purchases can only be bought once)... hope this helps..
this is what I have come across at http://www.raywenderlich.com/2797/introduction-to-in-app-purchases
StoreKit will prompt the user “are you sure?”, ask them to enter their
username/password (if appropriate), make the charge, and send you a
success or failure. They’ll also handle the case where the user
already paid for the app and is just re-downloading it, and give you a
message for that as well.
I fount the solution..
in app delegate i have to call
- (BOOL) removeAllKeychainData
in MKStoreManager, call it once at beginning, and then remove it from code, you don't want to accidently leave that on release :S

Clearing purchases from iOS in-app purchase sandbox for a test user

Does anyone have any ideas on how to reset and/or clear the iOS in-app purchase sandbox?
I have an app that I'm testing with the sandbox, and I'd like to test new purchases without having to create a new test user every time I purchase something.
If I don't do this, then I (of course) always get a message that the in-app purchase item has already been purchased when I click on my app's buy button.
IMO there are 3 things you can do to make testing non-consumables bearable:
You can have many test accounts associated to one email. Gmail for example lets you add a "plus" string to the email to create aliases for an address: so tester+01#gmail.com and tester+02#gmail.com both really just go to tester#gmail.com. Probably other email hosts do the same. When you create a test account you need to introduce: first name, last name, email address, password, secret question, secret answer, date of birth, and iTunes store country. You can put exactly the same data (including password) for tester+01#gmail.com and tester+02#gmail.com and you will have two test accounts. Finally, in your tester#gmail.com inbox you will receive two verification emails from Apple to confirm both test accounts.
Say that you have a non-consumable with product ID #"Extra_Levels". Instead of writing #"Extra_Levels" in all methods (requestProduct, purchaseProduct, ...), just write PRODUCT_ID1 and at some header file put #define PRODUCT_ID1 #"Extra_Levels" (with no semicolon!), then the preprocessor will search PRODUCT_ID1 and substitute it for #"Extra_Levels". Then creating a new non-consumable called #"Extra_Levels_01" and changing the #define will be as good as resetting the purchases for all your test users.
As appsmatics pointed out, you can test the correct behavior of your code when you buy a non-consumable IAP by first using a consumable IAP (so that test user can make as many purchases as needed) to get rid of some bugs. Of course, you should also test the code with the real non-consumable IAP after that.
You can't do this, as far as I know. The sandbox backend works like a real account-- once it's purchased, it's purchased (and thus you can test restore). You should do most of your development with the store stuff shimmed out, and then when you get to testing it for real, just expect to create several test accounts.
You can clear the purchase history for a tester so that you can continue to use the same sandbox Apple ID for ongoing testing. Clearing purchase history will delete all past auto-renewable subscriptions and non-consumables purchased by the selected testers in the sandbox environment. In-app purchases made by customers on the App Store are not affected.
To clear tester purchase history:
From Users and Access, under Sandbox, click Testers.
Click Edit.
Select the checkbox for each tester you want to modify and click Clear Purchase History.
Click Clear Purchase History in the dialog that appears.
Sandbox Apple IDs with a high number of purchases may take longer to clear. This action cannot be reversed.
I have 2 in app purchase items.
1 for production.
and the other for testing. when I need to "clear" I delete the in app item and create new one (15 seconds in itunes connect and 1 second to change the product id in code)
if i dont need to test "new user", i use the production in app item.
Deleting your app and reinstalling works also for sandbox testing. Depends on the app obviously, but I'm testing a subscription based app that only purchases during sign up at the moment so it's been the easiest solution.
Well, technically you don't need that.
If you get SKPaymentTransactionStateRestored, it is 100% equivalent to the app store verifying the user and granting him the purchase. I have a switch like:
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for( SKPaymentTransaction *purch in transactions )
{
switch( purch.transactionState )
{
case SKPaymentTransactionStateRestored:
info( "PURCHASE RESTORE" ) ;
// fall thru
case SKPaymentTransactionStatePurchased:
[[SKPaymentQueue defaultQueue] finishTransaction:purch];
// Do regular changes to app state for this purchase,
// register in keychain, etc.
break ;
//.. other cases
}
}
}
The question of having your app logic / take back the purchase is simple: if you're caching purchases in keychain, delete your keychain. If you're doing it some other how, just change your local app state to pretend like the user never purchased it before. The request to purchase dialog is still exactly the same, the only difference is when you punch YES, it gives you SKPaymentTransactionStateRestored instead of SKPaymentTransactionStatePurchased.
You can clear the purchase history for a sandbox Apple ID in App Store Connect.
To clear tester purchase history:
From Users and Access, under Sandbox, click Testers.
Click Edit.
Select the checkbox for each tester you want to modify and click Clear Purchase History.
Click Clear Purchase History in the dialog that appears.
see Documentation https://help.apple.com/app-store-connect/#/dev7e89e149d
Check out SimStoreKit. It's a "simulated version of the iPhone's StoreKit, for testing store UIs on the iPhone Simulator, or even on device without having to set up IAP in Connect."
SimStoreKit stores purchases in the user defaults under the key ILSimSKTransactions. So to clear all purchases you can do:
[[NSUserDefaults standardUserDefaults] removeObjectForKey:#"ILSimSKTransactions"]
On the simulator, you can simply remove your app and install it again.
I've successfully used SimStoreKit to debug my app's store front before testing with the sandbox. The beauty of this library is that it can be set-up to use the same class names as the real StoreKit framework (by doing #define ILSimReplaceRealStoreKit 1 before doing #include <ILSimStoreKit.h>).
In source files where I need to access StoreKit, I include this header file:
#import <TargetConditionals.h>
#if TARGET_IPHONE_SIMULATOR
#define kILSimAllowSimulatedStoreKit 1
#define ILSimReplaceRealStoreKit 1
#import <ILSimStoreKit.h>
#else
#import <StoreKit/StoreKit.h>
#endif
This has the effect of using SimStoreKit when I run on the simulator and the real StoreKit when I run on the device.
This is now possible for subscriptions purchases through Reset Eligibility
On the test iOS device, open Settings > Apple ID > Media & Purchases (or iTunes &/or App Store for iOS 13 and earlier). Under the Sandbox Account section, tap your highlighted Sandbox Apple ID then tap Manage to open the sandbox Subscription Management page.
If the subscription has expired, tap one of the options to resubscribe.
Once you resubscribe or when you have a subscription going on, you can use the Reset Eligibility button to reset and redeem another introductory offer (trial or discounted price).
Just keep using the same test account, restoring purchases as opposed to completing new ones. After all, whether you start a new purchase or restore an old one, YOUR APP will do the same thing (at least initially, maybe the user interface will update differently upon completion). Apple are the folks handling things differently in those different situations - don't worry about it.
Place your delivery logic in the SKPaymentTransactionStateRestored case within this method's implementation for testing:
- (void)paymentQueue:(SKPaymentQueue *)queue
updatedTransactions:(NSArray *)transactions;
Then be sure to put that delivery logic into the SKPaymentTransactionStatePurchased case.
At the end, because most of us are obsessive-compulsive to varying degrees, do a final test with a fresh account (not a big deal to make a second one for absolute certainty).
The final thing to note: consider apple's position. If there was a problem with developers having to waste time creating tens or hundreds of accounts to test IAP thoroughly, they would have solved the problem. There is no problem.
alternatively to create multiple test user solution you can create multiple test in app purchases in iTunes connect then you don't need to change a user account.

SKPaymentQueue addTransactionObserver asking for App Store password on startup after in-app purchase

My app is using in-app purchases, and most of my users can purchase just fine without any problems. For these folks, my app downloads the content after the purchase succeeds and they are happy.
However, for a growing number of my users, once they complete a successful in-app purchase they are being asked for their App Store password every time the app starts up after that. I believe this is happening on the call to:
[[SKPaymentQueue defaultQueue] addTransactionObserver:observer];
which I am calling on startup in accordance with step 6 in Apple's in-app purchase guide:
archived guide: https://web.archive.org/web/20130515222703/https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/StoreKitGuide/AddingaStoretoYourApplication/AddingaStoretoYourApplication.html
actual guide: https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Chapters/DeliverProduct.html#//apple_ref/doc/uid/TP40008267-CH5-SW4
My guess is that, for some reason, Apple's in-app purchase servers aren't registering that the transaction finished successfully - even though I call
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
when the transaction is completed and my content has been successfully downloaded.
2 questions:
Is anyone else seeing this?
Does anyone have a suggested fix?
BOUNTY EDIT:
Its a transaction which was made with a different Apple-ID. Thats why it cannot be finished unless you type in the right credentials into the dialog. The Question should be either:
How can I prevent such dead transactions (transaction has not been finished, user has no network, meanwhile changes App-ID)?
How can you prune the SkPaymentQueue?
I had the same problem.make sure that you call
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
for all three states of the transactions: SKPaymentTransactionStatePurchased, SKPaymentTransactionStateRestored, SKPaymentTransactionStateFailed.
I had the same problem of having the login prompt coming up at the call:
[[SKPaymentQueue defaultQueue] addTransactionObserver:observer];
It would also come up every now and then even when I wasn't using my app (on the home screen or in other apps), which was really annoying. Looking around, there seem to be so many suggested answers to this issue but I finally found a solution from a combination of what I've gathered.
Note: Before step 1, I had removed the test sandbox account in iTunes Connect. I'm not sure if that would affect the solution.
To solve the problem this is what I did:
Run your app from Xcode.
Wait for the prompt to come up. Type in the password for the account it wants and tap OK.
Press the Home button on the device.
Kill the app from Xcode.
Delete the app from the device.
Log out of iTunes & App Store in the Settings app.
Turn off the device and then turn it back on.
Purchase something from the App Store. When it prompts you, log in with a production Apple ID account. (I'm assuming you should be able to just log in with a production account in iTunes & App Store under the Settings app but this is how I did it).
Go back to Xcode and run your app again. (This should be a new install, as you deleted the app before.)
Wait for the login prompt to come up.
Tap Cancel. A dialog saying "Sign In Required. Tap Continue and sign in to check for downloads. [Environment: Sandbox]" should come up. This was a key difference from before. I never had this dialog come up when I pressed Cancel when it was asking me for the password.
Tap Continue.
Enter the password for the account.
That's it. From then on the login prompt stopped coming up whenever I ran my app and also stopped coming up at random times.
Hope this helps!
DO NOT DELETE THE ANSWER HERE. It was this particular Stackoverflow question that misled me and messed me up for days.
I'm putting this here because there are a lot of really bad answers that provide WRONG information on how to resolve the problem.
DO NOT:
Delete the sandbox test user. This makes it impossible to resolve the problem and you will have to contact Apple developer support to resolve manually.
If you delete the sandbox test user, when you are subsequently repeatedly prompted to log in as that user and complete the transaction, you can't, hence the name Endless Loop problem. Nor will you be able to add the deleted test user again; the developer portal says the user id has already been used.
Delete the App or re-install iOS or any other such nonsense. It has no effect, doesn't solve the problem and wastes a lot of time.
DO:
Call Finish for ALL transactions.
If one is interrupted for some reason, simply complete on a subsequent run of the App. The app will be repeatedly sent the payment queue notice until you call finish on it:
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
That's it, Finish all transactions! Else you will be sent to the hell of the Endless Loop of sign in requests every single time your App launches on that device.
There is a problem called the "endless loop". It was big issue back in the early days of auto renewables when, for about a week, the servers did not limit renewals to 5. A device that gets a transaction and doesn't call finishTransaction will get that transaction delivered to the device about once a week until that particular test user logs in and calls finishTransaction. If you switch to airplane mode you can 'clear' those transactions for another week - but they come back.
I suspect that this is a correct behaviour. When you set a delegate SKPaymentQueue try to check if there are some transactions to finalize. There may be no not finished transactions but the fact of checking requires to login in iTunes. And I think you can do nothing with it.
It generally has some sense, but it is pretty annoying for users who have set up asking for a password on each transaction (some child protection for instance). So the only way to struggle with it is to set delegate explicitly when you are about to request iTunes. For example you can add some button like "Restore my purchases". Not very beautiful but definitely less annoying.
If anyone is seeing this and is using GoogleMobileAds.framework then you may also need to call
[GADMobileAds disableAutomatedInAppPurchaseReporting];
in -application:didFinishLaunchingWithOptions:
Here's how I can consistently reproduce and resolve this issue:
iOS 8.4, development build of the app.
Use a test(sandbox) Apple Account.
Restore purchased transactions.
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions]
Immediately close the app.
Log out of test account (through settings)
Launch app
Now every time I launch the app it will pop up the "login" form. It doesn't matter if I enter the password or not. It doesn't matter if I make purchases or restore the purchases. It doesn't matter if I delete and re-install the app. Every time I launch the app, the iTune login is presented.
Fix: Hard reboot the device.
Here's what I don't know. Will this happen in a production environment (published app and actual apple login)? I hope not.
I had the same problem when testing IAP.
I tested with 3 test account. The app kept asking for password for both accounts. Even if I didn't touch any purchase/restore button or addTransactionObserver.
I think this is because some previous transaction has not finished correctly, but [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; can't help at all.
So here's what I did to solve this problem:
input the password for each account no matter how many times app
store ask for--I input 6 times for 3 account--until it never ask
again.
go to the setting and sign out the apple ID.
quit the app normally--don't terminate it in Xcode. then kill the process in task list.
remove the app from device (simply remove the app won't work it'll still ask for password )
reboot the device
Run the app again from Xcode/in your case reinstall the app from app store.
inspired by Expected sequence when using iTunes test user
Deleting and re-installing the app will remove any old transactions associated with another itunes account. If you are still seeing transactions posted to the notification queue, then you likely had some branch in your logic that did not call finishTransaction.
You need to to call finishTransaction on all transactions that are posted to paymentQueue:updatedTransactions:, even ones with SKPaymentTransactionStateFailed.
bugs related to in app purchases fixed in the iOS update 5.1.1
http://support.apple.com/kb/DL1521
Check for the following as I had it in my viewDidLoad method. I had an app rejected when apple wanted me to go from non-consumable to consumable, however I left the following line in my viewDidLoad method: (I thought the password prompt was a issue with swapping from the test user to normal user back to the test user)
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
Once removed the request for password stopped.
And I have left: [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
in and the app has now been approved just fine.
You know, I resolved this problem by making a modify in my updatedTransactions. I didn't add the [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; for all situations.
I resolved it by deleting the iTunes Connect test user account related with this issue. App actually asked once again to sign in, but then it disappeared (I've also deleted app, restarted iPhone and logged in as a normal non-sandbox user, made a real restore in other non-sandbox app and then launched the app)
You said:
However, for a growing number of my users, once they complete a successful in-app purchase they are being asked for their App Store password every time the app starts up after that. I believe this is happening on the call to:
[[SKPaymentQueue defaultQueue] addTransactionObserver:observer];
Note that the docs for add(_ observer: / addTransationObserver: state that the call may cause the user to authenticate with the App Store, so this appears to be expected behavior.
// Observers are not retained. The transactions array will only be synchronized with the server while the queue has observers. This may require that the user authenticate.
open func add(_ observer: SKPaymentTransactionObserver)
open func remove(_ observer: SKPaymentTransactionObserver)
So in addition to other answers here, it may be that you should stop calling add observer until you are ready to potentially display an App Store login prompt.