iOS in app purchase issue - ios

I have an app on Appstore containing in app purchase. Now I want to know:
How can I view what in app items were purchased.
If any user did transaction then from where I can get that full transaction detail.
In addition, in case, if any user made a claim of in app purchase and credits not returned to user or showed in application then how to verify that claim or what information can help to verify the claim?
Please provide all detail, the more information more better appreciated.
Thanks.

Take a look at SKPaymentQueue. It helps you to know / make purchases.
By calling addTransactionObserver: and subscribing to SKPaymentTransactionObserver protocol you tell SKPaymentQueue that you want to kwon when things are happened.
SKPaymentTransactionObserver have methods that called when user buy / cancel / restore purchases.
For example:
public func paymentQueue(queue: SKPaymentQueue!, updatedTransactions transactions: [AnyObject]!) {
for transaction in transactions as! [SKPaymentTransaction] {
switch (transaction.transactionState) {
case .Purchased:
// everything is ok
break
case .Failed:
// something fail
break
case .Restored:
// purchase restored
break
case .Deferred:
break
case .Purchasing:
break
}
}
}
There is a great tutorial from Ray.

Related

How do I detect if SKProduct already purchased as consumable in app purchases before?

I imported StoreKit and set my code for consumable purchase and set a sandbox tester. I have three buttons. Each one will buy a separate product. Here is my problem:
When my sandbox tester tries to purchase the same product again and presses the same button, Firebase analytics says:
"Purchase is a duplicate and will not be reported".
Ok, fine. But how I can get this as a response from AppStore? Where does Firebase get this from?
When I look at SKPaymentTransaction.transactionState enum, there is no case about it. I tried to check cases and print something to console, like this:
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch transaction.transactionState {
case .purchased:
print("purchased")
break
case .restored:
print("restored")
break
case .failed :
print("failed")
break
case .deferred:
print("deferred")
break
case .purchasing:
print("purchased")
break
#unknown default:
fatalError()
}
}
}
I also checked transaction.error, but no error either.
This code prints nothing when the purchase fails from duplicate purchase. How can I get at least an error here?
What should I do?
My goal is show an alert and tell the client "You already purchased this".

appStoreReceiptURL == nil after restoreCompletedTransactions()

SKPaymentQueue's restoreCompletedTransactions() does not update the appStoreReceiptURL receipt after completion.
I am working on a Mac app that will be supported via auto-renewing subscriptions. After restoring purchases finishes, I need the updated receipt because I will need to check the current subscription status (expiration date, etc.).
The only solution I could come up with was to refresh the receipt right after restore completed transactions finished, as shown below. This does appear to work. However, the user will be prompted to enter in their username and password twice, which is a less than a perfect experience. In addition, I am concerned that such behavior will lead to a rejection by app review.
public func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
if !fileExistsAtAppStoreReceiptURL() {
print("No receipt.")
}
let refreshRequest = SKReceiptRefreshRequest(receiptProperties: nil)
refreshRequest.delegate = self
refreshRequest.start()
}
private func fileExistsAtAppStoreReceiptURL() -> Bool {
guard let appStoreReceiptURL = Bundle.main.appStoreReceiptURL else { return false }
return FileManager().fileExists(atPath: appStoreReceiptURL.path)
}
public func requestDidFinish(_ request: SKRequest) {
if request is SKReceiptRefreshRequest {
updateCachedReceiptFromAppStoreReceiptURL()
reverifyKnownPurchases()
notify { self.delegate?.inAppPurchaseController(self, restoreCompletedTransactionsDidFinishWith: .success) }
} else {
notify { self.delegate?.inAppPurchaseController(self, didFinishRequestingIndividualProductInformationWith: .success) }
}
}
Some other notes:
No receipt. does show up in the log.
Because this behavior occurs when using the sandbox environment, I don't know if the behavior is the same when the code executes in production.
The behavior did not change after an attempt with a newly-created test user.
Because I wanted to simulate a real-life situation, I restored purchases on a second Mac, and not the Mac that purchased the subscription.
The rest of the restore process appears to work correctly. paymentQueue:updatedTransactions: is called for each restored transaction with a transactionState of .restored.
SKPaymentQueue.default().finishTransaction(transaction) is called for each restored transaction.
SKPaymentQueue.default().add(self) is called in applicationDidFinishLaunching:.
Although I am making use of an auto-renewing subscription, it does not play a role. A receipt does not appear even if I make use of a non-consumable, for example.
I am not using a server.
The Restoring Purchased Products and the restoreCompletedTransactions() documentation neither explicitly confirm nor deny that the receipt is updated as a part of the restore process. Could this be the expected behavior?
Because I had a similar issue with an iOS test app earlier in the year, this may not be a Mac-only situation.
With all of that in mind, does anyone know how to properly handle such a situation? Has anyone had success with restore + refresh?

