iOS in-app purchase. Identifying purchased product (non-consumable). - ios

I have a number of image templates for in-app purchase, that stored locally on device. I have created non-consumable in-app purchase in iTunes Connect. Now I cannot understand, how do I, kinda, assign a particular image to a product identifier? Because in in-app purchase there is no option to assign it to particular content. I am probably missing something here. From this question
that I have to create new product identifier for each image-template I sell. But this made it even more confusing, coz I cannot understand how to link each image to each product identifier.
Thank you.

You either have to ship all the images with your app and hard code the mapping of product identifier to image or load the product ids from the app store and use an external web site or server to upload the images for the identifiers.
It is a shame that apple do not let you assign meta data to the products which you can upload with the product identifiers. For non consumable items you can now at least host the content on the apple servers.
So unfortunately at the moment its hard code it or use an external server you can add images on between app releases.

In iTunes Connect when you create your in-app purchase, you give to each purchase a Product ID like com.example.purchase1
After in code you should check which was bought like following:
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
for (SKPaymentTransaction * transaction in transactions) {
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchased:
[self completeTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
[self failedTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
[self restoreTransaction:transaction];
default:
break;
}
};
}
- (void)completeTransaction:(SKPaymentTransaction *)transaction {
//get and pass productIdentifier of transaction
[self provideContentForProductIdentifier:transaction.payment.productIdentifier];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
- (void)provideContentForProductIdentifier:(NSString *)productIdentifier {
if ([productIdentifier isEqualToString:#"com.example.purchase1"]) {
//unlock purchase1
}

Actually You should create nonconsumable in-app for every image-template you want to sell.
Lets think that you have 4 different image-template.
Your product identifiers will be imageSet1,imageSet2,imageSet3,imageSet4.
And in your application you have 4 template names are Spring,Summer, Autumn, Winter.
When user using your app want to buy Spring, You will send a request to appstore that this user wants to buy imageSet1(it is Spring in your app). When transaction is successfull, you will activate credentials for Spring images.
When user change the device or reinstall your app later, When he tries to buy Spring theme again, you have to supply restore option in buy dialog.(it is a must. If you dont supply this option your app will bi rejected.)
When he press restore apple will return you that this user bought this before and you will activate Spring theme.

Related

How to check user already purchased or not using In-App Purchase in iOS based on apple ID

I want to know how to check if already purchased or not based on apple id as I don’t have login in my app.
Below is my code:
(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
}
Any help how to proceed?
Your app starts the process by calling the
restoreCompletedTransactions method of SKPaymentQueue class
This sends a request to the App Store to restore all of your app’s completed transactions. If your app sets a value for the applicationUsername property of its payment requests, as described in Detecting Irregular Activity, use the
restoreCompletedTransactionsWithApplicationUsername:
method to provide the same information when restoring transactions.
Your transaction queue observer is called with a status of SKPaymentTransactionStateRestored for each restored transaction, as described in Waiting for the App Store to Process Transactions. The action you take at this point depends on the design of your app.
NSMutableArray *productIDsToRestore = <# From the user #>;
SKPaymentTransaction *transaction = <# Current transaction #>;
if ([productIDsToRestore containsObject:transaction.transactionIdentifier]) {
// Re-download the Apple-hosted content, then finish the transaction
// and remove the product identifier from the array of product IDs.
} else {
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
Read the Apple Documentation here.
You can get a list of previous transactions from the store with -[SKPaymentQueue restoreCompletedTransactions]. The restored transactions can be verified just like normal transactions.

base64EncodedStringWithOptions for receipt data returns nil

I want to validate my in app purchases using server side.
So I use the following code:
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction * transaction in transactions) {
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchased:
{
NSData *reciptData = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];
if(reciptData) {
NSDictionary *parameters = #{#"receipt_data" : [reciptData base64EncodedStringWithOptions:0]};//App crashes here -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[0]
}
}
break;
default:
break;
}
};
}
The weirdest thing is that application crashes on one iPad with iOS 8.0.2 and does not crash on other with the same iOS version.
The worst thing is that I do not have access to device on which application crashes.
As far as I understand - base64EncodedStringWithOptions: returns nil but I don't know why.
Can anyone help me?
I have seen this empty receipt issue before where devices are running some form of in-app purchase cracking program such as "IAP Free" or "IAP Cracker" which implies the device has been jailbroken and an in-app purchase cracking tool has been installed. I would make sure the device where the application crashes is not running some form of in-app purchase cracking tool. The other thing you can do is ignore an empty receipt - but don't return any goods to avoid the crash or return a good that is only available locally. This depends on how you want to react to someone cracking in-app purchases - sometimes it's good to pretend it all works locally but limit features from your server.

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.

