I've been getting some reports from our users that we're charging them but not creating the account for them.
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction *transaction in transactions)
{
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchased:
[self didPurchaseTransaction:transaction queue:queue];
break;
and on the didPurchaseTransaction method, I store the logs to the server.
but I'm not getting the logs from these users. So for some reason, that didPurchaseTransaction were never called. We asked for their receipt from Apple, and we verified that we actually charge them.
This issues are not consistent as I'm never able to reproduce it myself. But every week or two, we get these reports. Anyone experienced this issue?
Thanks!
apparently this is a known issue. I've found some people having the same issues. Apple rep also confirmed this problem. The issue is happening if the user has to be taken out of the app to update the payment information during the transaction. Upon completion it's not sending the SKPaymentTransactionStatePurchased status.
Related
For Apple in app purchase, it seems we are supposed to observe the transactions queue:
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased:
...
}
but we also need to validate the receipt (parsing the content at [[NSBundle mainBundle] appStoreReceiptURL]];). But then... can we just take the shortcut to look at the receipt directly? What's the difference?
To validate the receipt you should create custom server and send the receipt to the server. Also receipt contains all transactions and you should find last transaction in it, it is not very easy way to show to a user some message about purchasing.
I use transactions for statistics and for quick information and the receipt for validation of purchasing.
Also you can use receipt for double check of purchasing, if the receipt is not contain appropriate transaction, you can restrict access to the content.
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.
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.
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.
I have two iPhones I use for testing my app.
On Device A:
NSLog(#"Unfinished Transactions: [%i]", [[SKPaymentQueue defaultQueue].transactions count]);
Which outputs: "Unfinished Transactions: [0]"
When when I do a:
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions]
the function
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
gets called with 17 items in the transactions array, each flagged as RESTORED. So, I loop over the set and call [[SKPaymentQueue defaultQueue] finishTransaction: transaction] on each.
After processing each item, (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue is called, and [[queue transactions] count] is equal to 4, not 17, which is something of a surprise.
If I re-run [[SKPaymentQueue defaultQueue] restoreCompletedTransactions] on this device, I repeatedly get the same result - despite having told each transaction I want it removed from the queue, updatedTransactions gets called with 17 items to process.
On device B,
$NSLog(#"Unfinished Transactions: [%i]", [[SKPaymentQueue defaultQueue].transactions count]);
Still outputs "Unfinished Transactions: [0]"
When I run
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions]
, the function
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
NEVER gets called. Eventually
- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
is called, with
[[queue transactions] count]
equal to 0.
What could be causing this? It's almost like the payment queue is linked to the device ID, not the Test Account Apple ID. Also, please forgive the terrible formatting of this question, I'm still getting used to StackOverflow. :)
This is a sandbox environment bug. I have had exactly the same issue that the
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
was never called. Only the paymentQueueRestoreCompletedTransactionsFinished arrived after the call to restoreCompletedTransactions.
I have checked it with an Apple engineer. He confirmed that my code is indeed correct and I should receive the updatedTransactions call. He said that the fact this is not happening in the sandbox environment is apparently a bug (he was not surprised about it at all).
Honestly there is not much you can do about it. After he confirmed my code is correct I took the risk and released the App. It all works as expected with the real iTunes Connect server.
So your best (maybe only) bet is to make sure you have it all set up as expected and go ahead and test it against the live server. Good luck! It worked for me.