Once I get a receipt from client and validate it by apple server, then I got a transaction id of the new purchase (it's an auto-subscription purchase) in this receipt. After a few days , I get a new receipt from the same client and validate it , strange things happen : the transaction id of that old purchase changes.
I compare the data of this purchase in the old receipt with the data in the new receipt , the only thing changes is the transaction_id field ,the original_transaction_id, purchase_date, expires_date, web_order_line_item_id and other fields are exactly same.
Than I check the database, I found about 1% transaction records have the same situation. And there is a trait, Most of their transaction id were increased or decreased by 1-2 .
I used to think the transaction id is the identifier of a purchase. Does anyone meet the same problem or know the reason?
Yes we see this happen as well. We see this triggered by 'Restore Purchase' button clicks.
If your 'Restore Purchase' button uses the restoreCompletedTransactions API then this will cause your transaction ID's to change. We have confirmed that this with Apple developer support.
Apparently you can call SKReceiptRefreshRequest instead which will just grab the latest receipt instead of replaying all of the transactions. It is my understanding that this will not cause the transaction ids to change.
We have, anecdotally, witnessed that the web_order_line_item_id values do not change across calls to restoreCompletedTransactions. However we only received an ambiguous, at best, response from Apple developer support when we asked for confirmation:
In regards to the web_order_line_item_id field, the value will change on every subsequent renewal.
You can use this, as long as you continue to store the new value as the renewal subscription events come in.
We take this to mean that the web_order_line_item_id is unique per renewal-purchase. Which neither confirms nor denies that it remains constant across calls to restoreCompletedTransactions
Additionally, I found this conference recording on Apple/Google/Amazon recurring billing very helpful (though, not for this particular case):
Rosa Gutierrez - The recurring nightmare. Implementing cross platform in-app subscription purchases
Based on latest Apple developer documentation,
web_order_line_item_id - A unique identifier for purchase events across devices, including subscription-renewal events. This value is the primary key for identifying subscription purchases.
transaction_id - You can use this value to:
Manage subscribers in your account database. Store the transaction_id, original_transaction_id, and product_id for each transaction, as a best practice to store transaction records for each customer. App Store generates a new value for transaction_id every time the subscription automatically renews or is restored on a new device.
Differentiate a purchase transaction from a restore or a renewal transaction. In a purchase transaction, the transaction_id always matches the original_transaction_id. For subscriptions, it indicates the first subscription purchase. For a restore or renewal, the transaction_id does not match the original_transaction_id. If a user restores or renews the same purchase multiple times, each restore or renewal has a different transaction_id.
Related
The documentation for SKPaymentTransaction.transactionIdentifier notes:
This value has the same format as the transaction’s transaction_id in
the receipt; however, the values may not be the same.
And the documentation for transaction_id notes:
This value has the same format as the transaction’s
transactionIdentifier property; however, the values may not be the
same.
You can use this value to:
• Manage subscribers in your account
database. Store the transaction_id, original_transaction_id, and
product_id for each transaction, as a best practice to store
transaction records for each customer. App Store generates a new value
for transaction_id every time the subscription automatically renews or
is restored on a new device.
• Differentiate a purchase transaction from
a restore or a renewal transaction. In a purchase transaction, the
transaction_id always matches the original_transaction_id. For
subscriptions, it indicates the first subscription purchase. For a
restore or renewal, the transaction_id does not match the
original_transaction_id. If a user restores or renews the same
purchase multiple times, each restore or renewal has a different
transaction_id.
Also will note the Receipt Validation Programming Guide (from the Documentation Archive) states under Transaction Identifier:
This value corresponds to the transaction’s transactionIdentifier
property.
With that noted, my question is: when is the SKPaymentTransaction.transactionIdentifier the same value as the validated receipt's transaction_id, or when isn't it?
In our app we only work with consumable in-app purchases, no subscriptions. In this scenario, are these two values the same? I ask because I need to be able to record the purchase server-side along with information about the user who purchased it. See below for an explanation of the process and problem it presents.
Let’s say a user purchases a consumable product and the request to record that transaction fails because our server is down for example, so we do not call finishTransaction:. Now let’s say that person logs out and logs into a different user account and purchases another consumable product and again we fail to record it. There are now two consumable products in the receipt. When they launch the app next paymentQueue(_:updatedTransactions:) is called with two items in the transactions array to inform us there’s purchased transactions we need to finish. We need to submit the receipt to our server to record the transactions but we also need to send some additional info along with each transaction such as the user id who purchased it. This means I need to persist this info on disk with an associated transactionIdentifier so I can get that data later. I can send an array of user ids and the receipt to the server. But how would the backend know which transaction in the receipt matches which user id in the array? I don’t believe the in_app array is guaranteed to be sorted in any particular way and may not match the order of the transactions array provided to paymentQueue(_:updatedTransactions:). So when there’s multiple transactions to be recorded, how can we properly link them so the purchase is applied to the correct user account server-side? In this scenario, is the transactionIdentifier available in-app guaranteed to be the same as the transaction_id in the validated receipt?
In testing we've determined the transactionIdentifier is the same value as the transaction's transaction_id in the validated receipt for consumable purchases.
However, we are reluctant to rely on that being the case due to the note in the documentation stating it may not be the same value. So we put a fallback in place to uniquely identify a transaction using a combination of the transaction.payment.productIdentifier and the transaction.transactionDate, which is documented to correspond to the original_purchase_date_ms in the receipt. This allows the backend to find the correct transaction in the receipt for the information the app submitted.
After a long search for an answer, I want to ask a very simple question here. Are transactions associated with a auto-renewable subscription removed after the expiration date from the list of transactions that can be obtained using the restoreCompletedTransactions() method?
In its simplest implementation, is it possible to manage application content associated with a auto-renewable subscription without using Validating Receipts? And using only the result of the restoreCompletedTransactions() method?
You have two questions here.
1) Are transactions associated with a auto-renewable subscription removed after the expiration date from the list of transactions that can be obtained using the restoreCompletedTransactions() method?
Answer:
No. Those entries will always remain there and when you validate the receipt, you will get it back in response. By Doing the Restore purchase won't delete the existing entries from receipt. When you testing the In app purchase with Sandbox account then you will have 35 mins to use that account within 8 Hours. You can do whatever you want. You can do subscription again(In this case system will say us that your subscription is still running). After 35 mins of first purchased, Plan will be considered as Cancel and you need to do Subscription again(Only in Sandbox mode, Well In production mode May be it will not ask to do subscription again? I am not sure here.)
2) Is it possible to manage application content associated with a auto-renewable subscription without using Validating Receipts
Answer: No. Its good to use the receipt validation for locking/unlocking content of your application.
I offer a subscription in my application, and after purchase I validate that transaction against the receipt to get its expiration date. I do this by loading the receipt from disk, sending it to Apple for validation/ decryption (I know this is bad practice), and then iterating over the latest_receipt_info in the JSON response looking for a transaction matching the transactionIdentifier of the SKPaymentTransction object.
This works for most subscriptions, but not those offered with a free trial/ introductory offer. Please take a look at my example below:
I purchase a subscription that is returned to my application with a transactionIdentifier of 1000000506350685 and originalTransaction.transactionIdentifier of null.
I get the latest JSON receipt from Apple whose latest entry is one matching my product identifier and timestamp, but the transaction_id is 1000000506350686 and so is original_transaction_id.
Therefore I am unable to validate the purchase.
There are no problems when purchasing a subscription without a free trial/ introductory offer. There are no other objects in the returned receipt that matches the transaction id returned to my application.
This is in the sandbox environment, I am not sure about the behavior in production.
Has anyone had any similar experiences? What can I do to fix this?
I think this is probably due to the fact that you've already purchased that product before in Sandbox, and there's no device receipt in sandbox until you make a purchase (unlike production).
Either way, what you should be looking at is the latest product_id and expiration_date from the receipt to validate if the subscription is active. Transaction IDs can be keys for your transactions if you're saving them in a database, but shouldn't be used to validate a purchase.
Here is a really good blog post that goes over what your in-app purchase server should be doing to validate and keep subscription status up-to-date: iOS Subscriptions are Hard
I do this by loading the receipt from disk, sending it to Apple for
validation/ decryption (I know this is bad practice).
This is actually the recommended approach by Apple.
I've been reading the various threads on in-app purchases auto-renewal subscriptions, and I think I've pieced together most of the information I need, but there are a few missing pieces. I'm hoping someone can help me.
The situation:
I have various subscription packages the user can subscribe to (e.g., package A for £1 a month, package B for £2 a month, etc.). I store the user's subscription information in my database. When the user logs in, I check which package he's on and if it's expired or not. My website, android and iOS all use the same database, hence this approach seems to make sense.
Subscribing users via in-app purchase seems straight forward enough. I check paymentQueue and once the payment is cleared, I can update my database.
My questions:
1) My understanding is the user can use iTunes to manage their subscription. Say, they go in to iTunes and cancel their subscription, how can I be notified so I can update my database? Do I need a daemon that checks expired subscriptions to see if the user renewed?
2) If the user wants to upgrade their subscription from Package A to Package B, how do I handle the pricing? Say on Jan 1st, they buy Package A, I charge them £1.00 and set the expiry date to Jan. 31st. On Jan. 15th, they want to upgrade to package B via in-app purchase. Ideally, I would charge them £2 for Package B minus £0.50 of credit they have for Package A and set the new expiry date to feb 14th. However, Apple forces me to associate each package with a tier price. How can I handle this? I don't want the user to wait until the end of the month to put them on a higher tier package...if they upgraded mid-month, it means they want the new content package B will deliver to them immediately.
Any help appreciated!
Thanks!
1) Yes you'll have to reverify your receipt check out the Receipt Validation Programming Guide in the documentation. They mention some important keys:
status - 0 if receipt is valid, or an error code
receipt - JSON response of the receipt
latest_receipt (auto-renewable only) - base 64 encoded receipt for the most recent renewal
latest_receipt_info - JSON version of latest_receipt
With this information, when a purchase is made, send the receipt to your backend for validation, the backend will keep the receipt in the DB and verify with status = 0 that it's a valid receipt. From there, every x days you can validate that receipt with a chron job, daemon, etc. and reverify. The response back each time will have latest_receipt_info that you now need to save to your DB so you have an up-to-date receipt for the next check in x days. This way you will always have the latest receipt. There is no instant notification for telling when a user cancels a subscription, but with this you'll know every x days if they have the subscription still.
2) Pricing like this unfortunately can't be handled. It was not intended for a user to "upgrade" with subscriptions - each subscription is access to it's own exclusive content as of this writing. However, if a customer emails in and complains about it, you could ask for their user name and figure out in your DB if this user has indeed upgraded mid-month and reimburse them appropriately. Very old-school and not feasible for a big user base, but hopefully you won't have that many and can keep them happy.
EDITED
For the second question, Apple's auto-renewable subscription system does not technically offer upgradeable plans between different products.
Every subscription is a stand alone product and it's up to the user to turn on/off subscriptions manually using the Subscription Manager in the iTunes Store.
However if package A and package B offer the exact same content only different duration than what ajay_nasa said is correct, you can create an single auto-subscription product with different duration options. If the user is on 1 month subscription and then the user tries to change to 2-months subscription they will get the following error message asking them go to the App Store's subscriptions manager
So basically the ONLY place the user can actually change the subscription's length is in the App Store. Whether Apple decide to pro-rate the amount left on the old subscription or just append it to the current one is really up to Apple. You need to make sure the user have access to the subscription as long as it's active by reading the Original Purchase Date and Subscription Expiration Date field from each receipt entry and determining the start and end dates of the subscription.
Answering question 1, you can verify subscription receipts in the same way as other IAP, but you'll need to check it periodically to see if the subscription has expired (the verification will tell you if it's expired).
There is more info on the Apple docs here:
http://developer.apple.com/library/mac/#documentation/NetworkingInternet/Conceptual/StoreKitGuide/RenewableSubscriptions/RenewableSubscriptions.html
Actually, Apple's auto-renewable subscription system does offer upgradeable plans.
To achieve this we simply should add duration in existing Auto-Renewable Subscription. Every Auto-Renewable Subscription could be family of Subscriptions so whenever developer wants to achieve upgradation in subscription he should add duration in existing Auto-Renewable Subscription with different productID.
Whenever, user upgrade his plan in between the month then his upgrade plan will be automatically works from next month.
Answering to your question #1
Recently Apple launched a feature to enable server notification whenever the subscription is renewed. However, the subscription should be in-app.
See the links given below for your reference:
https://help.apple.com/itunes-connect/developer/#/dev0067a330b
&
https://itunespartner.apple.com/en/apps/news/45333106?sc_cid=ITC-AP-ENREC
We Need to Check Cancellation-Date provided in Receipt.
I can't receive any transactions in my paymentQueue:updatedTransactions: delegate Method. Only the delegate method paymentQueueRestoreCompletedTransactionsFinished: gets hit by the debugger. When i try to retreive the transaction array, i get an empty array.
First of all, i buy a non-renewing product. After successfully purchaseing and verifying it from the server, it will be removed from the payment queue. Than at startup, i call the paymentqueues restoreCompletedTransactions method. As expected the Storekit asks me for the password (from the sandbox-user) which i enter. But the update Method won't get cought. Only the finish Method gets caught and as discribed, the transactions are empty.
What am i doing wrong? Is it my fault, or is it apples restoreCompletedTransactions broken??
Thanks and BR
Nic
Sorry for letting you wait for such a long time!
The problem is that in Apples In-App Purchase Programming Guide it clearly says, that i cannot take non-renewing subscriptions to be restored. I'll have to store the purchases on my own and implement an own restore functionality.
What i did was including some identifiers in the product name, that tell me, what kind of product it is. (Its one method to store information of the product without using my own server) So my Products name is e.g. com.mycompany.iphone.non_renewing_30d (This product is really configured as a non renewing product)
So when the user makes a purchase, i store this information in the iDevice's CoreData Database. I also include my expire functionality, because i have the purchase date and the products expire date (30d).
When the time expires, the user sees a message dialog which tells to make a new purchase. I really don't like apples implementation of non-renewing subscriptions because it tells the user, that he/she already bought the product and asks them to repurchase, but i had to deal with that.
I really suggest to store the purchase information on the iOS Device, because you don't need to create your own user recognize functionality (e.g. a userId or email adress), since Apple turned off the UDID uniqueness.