In the OS X app (10.9+) I'm writing, I'd like to determine the original purchase date of a non-consumable in-app purchase. This sounds straight forward, but there are two relevant fields, and Apple's docs don't seem to clearly state how to combine them.
Apple's Receipt Validation Programming Guide describes two relevant receipt fields:
Purchase Date ... The date and time that the item was purchased ... For a transaction that restores a previous transaction, the purchase date is the date of the restoration. Use Original Purchase Date to get the date of the original transaction.
Original Purchase Date ... For a transaction that restores a previous transaction, the date of the original transaction.
My trouble here is with "for a transaction that restores a previous transaction". This implies that in order to combine the two fields, I need to know if the transaction is a purchase or a restore.
This is easy within my <SKPaymentTransactionObserver> paymentQueue:updatedTransactions:
method, because there, I know if the transaction is SKPaymentTransactionStatePurchased
or SKPaymentTransactionStateRestored
.
But... when the app starts, it needs to know whether to enable features, based on the previous transactions. I think this should be done by examining only the receipt (I seem to remember a WWDC presentation that talked about the receipt being the "single source of truth").
In the transaction-handling code, I could stuff information about the customer's in-app purchases into (eg) a plist for later use, but that seems like adding (yet) more complexity.
So, if I examine only the receipt (so I don't know if the most recent transaction was a purchase or restore), which field contains the original purchase date?
I'm using Receigen to validate the receipt. This provides the macros <prefix>INAPP_ATTRIBUTETYPE_PURCHASEDATE
and <prefix>INAPP_ATTRIBUTETYPE_ORIGINALPURCHASEDATE
, and I believe these provide C strings.
I can think of a few methods (pseudo-code):
// method one
// assume that Original Purchase Date is always present
return ORIGINALPURCHASEDATE
// method two
// when present, Original Purchase Date overrides Purchase Date
if (ORIGINALPURCHASEDATE == "") {
return PURCHASEDATE
} else {
return ORIGINALPURCHASEDATE
}
// method three
// consider all possible combinations
// of the two values being present / not present
if (PURCHASEDATE != "" && ORIGINALPURCHASEDATE == "") {
// have only PURCHASEDATE
return PURCHASEDATE
} else if (PURCHASEDATE == "" && ORIGINALPURCHASEDATE != "") {
// have only ORIGINALPURCHASEDATE
return ORIGINALPURCHASEDATE
} else if (PURCHASEDATE != "" && ORIGINALPURCHASEDATE != "") {
// have both: use the earlier value
return min(PURCHASEDATE, ORIGINALPURCHASEDATE)
} else {
// have neither
return UNKNOWN
}
... but this kind of guesswork within code related to the customer's payment worries me :(
I will try some experiments in the sandbox to try and verify this, but I've seen comments that the sandbox behaviour doesn't always match the production environment.
Can anyone confirm the right thing to do here?
Update
To get more information, I've thrown together a tool which dumps the contents of all _MASReceipt/receipt
files in /Applications
, using some classes from SCPStoreKitManager (which has some lovely clear code). On my machine, the output includes:
/Applications/Dash-2.2.6.app/Contents/_MASReceipt/receipt
{
bundleIdentifier = "com.kapeli.dash";
bundleIdentifierData = <0c0f636f 6d2e6b61 70656c69 2e646173 68>;
hash = <acec4273 8a9bf57a 1d3ed8ab f7a4c696 497f2ae1>;
inAppPurchases = (
{
cancellationDate = "";
originalPurchaseDate = "2012-08-13 00:07:48 +0000";
originalTransactionIdentifier = 220000033036694;
productIdentifier = DPinky;
purchaseDate = "2012-08-13 00:07:48 +0000";
quantity = 1;
subscriptionExpiryDate = "";
transactionIdentifier = 220000033036694;
webItemId = "";
}
);
opaqueValue = <f50b37c7 ac1e9a02 ddb579ca 5c5dad62>;
originalVersion = "1.6.6";
version = "2.2.6";
}
In this case, both purchaseDate
and originalPurchaseDate
are present, and have the same value.
That's the only example of an OS X receipt with an in-app purchase that I have.
Can anyone provide more examples? I'd be particularly interested to see receipts where those values differ, or aren't present.
I've uploaded the binary and source for this tool: DumpMASReceipt-v1.zip.