Pretty much what the title says. The code works fine for all my development devices in the sandbox environment and for a majority of my users. However, there are some users reporting that the download process doesn't move beyond the waiting state (SKDownloadStateWaiting), even when left through the night. Some do manage to get the download started after a few tries (closing the app completely and going through restore purchases feature), so it does look to be completely random.
Here is the code I'm using to manage downloading:
- (void)paymentQueue:(SKPaymentQueue *)queue updatedDownloads:(NSArray *)downloads
{
SKDownload *download = [downloads objectAtIndex:0];
SKPaymentTransaction *transaction = download.transaction;
// Keep track of download status
switch (download.downloadState) {
case SKDownloadStateActive:
// Present time remaining and percentage
break;
case SKDownloadStateWaiting:
// Present "Waiting..." label
break;
case SKDownloadStateFinished:
[self purchaseNonconsumableAtURL:download.contentURL forProductIdentifier:productIdentifier];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKDownloadStateFailed:
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKDownloadStateCancelled:
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
default:
break;
}
}
Any help would be much appreciated.
You can try to start a download that's in SKDownloadStateWaiting by calling
[[SKPaymentQueue defaultQueue] startDownloads:[NSArray arrayWithObject:download]];
My application would always get downloads that were in a permanent "waiting" state when it attempted to resume earlier transactions. I edited the paymentQueue updatedDownloads function so that whenever it's called with a download that's in a waiting state, it will pass that download to startDownloads, and this seemed to fix the issue.
It's worth checking whether your users have a 12 hour (AM/PM) or 24 hour time setting on their devices, especially if you're performing receipt validation locally and ensuring certain date fields are present / valid (e.g., purchase date). I had problems with receipts not being validated with users who have 12 hour time setting. To make this even more of an edge case, the receipt validation for 12 hour time users will only fail if the purchase has been made in the afternoon in GMT. This is certainly a thing worth testing in sandbox.
If you use DateFormatter / NSDateFormatter in your receipt validation at all, ensure you set the locale to one that uses 24 hour time (e.g., en_GB) to ensure it uses a 24 hour time format.
For example, in Swift 3, my receipt validator's time formatter property would be something like this:
private let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.locale = Locale(identifier: "en_GB")
formatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"
return formatter
}()
Related
I'm having issues finding out how to code my button
Enter.isenabled = False
to become
Enter.isenabled = true
after the auto renew subscription is purchased. any insights on this would be awesome this is the last step I need before I can get my app going!
Assuming this is iOS. Its really pretty straight forward, you need to implement an SKPaymentQueue delegate with a paymentQueue:updatedTransactions: method.
In there you have to handle the state changes for SKPaymentTransactions. The possible states are SKPaymentTransactionStatePurchasing, SKPaymentTransactionStatePurchased, SKPaymentTransactionStateFailed, SKPaymentTransactionStateRestored, SKPaymentTransactionStateDeferred.
switch(transaction.state) {
case SKPaymentTransactionStatePurchasing:
disableUI();
break;
case SKPaymentTransactionStatePurchased:
unlockContent();
case SKPaymentTransactionStateRestored:
case SKPaymentTransactionStateDeferred:
case SKPaymentTransactionStateFailed:
enableUI();
queue.finishTransaction(transaction);
}
The above is a bad psuedo-code but should illustrate the basic idea.
I have in-app purchases in my app, and new to iOS 8 are "deferred" transactions, partially described in a tech note
I understand what it does and that I need to not block the UI, and update my UI to reflect that the transaction state is deferred. But what am I supposed to place in the method -(void)transactionDeferred:(SKPaymentTransaction *)transaction to disregard the transaction for the time being?
Do I only have update the UI? Also what should the content of the UI be? Do I need to replace the price label with something like "Your purchase is deferred"? I don't think there is a way to test this, at least I haven't seen anything about it with my sandbox test account. If there was a way to go through the process and see how it works, it would make a lot more sense to me.
What I am doing is:
Stopping indicator animation
Activating buy and restore buttons
Showing an alert:
Waiting For Approval
Thank you! You can continue to use Altershot while your purchase is pending an approval from your parent.
I watched WWDC 14 video. Apple says you should't block UI and allow to click on buy button again. I think we need this in case parent miss alert, so child can send one more.
What I know is that we should not call following method for deferred transactions:
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
The code bellow will allow you to check if the product ID you want to sell is in deferred mode. Use it to update the UI accordingly.
if ([[SKPaymentQueue defaultQueue].transactions count] > 0) {
for (SKPaymentTransaction *transaction in [SKPaymentQueue defaultQueue].transactions) {
if ([#"your.product.id" isEqualToString:transaction.payment.productIdentifier]) {
if (transaction.transactionState == SKPaymentTransactionStateDeferred) {
// update UI that you are still waiting for parent approval. You'll get "PURCHASED" if parent approved or "FAILD" if parent declined or 24 hours passed since request.
}
break;
}
}
}
I check & process IAP receipts (of consumables) on server side, and only call [[SKPaymentQueue defaultQueue] finishTransaction:transaction] on the app when it gets an okay back.
When the server does not return this okay (for whatever reason), the app correctly won't finish the transaction.
My question now is: Is there anything the app needs to do to receive the receipt again for a retry, or does iOS take care of this by invoking - (void)paymentQueue:(SKPaymentQueue*)queue updatedTransactions:(NSArray*)transactions`? When I reran my app, the item was re-submitted; but a user should not need to restart the app.
Related question: When I tried to buy a still pending item again (on the sandbox), I got an iOS alert saying that I bought it earlier but was not downloaded. Why is this? I would expect (and have actually seen) this for non-consumables. I could buy another consumable, with this previous one still pending.
Once you finish the transaction of CONSUMABLE IN-APP the following method will fire there add this statement "[[SKPaymentQueue defaultQueue] finishTransaction:transaction]" to remove the purchased product.Using this statement you can avoid the alert message(I bought it earlier but was not downloaded).
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction *transaction in transactions)
{
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased:
if (transaction.downloads)
{
[[SKPaymentQueue defaultQueue]
startDownloads:transaction.downloads];
} else {
//Add the following line to remove purchased observer
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
break;
case SKPaymentTransactionStateFailed:
[[SKPaymentQueue defaultQueue]
finishTransaction:transaction];
break;
}
}
}
The next time your App adds a transaction observer the transaction will appear in the queue.
And perhaps this is why you get the 'not yet downloaded' message - that means 'the app store has not yet gotten a finishTransaction'.
Sorry for that millionth question on autorenewable subscriptions, but i don't get it.
I've done everything as describet in Apples In-App Purchase Guidelines but it didn't solve the problem.
My problem is that i have created autorenewable subscriptions but they won't be autorenewed.
I've create a Payment Transaction Observer class, which implements the SKPaymentTransactionObserver interface. This class will be installed as a paymentObserver at Application startup in the viewDidLoad: method.
PaymentTransactionObserver *observer = [[PaymentTransactionObserver alloc] init];
[[SKPaymentQueue defaultQueue] addTransactionObserver:observer];
In the paymenttransactionobserver i have the paymentQueue:updateTransactions method: (same as describet in Apple's documentation)
(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];
break;
default:
break;
}
}
When i buy a autorenewable product, the product will successfully be purchase.
But it will never be autorenewed. I thought of, that the transaction observer, somehow will get deallocated, but it won't (Otherwhise, i would be notified by the debugger). I also though, i did remove the observer but it will never be removed.
I used the debugger to ensure, that the updateTranscations: method will get called, but nothing. When i buy a test product (in sandbox-mode) with autorenewal time of one week, the method should get called after 3 minutes, but it wont.
What am i doing wrong?
Can anybody help?
Br Nic
If a subscription is autorenewed, the transaction won't pass the paymentQueue:updateTransactions method. The renew just happens on the Store. If you want to test for it you have to either:
Revalidate the receipt on your application server, if you store the receipt there.
Revalidate the receipt on ur iOS client
(http://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/StoreKitGuide/VerifyingStoreReceipts/VerifyingStoreReceipts.html#//apple_ref/doc/uid/TP40008267-CH104-SW1)
In order to avoid testing for an autorenew each launch/activation you should store the endDate of the subscription period to test for a renew afterwards.
Also see:
http://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/StoreKitGuide/RenewableSubscriptions/RenewableSubscriptions.html#//apple_ref/doc/uid/TP40008267-CH4-SW4
However, there seems to be a bug in the sandbox. Subscriptions sometimes get renewed, sometimes not. Hard to test....
Auto-renewals only get posted to your app's purchase queue on app launch or when it cycles back from being backgrounded. Try clicking the home button and then returning to your app.
This question already has answers here:
Differentiating between initial buy and free "re-buy" in StoreKit/In-App Purchase
(3 answers)
Closed 9 years ago.
Is there a way to find out when a user buys a non-consumable, if he buys it for the first time or has bought it already and gets it again for free?
I checked transactionState, transactionDate of the transaction, but in both cases the data is the same:
transactionState: SKPaymentTransactionStatePurchased (and not SKPaymentTransactionStateRestored in case the user bought it already)
transactionDate: the date at which the transaction was added to the AppStore's payment queue.
You can check Array of transactions which will fill after restoreTransaction method and if Array of transactions is empty it means that user download this upgrade for the first time. In another case you'll check all transactions in Array and compare transaction.payment.productIdentifier with needful product identifire. If it doesn't exist add payment in transaction Array.
For Non-Consumable In-App Purchase I used following code:
#define kInAppPurchaseProUpgradeProductId #"upgradeProductId"
//...
//your payment code for all SKPaymentTransactionStates
//...
//method called when BUY button press
-(void)purchaseProUpgrade{
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
//when restore completed delegate method calls
-(void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue{
if([[[SKPaymentQueue defaultQueue] transactions] count]==0)
[self addNewPaymentForProductId:kInAppPurchaseProUpgradeProductId];
else
for (SKPaymentTransaction *transaction in [[SKPaymentQueue defaultQueue] transactions]){
if (![transaction.payment.productIdentifier isEqualToString:kInAppPurchaseProUpgradeProductId]){
[self addNewPaymentForProductId:kInAppPurchaseProUpgradeProductId];
break;
}
}
}
-(void)addNewPaymentForProductId:(NSString *)productId{
if([SKPaymentQueue canMakePayments]){
SKPayment *payment = [SKPayment paymentWithProductIdentifier:productId];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
}
the only disadvantage is that every time you call restoreCompletedTransactions, window will pop up asking you to enter the password of the current user. This solution ensures that the buy window does not appear more than 1 time for each upgrade but all the upgrades will restore every time when you'll try to buy one of them.
Not sure but maybe you can check if transaction.originalTransacion exists or if is different.