Storekit Appstore purchase promotion

this code is what is given on the apple developer website for when a user clicks on the promotional app-store product and it tells to check to see if can complete the transaction? how do I go about checking that? because then I have to cater if the transaction has failed or deferred and can't seem to figure out how to do that.
//MARK: - SKPaymentTransactionObserver
func paymentQueue(_ queue: SKPaymentQueue, shouldAddStorePayment payment: SKPayment,
forProduct product: SKProduct) -> Bool {
// Check to see if you can complete the transaction.
// Return true if you can.
return true
}
There is also the next scenarios I have to cater for which I find to be the same scenario as checking if the transaction can be completed
func paymentQueue(_ queue: SKPaymentQueue, shouldAddStorePayment payment: SKPayment,
forProduct product: SKProduct) -> Bool {
// ... Add code here to check if your app must defer the transaction.
let shouldDeferPayment = ...
// If you must defer until onboarding is completed, then save the payment and return false.
if shouldDeferPayment {
self.savedPayment = payment
return false
}
// ... Add code here to check if your app must cancel the transaction.
let shouldCancelPayment = ...
// If you must cancel the transaction, then return false:
if shouldCancelPayment {
return false
}
}
// (If you canceled the transaction, provide feedback to the user.)
// Continuing a previously deferred payment
SKPaymentQueue.default().add(savedPayment)
)
How do I check to see if the payment failed or needs to be deferred or can be completed as it says in both the code parts?
Whether the transaction needs to be deferred or not depends entirely on your app and any requirements your app may have.
As an example, say your app required the user to set up an account before they can purchase a subscription.
If the user begins the purchase in the App Store, the shouldAddStorePayment method will be called after your app is launched to complete the purchase.
At this point you may detect that the user has not set up an account in your app (indeed, the app could have been installed as a result of them tapping the promoted IAP). In this case you would return false fromshouldAddStorePayment because your app is not in a position to be able to complete the purchase.
Your app would then continue with its normal on-boarding process which gets the user to establish their account.
Once the account is established you want to complete the purchase; this is both a good user experience and ensures that you don't miss a sale.
This is where the other sample code in your question comes in; it shows how you can save the payment and initiate the purchase at a later stage.
In summary, when shouldAddStorePayment is called:
Determine if there is some reason that you cannot complete the purchase now
If there is, return false, otherwise return true
If you return false, save the purchase details so that you can initiate the purchase at a later stage when whatever was preventing the purchase has been resolved.

Catch "The In-App Purchase has already been bought" event

