Auto renewable IAP subscription user flow & refreshing receipts - ios

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.

Related

Restore Auto renewal subscription in iOS

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")
}
}

Automatic Renewal Subscription works on fresh installs, not on updating Version

In version 1.0 ,the app content was free. Whereas in version 1.1 , I implemented Automatic renewal Subscription for some content and used IAPHelper for implementing InApp purchase. The Subscription is working fine in case of fresh installs whereas the users who updated the version they can access the paid content for free. If any one know ,what could be the reason behind this ,please help me.
this is my code for purchase
[[SubIAPHelper sharedInstance] requestProductsWithCompletionHandler:^(BOOL success, NSArray *products) {
if (success) {
inAppproducts=products;
SKProduct *product =products[0];
if (![[SubIAPHelper sharedInstance] productPurchased:product.productIdentifier]) {
/* purchasing UI shown here and after successful purchase content is downloaded */
}
else
{
/*Content downloaded as previously purchased and rest of the flow*/
}
}
}];
Any kind of help will be appreciable.

Restore purchases with RMStore

I'm trying to get the RMStore library to work, but therefore I need to be able to restore the in app purchases the user could have possible made. I'm aware of the method: restoreTransactionsOnSuccess: but with this method I don't get to know which in app purchases are restored.
[[RMStore defaultStore] restoreTransactionsOnSuccess:^{
} failure:^(NSError *error) {
}];
The above code is what I used, and it's working because in the logging I see the bought in app purchases. Do I miss something?
Could somebody point me to the right direction?
Thanks in advance!
As Merlea Dan mentioned, you can achieve this with notifications. The RMStore documentation states:
Payment transaction notifications are sent after a payment has been
requested or for each restored transaction.
Simply register as an observer and implement:
- (void)storePaymentTransactionFinished:(NSNotification*)notification
{
SKPaymentTransaction *transaction = notification.rm_transaction;
if (transaction.state == SKPaymentTransactionStateRestored)
{
// Do something
}
}
It's worth mentioning that a few others have requested to have restoreTransactionsOnSuccess return the list of restored product ids in the success block. You might want to subscribe to this issue in case it gets added.

Auto-renewing subscription: app-store completes transaction despite finishTransaction not called

I'm simulating purchases of an Auto-renewing subscription in my app on an iPhone. My issue is that the purchase is considered to be done by the App store while it is not.
Here is what is going on:
The user presses a button to purchase the renewing subscription
The user gives his iTunes password and confirms the purchase
The app submits the receipt received from the app store to my server to check validity
The server returns a "ok" or "not ok" string. The app calls finishTransaction only on "ok"
I have an issue when there is a network failure on step 3. I can't validate the receipt. But if the user tries to purchase a second time, the app store tells him that he has already subscribed, even though I didn't call the finishTransaction method to complete the purchase!
Is this an expected behavior? Shouldn't the app-store treat non-finished transactions as non-finished, or am I missing something?
I would welcome any suggestion to solve this issue.
-(void) userPurchase:(SKProduct*) product{
SKPayment *payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
NSLog(#"paymentQueue updatedTransaction");
for (SKPaymentTransaction * transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchasing:
break;
case SKPaymentTransactionStatePurchased:
[self recordSubscription:transaction];
break;
case SKPaymentTransactionStateFailed:
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
[self recordSubscription:transaction];
break;
default: NSLog(#"Default");
break;
}
};
}
-(void) recordSubscription:(SKPaymentTransaction*)transaction{
NSString *jsonObjectString = [self encode:(uint8_t *)transaction.transactionReceipt.bytes length:transaction.transactionReceipt.length];
NSMutableDictionary *params = [[NSMutableDictionary alloc] initWithObjectsAndKeys:jsonObjectString,#"receiptdata", nil];
[[AFNetworkSubClass sharedClient] postPath:#"myserver" params:params
success:^(AFHTTPRequestOperation *operation, id output) {
/* some code */
if([valstring isEqualToString:#"ok"]){
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
}
}failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"validation failed");
}
I think before you call recordSubscription method, you can call finish finishTransaction method to remove payment from payment queue because you have finished the purchase action with apple server.
If network failure make you can't validate app receipt, just record the receipt and validate the receipt to your own server again or validate when network is reachable another time.
I hope it can help you.
Users cannot purchase same subscription until it expires. In your case, the purchasing transaction was finished, but you didn't provide the service because of the network failure.
I used to have similar problem. You may need to ask the users to restore all completed transactions.
Then completeTransaction function will be called. Good luck!
If you haven't called finishTransaction that doesn't meant the transaction has not taken money from the user. All that call does is prevent StoreKit from notifying your App on the transaction every launch. Its just a means for you to tell StoreKit that you have finished with the transaction i.e. unlocked the users content and saved that to your backend etc.
So even with a network error you should be retrying your API call. If you close the App and reopen you should get a transaction update to indicate it has been 'purchased' giving you time to submit to your server again:)
This is the expected behaviour otherwise you would be double/triple billing users.
Just a heads up if your App doesn't fit within the following statement then you will not be able to use Auto-renewing Subscriptions. The following is from the App Review Guidelines;
11.15 Apps may only use auto renewing subscriptions for periodicals (newspapers, magazines), business Apps (enterprise, productivity,
professional creative, cloud storage) and media Apps (video, audio,
voice), or the App will be rejected
In case your app does fit into this bracket then what you could do setup your app so it considers itself to be temporarily "Subscribed" and it keeps trying to authenticate with the server (notify the user if it has been too long since it was connected to the internet).
I used to have issues like this with both the app store purchases and the game center. And I know this is going to sound like a non answer, but it worked for me.
I deleted the app and all its data from my device, restarted xcode and cleaned the project. Turned the device on and setup a fresh deployment. And the app started to receive the correct response from the App Store. I'm not sure if it was because something was cached on the device or if the App Store just needed more time to work correctly but things just fell into place after that.
Although I have never worked with subscription based purchases, nothing in your code stands out as incorrect as far as I can tell.
Good luck.

in app purchase "Error Domain=SKErrorDomain Code=0"

In my app I have an in-app-purchase where I allow to purchase some items. (with mkstorekit)
A think that I notice in iOS 7 is this:
if In general setting of my iPhone I'm logged with my Apple id and I proceed to purchase an item I have an error "Error Domain=SKErrorDomain Code=0" and the code finish in "canceled" block.
Then I logout from my apple id, I start again the app and when I must purchase an item it ask me id and password and its correct (naturally I insert a tester user data that I created in iTunes Connect)
At this time I stop my app and in my general setting is stored the tester user, then I start again my app and when I must purchase an item I have not the problem that I had with my personal apple id and it work fine.
In iOS 6 it didn't happen because if I was logged with my apple id in general settings and I tried to purchase an item, it showed me my apple id and asked me to insert the password (naturally it didn't work)
Finally this is not a big problem, I solve it because I used my tester account, but when I release my app in App Store can I have the same problem? If an user is logged in general setting the mkstorekit sto purchase and go in "canceled" block?
I show the method where is the cancel block:
- (void)purchaseFeature:(NSString *)productID
{
[[MKStoreManager sharedManager] buyFeature:productID
onComplete:^(NSString* purchasedFeature,
NSData* purchasedReceipt,
NSArray* availableDownloads)
{
[HUD hide:YES];
/*** CODE TO OPEN VIEW IF THE PURCHASE IS DONE ****/
}
onCancelled:^
{
NSLog(#"User Cancelled Transaction");
[HUD hide:YES];
/*** HERE FINISH MY CODE IF I'M JUST LOGGED IN GENERAL SETTING WITH MY APPLE ID ***/
}];
}

Resources