iOS StoreKit - simulating incomplete and interrupted in-app purchases

I have an issue with a live app where incomplete purchases are being mishandled. I am trying to test my new code to make sure that this will be taken care of, so I download the live app, cause the problem, then load my development app (or Ad-Hoc app) hoping that the StoreKit Observer will catch the incomplete purchase notification. No matter how I do this (development or Ad-Hoc) the observer does not fire a notification.
My general question is: How can I simulate incomplete and interrupted purchases in the App Store testing environment?
My more specific question is: Can I simulate the specific issue where the user must leave the app to confirm their current credit card pin number on the app store?
According to this helpful page:
Test an Interrupted Transaction
Set a breakpoint in your transaction queue observer’s
paymentQueue:updatedTransactions: method so you can control whether it
delivers the product. Then make a purchase as usual in the test
environment, and use the breakpoint to temporarily ignore the
transaction—for example, by returning from the method immediately
using the thread return command in LLDB. Terminate and relaunch your
app. Store Kit calls the paymentQueue:updatedTransactions: method
again shortly after launch; this time, let your app respond normally.
Verify that your app correctly delivers the product and completes the
transaction.
Hope this helps someone.
To your general question:
SKPaymentTransaction provides several transaction states like SKPaymentTransactionStateFailed
According to the Documentation you can check out the error property to see what happened.
For example you can check it in -(void)paymentQueue:(SKPaymentQueue *)updatedTransactions:(NSArray *)transactions callback like so
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{
for (SKPaymentTransaction * transaction in transactions) {
switch (transaction.transactionState)
{
case SKPaymentTransactionStateFailed:
...
break;
default:
break;
}
};
}
Hope this helps.

How to recover missing SKPaymentTransactions?

I have one test account that has exactly two purchases in its transaction history. Both products are Non-Consumable.
I logged in on iPad 1 and bought product A.
Then I logged out of iPad 1 and logged in on iPad 2 and bought product B.
Then I attempted to restore previous transactions using [[SKPaymentQueue defaultQueue] restoreCompletedTransactions]; to unlock product A on iPad 2.
When the call comes back, only product B is in the list of restorable transactions.
Additionally, when I attempt to buy product A again on iPad 2 using
SKPayment *payment = [SKPayment paymentWithProduct:productA];
[[SKPaymentQueue defaultQueue] addPayment:payment];
I get a popup saying 'You've already purchased this. Tap OK to download it again for free.'
If I've already purchased product A, why isn't it in the list of products when I try to restore previous purchases? Why do I have to have the user attempt to purchase it again?
EDIT:
I've discovered that it doesn't matter which iPad I use (iPad 1 or iPad 2), only product B shows up in the list of restorable transactions and product A needs to be repurchased.
EDIT:
I extrapolated the product request, restore and purchase work into its own project with the same bundle ID and product IDs as the original.
Now nothing appears in the list of restorable transactions but the same popup appears when I attempt to buy either product.
Another trip down the rabbit hole with broken Apple tools.
EDIT:
The inspiration for this investigation is a rash of user complaints about missing entitlements. This largely started to become an issue when users were switching from their iPad 2s to new iPad 3s. This isn't consistent among all my users, but enough of them have raised a stink about it to make it a priority, and Apple customer support has directed them back to me, but I'm not sure this is a problem I can fix, especially since Apple insists on being the cash register for in-app purchasing.
I have it on good authority that this is a bug in Apple's server software somewhere.
This message 'You've already purchased this. Tap OK to download it again for free.' is usually appear when you add a payment transaction to the default queue and didn't finish the transaction so the StoreKit suppose that the user did purchased the item but it hasn't been downloaded by your application , so make sure that you are delivered the item to the user and finish the transaction ... I'v got a way to retrieve the user purchased items and its work good for me .. try it
- (void) checkPurchasedItems
{
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}//You Call This Function
//Then this delegate Function Will be fired
- (void) paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
purchasedItemIDs = [[NSMutableArray alloc] init];
NSLog(#"received restored transactions: %i", queue.transactions.count);
for (SKPaymentTransaction *transaction in queue.transactions)
{
NSString *productID = transaction.payment.productIdentifier;
[purchasedItemIDs addObject:productID];
}
}
the purchasedItemIDs will contain all the product IDs that the user purchased it.

Resources