-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{
for (SKPaymentTransaction* transaction in transactions)
{
CCLOG("transactionState = %ld", transaction.transactionState);
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchased:
[self completeTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
[self failedTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
[self completeTransaction:transaction];
break;
default:
break;
}
}
The code above is for tracking the purchase state we know.
But it seems that the code won't track the event of "The In-App Purchase has already been bought".
[[SKPaymentQueue defaultQueue] addPayment:payment];
Each time I use the code above to purchase an IAP which is already purchased, the IOS confirm purchasing dialog will show out with transactionState = SKPaymentTransactionStatePurchasing(The output is "transactionState = 0"). Then I buy the item and "The In-App Purchase has already been bought" dialog show out with no other state code output.
I wish to catch the event above. I bet it must be catchable. Does the event is threw in other place?
In my case the problem was caused by adding the SKPaymentTransactionObserver to the SKPaymentQueue after another library had already registered its own observer. Switching the order of initialization so that my app registered its own observer first fixed the problem.
I've provided in more detail here
We had a similar Issue when our server receipt validation stopped working and the transactions weren't finished. So I've modified the purchase flow. Before the App adds a new product payment the app iterates now through all open transactions ([SKPaymentQueue defaultQueue].transactions) and stops the payment when an open transaction has the same product identifier as the new product payment.

Apple In-App Purchase Receipt - validation on the server-side

I have a problem with validating an apple receipt on the server-side.
I tried to find a solution in the internet, but haven't succeeded.
So, description:
First of all, application is made for iOS7. Secondly, I have a few items ( type = Non-Renewing Subscription ). So user can buy one or multiple items and then he should manually renew them ( buy again ).
Applications sends a receipt to the server-side, I make a request to the Apple and get the result with a lot of in_app receipts. Something like:
"in_app":[
{
"quantity":"1", "product_id":"...", "transaction_id":"...",
"original_transaction_id":"...", "purchase_date":"...",
"purchase_date_ms":"...", "purchase_date_pst":"...",
"original_purchase_date":"...",
"original_purchase_date_ms":"...", "original_purchase_date_pst":"...",
"is_trial_period":"..."},
{
"quantity":"1", "product_id":"...",
"transaction_id":"...","original_transaction_id":"...",
"purchase_date":"...", "purchase_date_ms":"...",
"purchase_date_pst":"...", "original_purchase_date":"...",
"original_purchase_date_ms":"...", "original_purchase_date_pst":"...",
"is_trial_period":"..."}
]
So, each "receipt" in "in_app" has transaction_id. But how I can identify the transactionId of the current purchase? I would like to validate it as well and make sure that this is unique.
My concern is: if somebody will get one valid receipt, he will be able to hack our server-side API and make unlimited numbers of in-app purchases with the same valid receipt.
Should I somehow decrypt and check for transaction_id the "original" receipt, the one what I send to Apple for verification?
Any help/suggestions would be highly appreciated.
Thank you in advance.
Regards,
Maksim
#Doug Smith
https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html
If you go through the different fields on this page, you will find
Original Transaction Identifier::
For a transaction that restores a previous transaction, the transaction identifier of the original transaction. Otherwise, identical to the transaction identifier.
This value corresponds to the original transaction’s transactionIdentifier property.
All receipts in a chain of renewals for an auto-renewable subscription have the same value for this field.
So for your non-auto renewable subscriptions, you have to keep track of two things on your server side:
The original transaction identifier of the receipt that you are validating with itunes server, associate this with the user Id in your database.
Whether the request that you received from the client side is of a Purchase or of a Restore Purchase.
Once you have these two things with you, you can write your logic on these two parameters like below:
::If a request is of type "Purchase" and you already have the original transaction identifier of that receipt associated with some other user Id, you can block that purchase.
::If a request is of type "Restore Purchase" and request is coming from the same user id against which the original transaction identifier is associated in your DB than allow him otherwise block his restore.
Furthermore, you can derive your own logic based on these things, according to your needs.
Let me know if you have any further doubts.
For each new transaction apple send a new receipt which is unique, encode it so no one can forge data.
Get the transaction receipt from the completed transaction encode it and send it to your server, and on the server side decode it and match with the one apple send to server.
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
_transactionArray = transactions;
for (SKPaymentTransaction * transaction in transactions)
{
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchased: {
NSData *receipt = transaction.transactionReceipt;
[self sendReceiptToServer];
} break;
case SKPaymentTransactionStateFailed: {
// finish this transaction
} break;
case SKPaymentTransactionStateRestored:
NSData *receipt = transaction.transactionReceipt;
[self sendReceiptToServer:receipt];
} break;
default:
break;
}
};
}
-(void)sendReceiptToServer:(NSData *)receipt {
// encode receipt
// send receipt to server
// add success and error callback
}
-(void) receiptSuccess {
// finish transaction here
}
-(void) receiptError {
// try again sending receipt to server
}

Resources