Multiple receipt count for restoreCompletedTransaction inapp purchasing - ios

I have an autorenewable subscription. When the app is installed on a new device, Apple returns ALL previous purchase receipts, in this case since it is sandbox I get 6 receipts every time I install. The observer then sends the queue for restoredCompleted transactions. I have a method to send the transaction to my server for Apple verification, but it runs 6 times because of the 6 receipts. I really only want to deal with the LAST receipt sent.
So I am trying to count the transactions in the queue and ONLY verify the receipt when the count reaches 1.
Here is what I have so far:
- (void)paymentQueue: (SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
if (myQueue == nil) {
myQueue = [SKPaymentQueue defaultQueue];
}
NSLog(#"Transactions in Array in My Store %#", [queue transactions]);
tCount =myQueue.transactions.count;
NSString *transCount = [NSString stringWithFormat:#"%d",tCount];
for (SKPaymentTransaction *transaction in transactions)
{ switch (transaction.transactionState)
{ case SKPaymentTransactionStatePurchased:
[self completeTransaction: transaction];
break;
case SKPaymentTransactionStateFailed:
[self failedTransaction: transaction];
break;
case SKPaymentTransactionStateRestored:
if ([transCount isEqualToString:#"1"]) {
[self restoreTransaction: transaction];
}
else {
tCount--;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
});
}
default:
break;
}
}
return;
}
The restore cycles through, but the count does not decrement. This is probably something simple and dumb. Can someone show me how to decrement this count?
Thanks!

I wait until restoreCompletedTransactionsFinished before sending a receipt to my server to verify with Apple. This eliminates sending every receipt, which after a few months could get onerous. So, no need to decrement to transaction cout.

Related

InApp purchase incomplete payment

I have made a demo of IAP and in that I have registered my demo App in the apple account, I also have added the products info , description, app-id, title, priceTier, sandbox account for testing the payment process as described in the demo. I am facing two problems right now:-
a) Like for the very first time the popUp will ask for apple-id/password and I have provided both the values. It will show whether I want to confirm my payment and in that popup it will display the product name and price of the product. After clicking OK I am just getting nothing not even a mail from Apple, so how can I justify whether I have done a successful payment.
b) I also want to change my login id while buying the product. But every time it is by default taking my previous id and show me the password box only to enter the password. How can I reset this settings? Like I have tried reseting the simulator and again adding a new product in the my apple a/c in iTunes. So if any one got any idea please free to help. sharing my code below:-
-(void)fetchAvailableProducts{
if ([SKPaymentQueue canMakePayments])
{
SKProductsRequest *request = [[SKProductsRequest alloc]
initWithProductIdentifiers:
[NSSet setWithObject:kTutorialPointProductID]];
request.delegate = self;
[request start];
}
else
NSLog(#"Please enable In App Purchase in Settings") ;
}
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
NSArray *products = response.products;
if (products.count != 0)
{
_product = products[0];
NSLog(#"procudt info is %#",_product.localizedTitle);
// _buyButton.enabled = YES;
// _productTitle.text = _product.localizedTitle;
// _productDescription.text = _product.localizedDescription;
[self purchaseProducts];
} else {
//_productTitle.text = #"Product not found";
}
products = response.invalidProductIdentifiers;
for (SKProduct *product in products)
{
NSLog(#"Product not found: %#", product);
}
}
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction *transaction in transactions)
{
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased:
[[SKPaymentQueue defaultQueue]
finishTransaction:transaction];
NSLog(#"Transaction complete");
break;
case SKPaymentTransactionStateFailed:
NSLog(#"Transaction Failed");
[[SKPaymentQueue defaultQueue]
finishTransaction:transaction];
break;
default:
break;
}
}
}
First of all I am just fetching my product using my product id and I am getting response in the delegate method and if count >0 then "purchaseProducts" method will be called. But the thing is that my delegate method is not getting called, nothing is happening after I click on "OK" to buy the product. Please share your ideas.
Hi Follow the given steps, hope it works-
First check if the user can make payments or not-
-(void)checkForPayement{
//First check if user can make payments
if (![SKPaymentQueue canMakePayments]) {
// Show the error that user can't make payments.
}else{
// Make the product request on App Store
SKProductsRequest *productRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:IAP_PRODUCT_ID]]; // IAP_PRODUCT_ID is the product id which you got when you register the in-app-purchase
productRequest.delegate = self;
[productRequest start];
}
}
If the product is available the you will get response in the following method-
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
if (response.products.count > 0) {
SKProduct *validProduct = [response.products objectAtIndex:0];
[self purchase:validProduct];
} else {
// Product is not available so please cross check about the IAP_PRODUCT_ID
}
}
If the request is failed, you will get callback in following method-
-(void)request:(SKRequest *)request didFailWithError:(NSError *)error{
// Check here for the error
}
Start your purchase
- (void)purchase:(SKProduct *)product{
SKPayment *payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}
You will get callback in given method and you can check the status of the transaction-
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions{
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchasing:
DDLogVerbose(#"Purchasing");
break;
case SKPaymentTransactionStateFailed:
if (transaction.error.code == SKErrorPaymentCancelled) {
// User has cancelled the transaction
}
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
DDLogVerbose(#"Restored");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStatePurchased:
{
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
// Payment has been done and you should provide the purchased content or you may verify the receipt also
DDLogVerbose(#"Purchased");
}
break;
default:
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
}
}
}
If you want more security then you can also Verify receipt from app store
Solution of your problems
a. While testing the in-app-purchase in sandbox environment, you will not get any mail for payment confirmation, you can check status of your payment in Storekit updatedTransactions delegate method. And no balance will be deducted in Sandbox environment.After entering the sandbox account details, also check the iTunes account if you can login or not? If not, you id may not verified so verify your id.
b. Before trying in-app-purchase just logout the apple id from Settings -> iTunes & App Store. Tap on the apple id and click on logout. And when you initiate in-app-purchase enter the sandbox account credentials and don't login the sandbox account in actual production environment because it may invalidate your sandbox account.

iOS In-App Purchases handling when app goes to background (Unfinished interrupted transactions)

In my app In-App Purchases are working fine.
The issue I am facing is that;
if I initiate a subscription process and send app to background by pressing Home button on iPhone.
then In-App purchases API keeps on working and prompts user for iTunes credentials, while app is in background and user completes the process of purchasing the subscription successfully.
Now user has purchased the subscription through In-App API while app is in background but I am not getting that how to handle this scenario as, if user kills app without taking it back to foreground then the subscription purchase information will never be forwarded to our server and we will not be able to update user account in our server and user will not be able to use the special features of the app.
To send the latest receipt and In-App Purchase info to server, I get notified through below method:
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
NSLog(#"updated transaction");
for (SKPaymentTransaction *transaction in transactions)
{
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchased:
NSLog(#"transationStatePurchased");
// here I send data to server, but it never runs if app is in background.
[self completeTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
NSLog(#"transationStateFailed");
[self failedTransaction:transaction];
break;
default:
break;
}
}
}
But the above method always works when app is in foreground so the issue is how can I get notified about the complete transactions even if above method does not executes.
OK, So I have got the answer.
For others in easy words.
If you have an interrupted In-App Purchase which was due to any reason was completed but the receipt/information of that transaction of In-App Purchase, could not be sent to your server. Simply do this
Most Important: NEVER CALL [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; BEFORE SAVING THE RECEIPT ON YOUR SERVER, CALL IT WHEN PROCESS OF SAVING THE RECEIPT COMPLETES SUCCESSFULLY, SO THAT IF TRANSACTION GETS INTERRUPTED YOU WOULD HAVE INFORMATION REGARDING IT, NEXT TIME WHEN USER WILL START THE APP.
In your app delegate or in your first view controller or wherever you want, add this: (In my scenario I need to update user data before login so that user could use the special features of the app)
Add #import <StoreKit/StoreKit.h> to your .h file.
Add Delegate <SKPaymentTransactionObserver> to .h file.
Add observer
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
to - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
And delegate Method
-(void) paymentQueue:(SKPaymentQueue *) queue updatedTransactions:(NSArray *) transactions {
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchasing:
NSLog(#"Purchasing");
break;
case SKPaymentTransactionStatePurchased:
if ([transaction.payment.productIdentifier isEqualToString:kProductID]) {
NSLog(#"Purchased ");
UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:
#"Purchase is completed succesfully" message:nil delegate:
self cancelButtonTitle:#"Ok" otherButtonTitles: nil];
[alertView show];
}
[self sendReceiptDataToServer:transaction];
break;
case SKPaymentTransactionStateRestored:
NSLog(#"Restored ");
[self methodAfterTransactionSuccessful:transaction];
break;
case SKPaymentTransactionStateFailed:
NSLog(#"Purchase failed ");
break;
default:
break;
}
}}
-(void) sendReceiptDataToServer:(SKPaymentTransaction*)transaction {
NSLog(#"transaction successful");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}

updatedTransactions first time, call old queue

Firs time i tried to make a subscription this function call for all transactions, but after then if i tried to subscribe it calls just 1 state.
This code same as in apple developer documentation
- (void)paymentQueue:(SKPaymentQueue *)queue
updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchasing:
NSLog(#"purchasing in progress");
break;
case SKPaymentTransactionStateDeferred:
NSLog(#"Stade deferred");
break;
case SKPaymentTransactionStateFailed:
NSLog(#"State failed");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStatePurchased:
NSLog(#"Sucsess purchased");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
NSLog(#"restored");
break;
default:
// For debugging
NSLog(#"Unexpected transaction state %#",#(transaction.transactionState));
break;
}
}
}
updatedTransactions is called only for updated transactions (see Apple docs https://developer.apple.com/library/prerelease/ios/documentation/StoreKit/Reference/SKPaymentTransactionObserver_Protocol/)
transactions - An array of the transactions that were updated.
But on application start or first purchase iOS sends every transaction from SKPaymentQueue: defaultQueue.
https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Chapters/DeliverProduct.html:
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
So if app should view all transactions, it should request SKPaymentQueue: defaultQueue.

I have an in-app purchase set up, and it works, except when the user presses "cancel" it still ends up buying the product

When the iPad prompts you to "Confirm your In-App purchase", if you hit "Cancel" then it seems to still buy the product for the user. Because if I then try to buy it again, and then DO confirm the purchase, then it gives me an alert saying "You've already purchased this. Would you like to get it again for free?"
Note: this is all in the environmental sandbox, so maybe that is the reason? Here is my code:
- (IBAction)makePurchase:(id)sender {
SKPayment *payment = [SKPayment paymentWithProduct:_product];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
-(void)getProductInfo: (drawKitViewController *) viewController
{
if ([SKPaymentQueue canMakePayments])
{
SKProductsRequest *request = [[SKProductsRequest alloc]
initWithProductIdentifiers:
[NSSet setWithObject:self.productID]];
request.delegate = self;
[request start];
}
}
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
NSArray *products = response.products;
if (products.count != 0)
{
_product = products[0];
}
else {
}
products = response.invalidProductIdentifiers;
for (SKProduct *product in products)
{
NSLog(#"Product not found: %#", product);
}
}
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction *transaction in transactions)
{
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased:
[self unlockCreateAccount];
[[SKPaymentQueue defaultQueue]
finishTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
NSLog(#"Transaction Failed");
[[SKPaymentQueue defaultQueue]
finishTransaction:transaction];
break;
default:
break;
}
}
}
That is because your in app purchase product is Non-consumable. This means once you have successfully bought this product. You don't need to buy it again.
If you still try to buy it instead of restore previous bought product, you will get the alert "You've already purchased this. Would you like to get it again for free?".
BTW: You MUST supply user the entrance to restore previous bought product feature in your app. Otherwise your app will be rejected (my experience).
This is the line that executes the purchase operation
[SKPaymentQueue defaultQueue] addPayment:payment];
As long as you don't execute this code when the user cancels the operation, the purchase won't go through.
However, for a non-consumable purchase, if your test user has purchased the item at any time in the past you will get the message you described. The only things you can do is set up a new test Apple id for purchasing or create a different in-app product.

In-App Purchase: separate download of purchased items from transaction

I'm implementing In-App purchase with my server delivering the purchases in the form of zip files (not Apple server).
As downloadable files are quite big, 50Mb-500Mb I would like to let the user buy the items
and only when they want download and install the purchases.
So my implementation would be
By - Close Apple Transaction and Mark Item as Purchased - Trigger Download when you Want
Apple docs suggest you close transaction only after download completed:
I've been reading all Apple docs and I cannot see any specific comment about this saying that you must
close transaction only after download.
Am I going toward an Apple rejection?
Here the code: in case "SKPaymentTransactionStatePurchased" I call the method
"completeTransaction" which is closing the transaction itself. My question is about the
method:
[self provideContent:transaction.payment.productIdentifier];
Can I move this download from here in later action ofter "finishTransaction"?
- (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
{
[self recordTransaction:transaction];
[self provideContent:transaction.payment.productIdentifier];
// Remove the transaction from the payment queue.
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}

Resources