This is the code in my iOS project:
if let receiptURL = Bundle.main.appStoreReceiptURL, let data = try? Data(contentsOf: receiptURL) {
//send receipt data.base64EncodedString() to server
}
My server code
def verify():
json_data = request.get_json()
receipt = json_data["receipt"]
env = json_data["env"]
bundle_id = json_data["bundle_id"]
if env == "production":
endpoint = "https://buy.itunes.apple.com/verifyReceipt"
else:
endpoint = "https://sandbox.itunes.apple.com/verifyReceipt"
applePayload = {
"receipt-data": receipt,
"password": "mysecret",
"exclude-old-transactions": True
}
r = requests.post(endpoint, json=applePayload)
r.headers['Content-Type'] = "application/json; charset=utf-8"
return r.text
This is the Apple response when I send the receipt from an test account:
{"status":0, "environment":"Sandbox",
"receipt":{"receipt_type":"ProductionSandbox", "adam_id":0, "app_item_id":0, "bundle_id":"es.mediaregie.wholovesme", "application_version":"2", "download_id":0, "version_external_identifier":0, "receipt_creation_date":"2019-05-11 18:43:58 Etc/GMT", "receipt_creation_date_ms":"1557600238000", "receipt_creation_date_pst":"2019-05-11 11:43:58 America/Los_Angeles", "request_date":"2019-05-14 05:46:18 Etc/GMT", "request_date_ms":"1557812778939", "request_date_pst":"2019-05-13 22:46:18 America/Los_Angeles", "original_purchase_date":"2013-08-01 07:00:00 Etc/GMT", "original_purchase_date_ms":"1375340400000", "original_purchase_date_pst":"2013-08-01 00:00:00 America/Los_Angeles", "original_application_version":"1.0",
"in_app":[
{"quantity":"1", "product_id":"weekly_subscription", "transaction_id":"1000000526894772", "original_transaction_id":"1000000526894772", "purchase_date":"2019-05-11 16:17:20 Etc/GMT", "purchase_date_ms":"1557591440000", "purchase_date_pst":"2019-05-11 09:17:20 America/Los_Angeles", "original_purchase_date":"2019-05-11 16:17:22 Etc/GMT", "original_purchase_date_ms":"1557591442000", "original_purchase_date_pst":"2019-05-11 09:17:22 America/Los_Angeles", "expires_date":"2019-05-11 16:20:20 Etc/GMT", "expires_date_ms":"1557591620000", "expires_date_pst":"2019-05-11 09:20:20 America/Los_Angeles", "web_order_line_item_id":"1000000044334416", "is_trial_period":"false", "is_in_intro_offer_period":"false"},
{"quantity":"1", "product_id":"weekly_subscription", "transaction_id":"1000000526894809", "original_transaction_id":"1000000526894772", "purchase_date":"2019-05-11 16:20:20 Etc/GMT", "purchase_date_ms":"1557591620000", "purchase_date_pst":"2019-05-11 09:20:20 America/Los_Angeles", "original_purchase_date":"2019-05-11 16:17:22 Etc/GMT", "original_purchase_date_ms":"1557591442000", "original_purchase_date_pst":"2019-05-11 09:17:22 America/Los_Angeles", "expires_date":"2019-05-11 16:23:20 Etc/GMT", "expires_date_ms":"1557591800000", "expires_date_pst":"2019-05-11 09:23:20 America/Los_Angeles", "web_order_line_item_id":"1000000044334417", "is_trial_period":"false", "is_in_intro_offer_period":"false"},
{"quantity":"1", "product_id":"weekly_subscription", "transaction_id":"1000000526895042", "original_transaction_id":"1000000526894772", "purchase_date":"2019-05-11 16:23:20 Etc/GMT", "purchase_date_ms":"1557591800000", "purchase_date_pst":"2019-05-11 09:23:20 America/Los_Angeles", "original_purchase_date":"2019-05-11 16:17:22 Etc/GMT", "original_purchase_date_ms":"1557591442000", "original_purchase_date_pst":"2019-05-11 09:17:22 America/Los_Angeles", "expires_date":"2019-05-11 16:26:20 Etc/GMT", "expires_date_ms":"1557591980000", "expires_date_pst":"2019-05-11 09:26:20 America/Los_Angeles", "web_order_line_item_id":"1000000044334428", "is_trial_period":"false", "is_in_intro_offer_period":"false"},
{"quantity":"1", "product_id":"weekly_subscription", "transaction_id":"1000000526895281", "original_transaction_id":"1000000526894772", "purchase_date":"2019-05-11 16:26:20 Etc/GMT", "purchase_date_ms":"1557591980000", "purchase_date_pst":"2019-05-11 09:26:20 America/Los_Angeles", "original_purchase_date":"2019-05-11 16:17:22 Etc/GMT", "original_purchase_date_ms":"1557591442000", "original_purchase_date_pst":"2019-05-11 09:17:22 America/Los_Angeles", "expires_date":"2019-05-11 16:29:20 Etc/GMT", "expires_date_ms":"1557592160000", "expires_date_pst":"2019-05-11 09:29:20 America/Los_Angeles", "web_order_line_item_id":"1000000044334441", "is_trial_period":"false", "is_in_intro_offer_period":"false"},
{"quantity":"1", "product_id":"weekly_subscription", "transaction_id":"1000000526895686", "original_transaction_id":"1000000526894772", "purchase_date":"2019-05-11 16:29:20 Etc/GMT", "purchase_date_ms":"1557592160000", "purchase_date_pst":"2019-05-11 09:29:20 America/Los_Angeles", "original_purchase_date":"2019-05-11 16:17:22 Etc/GMT", "original_purchase_date_ms":"1557591442000", "original_purchase_date_pst":"2019-05-11 09:17:22 America/Los_Angeles", "expires_date":"2019-05-11 16:32:20 Etc/GMT", "expires_date_ms":"1557592340000", "expires_date_pst":"2019-05-11 09:32:20 America/Los_Angeles", "web_order_line_item_id":"1000000044334455", "is_trial_period":"false", "is_in_intro_offer_period":"false"},
{"quantity":"1", "product_id":"weekly_subscription", "transaction_id":"1000000526896082", "original_transaction_id":"1000000526894772", "purchase_date":"2019-05-11 16:32:20 Etc/GMT", "purchase_date_ms":"1557592340000", "purchase_date_pst":"2019-05-11 09:32:20 America/Los_Angeles", "original_purchase_date":"2019-05-11 16:17:22 Etc/GMT", "original_purchase_date_ms":"1557591442000", "original_purchase_date_pst":"2019-05-11 09:17:22 America/Los_Angeles", "expires_date":"2019-05-11 16:35:20 Etc/GMT", "expires_date_ms":"1557592520000", "expires_date_pst":"2019-05-11 09:35:20 America/Los_Angeles", "web_order_line_item_id":"1000000044334476", "is_trial_period":"false", "is_in_intro_offer_period":"false"}]},
"latest_receipt_info":[
{"quantity":"1", "product_id":"weekly_subscription", "transaction_id":"1000000526896082", "original_transaction_id":"1000000526894772", "purchase_date":"2019-05-11 16:32:20 Etc/GMT", "purchase_date_ms":"1557592340000", "purchase_date_pst":"2019-05-11 09:32:20 America/Los_Angeles", "original_purchase_date":"2019-05-11 16:17:22 Etc/GMT", "original_purchase_date_ms":"1557591442000", "original_purchase_date_pst":"2019-05-11 09:17:22 America/Los_Angeles", "expires_date":"2019-05-11 16:35:20 Etc/GMT", "expires_date_ms":"1557592520000", "expires_date_pst":"2019-05-11 09:35:20 America/Los_Angeles", "web_order_line_item_id":"1000000044334476", "is_trial_period":"false", "is_in_intro_offer_period":"false"}],
"latest_receipt":"MIIT2gYJKoZIhvcNAQcCoIITyzCCE8cCAQExCzAJBgUrDgMCGgUAMIIDewYJKoZIhvcNAQcBoIIDbASCA2gxggNkMAoCAQgCAQEEAhYAMAoCARQCAQEEAgwAMAsCAQECAQEEAwIBADALAgEDAgEBBAMMATIwCwIBCwIBAQQDAgEAMAsCAQ8CAQEEAwIBADALAgEQAgEBBAMCAQAwCwIBGQIBAQQDAgEDMAwCAQoCAQEEBBYCNCswDAIBDgIBAQQEAgIAojANAgENAgEBBAUCAwHViDANAgETAgEBBAUMAzEuMDAOAgEJAgEBBAYCBFAyNTIwGAIBBAIBAgQQ6OK/a51S6iIUQnFSi0P5zTAbAgEAAgEBBBMMEVByb2R1Y3Rpb25TYW5kYm94MBwCAQUCAQEEFFP2sppxWfTXtkBJcsvGvzKITqx/MB4CAQwCAQEEFhYUMjAxOS0wNS0xNFQwNTo0NjoxOFowHgIBEgIBAQQWFhQyMDEzLTA4LTAxVDA3OjAwOjAwWjAiAgECAgEBBBoMGGVzLm1lZGlhcmVnaWUud2hvbG92ZXNtZTAzAgEHAgEBBCsIq51ClKMuBQK1z+iZps39Vi/4u/YplwFSH6+jMq/BAQUFdf2QJhwwPbItMEACAQYCAQEEOGdS2t9XLCpzS7CyzXasBOmlm/vVg7IQpnOVHM1ormnQCDkwB0r5+Shr26dh8lV8LuZhgP/hYLuvMIIBgAIBEQIBAQSCAXYxggFyMAsCAgatAgEBBAIMADALAgIGsAIBAQQCFgAwCwICBrICAQEEAgwAMAsCAgazAgEBBAIMADALAgIGtAIBAQQCDAAwCwICBrUCAQEEAgwAMAsCAga2AgEBBAIMADAMAgIGpQIBAQQDAgEBMAwCAgarAgEBBAMCAQMwDAICBq4CAQEEAwIBADAMAgIGsQIBAQQDAgEAMAwCAga3AgEBBAMCAQAwEgICBq8CAQEECQIHA41+p2r9jDAbAgIGpwIBAQQSDBAxMDAwMDAwNTI2ODk2MDgyMBsCAgapAgEBBBIMEDEwMDAwMDA1MjY4OTQ3NzIwHgICBqYCAQEEFQwTd2Vla2x5X3N1YnNjcmlwdGlvbjAfAgIGqAIBAQQWFhQyMDE5LTA1LTExVDE2OjMyOjIwWjAfAgIGqgIBAQQWFhQyMDE5LTA1LTExVDE2OjE3OjIyWjAfAgIGrAIBAQQWFhQyMDE5LTA1LTExVDE2OjM1OjIwWqCCDmUwggV8MIIEZKADAgECAggO61eH554JjTANBgkqhkiG9w0BAQUFADCBljELMAkGA1UEBhMCVVMxEzARBgNVBAoMCkFwcGxlIEluYy4xLDAqBgNVBAsMI0FwcGxlIFdvcmxkd2lkZSBEZXZlbG9wZXIgUmVsYXRpb25zMUQwQgYDVQQDDDtBcHBsZSBXb3JsZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9ucyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNTExMTMwMjE1MDlaFw0yMzAyMDcyMTQ4NDdaMIGJMTcwNQYDVQQDDC5NYWMgQXBwIFN0b3JlIGFuZCBpVHVuZXMgU3RvcmUgUmVjZWlwdCBTaWduaW5nMSwwKgYDVQQLDCNBcHBsZSBXb3JsZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9uczETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQClz4H9JaKBW9aH7SPaMxyO4iPApcQmyz3Gn+xKDVWG/6QC15fKOVRtfX+yVBidxCxScY5ke4LOibpJ1gjltIhxzz9bRi7GxB24A6lYogQ+IXjV27fQjhKNg0xbKmg3k8LyvR7E0qEMSlhSqxLj7d0fmBWQNS3CzBLKjUiB91h4VGvojDE2H0oGDEdU8zeQuLKSiX1fpIVK4cCc4Lqku4KXY/Qrk8H9Pm/KwfU8qY9SGsAlCnYO3v6Z/v/Ca/VbXqxzUUkIVonMQ5DMjoEC0KCXtlyxoWlph5AQaCYmObgdEHOwCl3Fc9DfdjvYLdmIHuPsB8/ijtDT+iZVge/iA0kjAgMBAAGjggHXMIIB0zA/BggrBgEFBQcBAQQzMDEwLwYIKwYBBQUHMAGGI2h0dHA6Ly9vY3NwLmFwcGxlLmNvbS9vY3NwMDMtd3dkcjA0MB0GA1UdDgQWBBSRpJz8xHa3n6CK9E31jzZd7SsEhTAMBgNVHRMBAf8EAjAAMB8GA1UdIwQYMBaAFIgnFwmpthhgi+zruvZHWcVSVKO3MIIBHgYDVR0gBIIBFTCCAREwggENBgoqhkiG92NkBQYBMIH+MIHDBggrBgEFBQcCAjCBtgyBs1JlbGlhbmNlIG9uIHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0YW5jZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1zIGFuZCBjb25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5IGFuZCBjZXJ0aWZpY2F0aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMDYGCCsGAQUFBwIBFipodHRwOi8vd3d3LmFwcGxlLmNvbS9jZXJ0aWZpY2F0ZWF1dGhvcml0eS8wDgYDVR0PAQH/BAQDAgeAMBAGCiqGSIb3Y2QGCwEEAgUAMA0GCSqGSIb3DQEBBQUAA4IBAQANphvTLj3jWysHbkKWbNPojEMwgl/gXNGNvr0PvRr8JZLbjIXDgFnf4+LXLgUUrA3btrj+/DUufMutF2uOfx/kd7mxZ5W0E16mGYZ2+FogledjjA9z/Ojtxh+umfhlSFyg4Cg6wBA3LbmgBDkfc7nIBf3y3n8aKipuKwH8oCBc2et9J6Yz+PWY4L5E27FMZ/xuCk/J4gao0pfzp45rUaJahHVl0RYEYuPBX/UIqc9o2ZIAycGMs/iNAGS6WGDAfK+PdcppuVsq1h1obphC9UynNxmbzDscehlD86Ntv0hgBgw2kivs3hi1EdotI9CO/KBpnBcbnoB7OUdFMGEvxxOoMIIEIjCCAwqgAwIBAgIIAd68xDltoBAwDQYJKoZIhvcNAQEFBQAwYjELMAkGA1UEBhMCVVMxEzARBgNVBAoTCkFwcGxlIEluYy4xJjAkBgNVBAsTHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRYwFAYDVQQDEw1BcHBsZSBSb290IENBMB4XDTEzMDIwNzIxNDg0N1oXDTIzMDIwNzIxNDg0N1owgZYxCzAJBgNVBAYTAlVTMRMwEQYDVQQKDApBcHBsZSBJbmMuMSwwKgYDVQQLDCNBcHBsZSBXb3JsZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9uczFEMEIGA1UEAww7QXBwbGUgV29ybGR3aWRlIERldmVsb3BlciBSZWxhdGlvbnMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDKOFSmy1aqyCQ5SOmM7uxfuH8mkbw0U3rOfGOAYXdkXqUHI7Y5/lAtFVZYcC1+xG7BSoU+L/DehBqhV8mvexj/avoVEkkVCBmsqtsqMu2WY2hSFT2Miuy/axiV4AOsAX2XBWfODoWVN2rtCbauZ81RZJ/GXNG8V25nNYB2NqSHgW44j9grFU57Jdhav06DwY3Sk9UacbVgnJ0zTlX5ElgMhrgWDcHld0WNUEi6Ky3klIXh6MSdxmilsKP8Z35wugJZS3dCkTm59c3hTO/AO0iMpuUhXf1qarunFjVg0uat80YpyejDi+l5wGphZxWy8P3laLxiX27Pmd3vG2P+kmWrAgMBAAGjgaYwgaMwHQYDVR0OBBYEFIgnFwmpthhgi+zruvZHWcVSVKO3MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4wLgYDVR0fBCcwJTAjoCGgH4YdaHR0cDovL2NybC5hcHBsZS5jb20vcm9vdC5jcmwwDgYDVR0PAQH/BAQDAgGGMBAGCiqGSIb3Y2QGAgEEAgUAMA0GCSqGSIb3DQEBBQUAA4IBAQBPz+9Zviz1smwvj+4ThzLoBTWobot9yWkMudkXvHcs1Gfi/ZptOllc34MBvbKuKmFysa/Nw0Uwj6ODDc4dR7Txk4qjdJukw5hyhzs+r0ULklS5MruQGFNrCk4QttkdUGwhgAqJTleMa1s8Pab93vcNIx0LSiaHP7qRkkykGRIZbVf1eliHe2iK5IaMSuviSRSqpd1VAKmuu0swruGgsbwpgOYJd+W+NKIByn/c4grmO7i77LpilfMFY0GCzQ87HUyVpNur+cmV6U/kTecmmYHpvPm0KdIBembhLoz2IYrF+Hjhga6/05Cdqa3zr/04GpZnMBxRpVzscYqCtGwPDBUfMIIEuzCCA6OgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwHhcNMDYwNDI1MjE0MDM2WhcNMzUwMjA5MjE0MDM2WjBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkkakJH5HbHkdQ6wXtXnmELes2oldMVeyLGYne+Uts9QerIjAC6Bg++FAJ039BqJj50cpmnCRrEdCju+QbKsMflZ56DKRHi1vUFjczy8QPTc4UadHJGXL1XQ7Vf1+b8iUDulWPTV0N8WQ1IxVLFVkds5T39pyez1C6wVhQZ48ItCD3y6wsIG9wtj8BMIy3Q88PnT3zK0koGsj+zrW5DtleHNbLPbU6rfQPDgCSC7EhFi501TwN22IWq6NxkkdTVcGvL0Gz+PvjcM3mo0xFfh9Ma1CWQYnEdGILEINBhzOKgbEwWOxaBDKMaLOPHd5lc/9nXmW8Sdh2nzMUZaF3lMktAgMBAAGjggF6MIIBdjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUK9BpR5R2Cf70a40uQKb3R01/CF4wHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4wggERBgNVHSAEggEIMIIBBDCCAQAGCSqGSIb3Y2QFATCB8jAqBggrBgEFBQcCARYeaHR0cHM6Ly93d3cuYXBwbGUuY29tL2FwcGxlY2EvMIHDBggrBgEFBQcCAjCBthqBs1JlbGlhbmNlIG9uIHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0YW5jZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1zIGFuZCBjb25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5IGFuZCBjZXJ0aWZpY2F0aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMA0GCSqGSIb3DQEBBQUAA4IBAQBcNplMLXi37Yyb3PN3m/J20ncwT8EfhYOFG5k9RzfyqZtAjizUsZAS2L70c5vu0mQPy3lPNNiiPvl4/2vIB+x9OYOLUyDTOMSxv5pPCmv/K/xZpwUJfBdAVhEedNO3iyM7R6PVbyTi69G3cN8PReEnyvFteO3ntRcXqNx+IjXKJdXZD9Zr1KIkIxH3oayPc4FgxhtbCS+SsvhESPBgOJ4V9T0mZyCKM2r3DYLP3uujL/lTaltkwGMzd/c6ByxW69oPIQ7aunMZT7XZNn/Bh1XZp5m5MkL72NVxnn6hUrcbvZNCJBIqxw8dtk2cXmPIS4AXUKqK1drk/NAJBzewdXUhMYIByzCCAccCAQEwgaMwgZYxCzAJBgNVBAYTAlVTMRMwEQYDVQQKDApBcHBsZSBJbmMuMSwwKgYDVQQLDCNBcHBsZSBXb3JsZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9uczFEMEIGA1UEAww7QXBwbGUgV29ybGR3aWRlIERldmVsb3BlciBSZWxhdGlvbnMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkCCA7rV4fnngmNMAkGBSsOAwIaBQAwDQYJKoZIhvcNAQEBBQAEggEASyqCJc3ZjAyHmcDPVgrk6zjDxqdf6UaZWkfpU7tlQNKU2GJIZFePKEX+2HnNGQLYnYxuq/vuQ7H6EXnFzGoC8WZ8BK1Xb1+GHeYXxEjMk7XjoAwinTqAfw1aSgOgBH7yS+1dgzCwwR1+lH8oxkgbqMODmytky1NYmKdQ4Xth+WB6MdwCWIBS0Ck55PIzIOrarWnLP6ceA//CxpgVb0F+Wvr8LsVqvViLcXweJLsCnOtZ78cYAkhdQOpblUuUlE5hunhhgeVNE4Yp36/4lZxc+28K50zhYx7vFbD2B7KS9tj+XU3vBQ930G/0/iL7XoX9+AyTnPgRWQTEasHsOo6neg==",
"pending_renewal_info":[
{"expiration_intent":"1", "auto_renew_product_id":"weekly_subscription", "original_transaction_id":"1000000526894772", "is_in_billing_retry_period":"0", "product_id":"weekly_subscription", "auto_renew_status":"0"}]}
According to this https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html#//apple_ref/doc/uid/TP40010573-CH104-SW3 If I understand correctly I should use only the latest_receipt_info field to check if the user subscription is currently
active.
What about if there are more products(consumable, subscriptions, etc)?
It's ok ignore the latest_receipt field?
Should I perform any validation of the server response in the iOS device?
Related
I'm in the process of setting up receipt validation for Apple's auto-renewable subscriptions on our server and noticed some inconsistencies with the official documentation. When verifying a sandbox receipt with the sandbox verifyReceipt endpoint, the response looks as follows:
{
"auto_renew_status": 1,
"status": 0,
"auto_renew_product_id": "app.xxx",
"receipt": {
"original_purchase_date_pst": "2020-03-18 01:11:45 America/Los_Angeles",
"quantity": "1",
"unique_vendor_identifier": "6D2xxx194",
"bvrs": "2",
"expires_date_formatted": "2020-03-20 12:27:07 Etc/GMT",
"is_in_intro_offer_period": "false",
"purchase_date_ms": "1584703627636",
"expires_date_formatted_pst": "2020-03-20 05:27:07 America/Los_Angeles",
"is_trial_period": "false",
"item_id": "15xxx27",
"unique_identifier": "cd5xxx424",
"original_transaction_id": "100xxx735",
"subscription_group_identifier": "20xxx02",
"transaction_id": "100xxx439",
"web_order_line_item_id": "100xxx419",
"version_external_identifier": "0",
"purchase_date": "2020-03-20 11:27:07 Etc/GMT",
"product_id": "app.xxx",
"expires_date": "1584707227636",
"original_purchase_date": "2020-03-18 08:11:45 Etc/GMT",
"purchase_date_pst": "2020-03-20 04:27:07 America/Los_Angeles",
"bid": "app.xxx",
"original_purchase_date_ms": "1584519105000"
},
"latest_receipt_info": {
"original_purchase_date_pst": "2020-03-18 01:11:45 America/Los_Angeles",
"quantity": "1",
"unique_vendor_identifier": "6D2xxx194",
"bvrs": "2",
"expires_date_formatted": "2020-03-20 12:27:07 Etc/GMT",
"is_in_intro_offer_period": "false",
"purchase_date_ms": "1584703627000",
"expires_date_formatted_pst": "2020-03-20 05:27:07 America/Los_Angeles",
"is_trial_period": "false",
"item_id": "15xxx27",
"unique_identifier": "cd5xxx424",
"original_transaction_id": "100xxx735",
"subscription_group_identifier": "20xxx02",
"transaction_id": "100xxx439",
"bid": "app.xxx",
"web_order_line_item_id": "100xxx419",
"purchase_date": "2020-03-20 11:27:07 Etc/GMT",
"product_id": "app.xxx",
"expires_date": "1584707227000",
"original_purchase_date": "2020-03-18 08:11:45 Etc/GMT",
"purchase_date_pst": "2020-03-20 04:27:07 America/Los_Angeles",
"original_purchase_date_ms": "1584519105000"
},
"latest_receipt": "xxx"
}
I especially want to point out the following fields of that response:
{
...
"latest_receipt_info": {
...
"expires_date": "1584707227000",
"expires_date_formatted": "2020-03-20 12:27:07 Etc/GMT",
"expires_date_formatted_pst": "2020-03-20 05:27:07 America/Los_Angeles",
"subscription_group_identifier": "20xxx02",
"bid": "app.xxx",
...
},
"receipt": {
...
"expires_date": "1584707227636",
"expires_date_formatted": "2020-03-20 12:27:07 Etc/GMT",
"expires_date_formatted_pst": "2020-03-20 05:27:07 America/Los_Angeles",
"subscription_group_identifier": "20xxx02",
"bid": "app.xxx",
...
},
...
}
The inconsistencies with the official documentation are:
latest_receipt_info is documented to be an array, however, it is a single json object.
In the latest_receipt_info, the expires_date is not in "date-time format similar to the ISO 8601" as the documentation says, but looks like it is in milliseconds since epoch (what should be the expires_date_ms). However, we can find the key expires_date_formatted that is in date-time format.
The same fields as in (2) can be also found in the receipt, however, the documentation states only a key expiration_date (analogue to the expires_date in the latest_receipt_info in date-time format) and expiration_date_ms (in milliseconds since epoch).
The documented bundle_id key (here and here) is not present, but a key bid is, that contains the bundle id.
The key subscription_group_identifer does not contain the exact string entered in AppStoreConnect as subscription group identifer, as documented (here and here), but contains some integer value.
So according to the documentation, the response should look like this, for me:
{
...
"latest_receipt_info": [
{
...
"expires_date": "2020-03-20 12:27:07 Etc/GMT",
"expires_date_ms": "1584707227000",
"expires_date_pst": "2020-03-20 05:27:07 America/Los_Angeles",
"subscription_group_identifier": "MY_SUBSCRIPTION_GROUP_ID",
"bundle_id": "app.xxx",
...
}
],
"receipt": {
...
"expiration_date": "2020-03-20 12:27:07 Etc/GMT",
"expiration_date_ms": "1584707227636",
"expiration_date_pst": "2020-03-20 05:27:07 America/Los_Angeles",
"subscription_group_identifier": "MY_SUBSCRIPTION_GROUP_ID",
"bundle_id": "app.xxx",
...
},
...
}
I am not sure how to cope with that situation, is that a bug in the API or is the documentation simply wrong?
Can I expect the same inconsistencies for the production endpoint (can someone share a sample response please)?
How about the notifications in server-to-server notifications, are there also inconsistencies?
Thanks in advance!
Where did you get that receipt example? Here is a proper receipt example from verifyReceipt endoint: https://gist.github.com/ren6/3da2d14ea629ab9add489c0e6df1917c
Also I can recommend you reading article from our blog: https://blog.apphud.com/receipt-validation/
Regarding Apple server-to-server notifications, they are now unified, i.e. data is being returned in the same structure as from verifyReceipt endpoint.
For everyone that faces the same problem: We sent the wrong receipt data to our backend since we requested the receipt via the deprecated transactionReceipt and not via appStoreReceiptURL.
While testing my in-app subscription, I find out that the latest_receipt is possible to get. When it comes to running the app at the release mode on App Store, all new app users find the exception at the line of code :
let latestReceipt = responseTransaction.object(forKey: "latest_receipt") as! String
Would you please tell me under IAP documentation, what is the possible state of this the field latest_receipt? and it turns empty or null responses?
When I use sandbox, it often gives :
{
"latest_receipt": "MIIbngYJKoZIhvcNAQcCoIIbj...",
"status": 0,
"receipt": {
"download_id": 0,
"receipt_creation_date_ms": "1486371475000",
"application_version": "2",
"app_item_id": 0,
"receipt_creation_date": "2017-02-06 08:57:55 Etc/GMT",
"original_purchase_date": "2013-08-01 07:00:00 Etc/GMT",
"request_date_pst": "2017-02-06 04:41:09 America/Los_Angeles",
"original_application_version": "1.0",
"original_purchase_date_pst": "2013-08-01 00:00:00 America/Los_Angeles",
"request_date_ms": "1486384869996",
"bundle_id": "com.yourcompany.yourapp",
"request_date": "2017-02-06 12:41:09 Etc/GMT",
"original_purchase_date_ms": "1375340400000",
"in_app": [{
"purchase_date_ms": "1486371474000",
"web_order_line_item_id": "1000000034281189",
"original_purchase_date_ms": "1486371475000",
"original_purchase_date": "2017-02-06 08:57:55 Etc/GMT",
"expires_date_pst": "2017-02-06 01:00:54 America/Los_Angeles",
"original_purchase_date_pst": "2017-02-06 00:57:55 America/Los_Angeles",
"purchase_date_pst": "2017-02-06 00:57:54 America/Los_Angeles",
"expires_date_ms": "1486371654000",
"expires_date": "2017-02-06 09:00:54 Etc/GMT",
"original_transaction_id": "1000000271014363",
"purchase_date": "2017-02-06 08:57:54 Etc/GMT",
"quantity": "1",
"is_trial_period": "false",
"product_id": "com.yourcompany.yourapp",
"transaction_id": "1000000271014363"
}],
"version_external_identifier": 0,
"receipt_creation_date_pst": "2017-02-06 00:57:55 America/Los_Angeles",
"adam_id": 0,
"receipt_type": "ProductionSandbox"
},
When pushing into production, it gives something like :
{'environment': 'Production',
'receipt': {'adam_id': 1465637208,
'app_item_id': 1465637208,
'application_version': '33',
'bundle_id': 'gogoyuedu.testing.anc',
'download_id': 7505071111117518,
'in_app': [],
'original_application_version': '31',
'original_purchase_date': '2019-09-30 05:16:04 Etc/GMT',
'original_purchase_date_ms': '1569820564000',
'original_purchase_date_pst': '2019-09-29 22:16:04 '
'America/Los_Angeles',
'receipt_creation_date': '2019-10-02 08:49:40 Etc/GMT',
'receipt_creation_date_ms': '1570006180000',
'receipt_creation_date_pst': '2019-10-02 01:49:40 '
'America/Los_Angeles',
'receipt_type': 'Production',
'request_date': '2019-10-02 08:49:47 Etc/GMT',
'request_date_ms': '1570006187285',
'request_date_pst': '2019-10-02 01:49:47 America/Los_Angeles',
'version_external_identifier': 832979108},
'status': 0}
At my swift method, should I get purchases record under attribute in_app instead of latest_receipt_info ?
If so, how can I get my latest receipt at the field in_app ?
It is always a good idea to be on the safe side:
try
if let latestReceipt = responseTransaction.object(forKey: "latest_receipt") as? String {
// do something here
}
It might be the reason that the "new" users have no latest receipts but you have on your phone because you already "bought" something?!
I am trying to implement the Server-to-Server Notifications for IOS subscriptions. I have went through the Server-to-Server Notifications documentation and followed all the necessary steps. My server is in the GCM.
Now when i do a purchase in the app i am getting the notification in my server, but the response is totally different from the which is defined in the above link.
The response which i am getting contains only one field that is latest_receipt . But in the documentation they have mentioned various parameters. So, i thought i have to verify the receipt by send a post message to this endpoint . Now i am getting a json body which contains lot of information, but still i am not getting the json which is mentioned in the documentation.
The response body i am getting after verifying the purchase ( https://sandbox.itunes.apple.com/verifyReceipt)
{
"auto_renew_status": 1,
"status": 0,
"auto_renew_product_id": " ",
"receipt": {
"original_purchase_date_pst": "2019-01-09 01:26:35 America/Los_Angeles",
"quantity": "1",
"unique_vendor_identifier": " ",
"bvrs": "56",
"expires_date_formatted": "2019-07-18 06:19:17 Etc/GMT",
"is_in_intro_offer_period": "false",
"purchase_date_ms": "1563430577000",
"expires_date_formatted_pst": "2019-07-17 23:19:17 America/Los_Angeles",
"is_trial_period": "false",
"item_id": "1298435177",
"unique_identifier": " ",
"original_transaction_id": "1000000492823158",
"expires_date": "1563430757000",
"transaction_id": "1000000548145129",
"web_order_line_item_id": "1000000045717939",
"version_external_identifier": "0",
"bid": " ",
"product_id": " ",
"purchase_date": "2019-07-18 06:16:17 Etc/GMT",
"original_purchase_date": "2019-01-09 09:26:35 Etc/GMT",
"purchase_date_pst": "2019-07-17 23:16:17 America/Los_Angeles",
"original_purchase_date_ms": "1547025995000"
},
"latest_receipt_info": {
"original_purchase_date_pst": "2019-01-09 01:26:35 America/Los_Angeles",
"unique_identifier": " ",
"original_transaction_id": "1000000492823158",
"expires_date": "1563430757000",
"transaction_id": "1000000548145129",
"quantity": "1",
"product_id": " ",
"bvrs": "56",
"bid": " ",
"unique_vendor_identifier": " ",
"web_order_line_item_id": "1000000045717939",
"original_purchase_date_ms": "1547025995000",
"expires_date_formatted": "2019-07-18 06:19:17 Etc/GMT",
"purchase_date": "2019-07-18 06:16:17 Etc/GMT",
"is_in_intro_offer_period": "false",
"purchase_date_ms": "1563430577000",
"expires_date_formatted_pst": "2019-07-17 23:19:17 America/Los_Angeles",
"is_trial_period": "false",
"purchase_date_pst": "2019-07-17 23:16:17 America/Los_Angeles",
"original_purchase_date": "2019-01-09 09:26:35 Etc/GMT",
"item_id": "1298435177"
}
}
But in the documentation they have mentioned :
environment
notification_type
password
cancellation_date
cancellation_date_pst
cancellation_date_ms
web_order_line_item_id
latest_receipt
latest_receipt_info
latest_expired_receipt
latest_expired_receipt_info
auto_renew_status
auto_renew_product_id
auto_renew_status_change_date
auto_renew_status_change_date_pst
auto_renew_status_change_date_ms
I am not getting the main fields such as environment and notification_type.
Whats wrong in it ?
For the first time this is the response i am getting
{ latest_receipt: 'ewoJInNpZ25hdHVyZ'}
The response which i am getting from the server 2 server notification is on the second time (once its renewed):
{ environment: 'Sandbox',
auto_renew_status: 'false',
latest_expired_receipt: 'ewoJIn'
}
The parameters you get when you set up server 2 server notification are not exactly in the same format (or contains the same fields) as when you query verifyReceipt.
For example environment and notification_type are available only in the server 2 server notification and not in verifyReceipt.
Here is an example of the complete params you get when apple send you a notification with the server 2 server notifications -
{
"latest_receipt": "ewoXXXXX",
"latest_receipt_info": {
"original_purchase_date_pst": "2019-07-29 21:13:18 America/Los_Angeles",
"quantity": "1",
"unique_vendor_identifier": "XXX",
"original_purchase_date_ms": "1564459998000",
"expires_date_formatted": "2019-08-06 04:13:17 Etc/GMT",
"is_in_intro_offer_period": "false",
"purchase_date_ms": "1564459997000",
"expires_date_formatted_pst": "2019-08-05 21:13:17 America/Los_Angeles",
"is_trial_period": "true",
"item_id": "1452171111",
"unique_identifier": "00000",
"original_transaction_id": "0000000",
"expires_date": "00000000",
"app_item_id": "0000000",
"transaction_id": "00000000",
"bvrs": "00000",
"web_order_line_item_id": "00000000",
"version_external_identifier": "000000",
"bid": "com.XXX",
"product_id": "XXXXX",
"purchase_date": "2019-07-30 04:13:17 Etc/GMT",
"purchase_date_pst": "2019-07-29 21:13:17 America/Los_Angeles",
"original_purchase_date": "2019-07-30 04:13:18 Etc/GMT"
},
"environment": "PROD",
"auto_renew_status": "true",
"password": "*****",
"auto_renew_product_id": "com.XXXX",
"notification_type": "INITIAL_BUY"
}
Your example is from a verifyReceipt response.
The documentation is not that great about server 2 server notification but the latest wwdc video is great - https://developer.apple.com/videos/play/wwdc2019/302/
Note that quite a few of the fields in this payload are considered deprecated by now.
In-App Purchase is successfully added in Application
IAP is currently in development mode and I put the IAP Receipt Validation on server end and it's working fine.
Now I want to integrate Promo Codes for IAP. but they can be generated upon approval from Apple (IAP Review)
Question: How would I detect that an Promo Code has been used against a transaction as the receipt validation response coming from Apple Server doesn't contain transaction amount or any promo code field
example:
{
"receipt": {
"receipt_type": "ProductionSandbox",
"adam_id": 0,
"app_item_id": 0,
"bundle_id": "com.myApp",
"application_version": "14.06",
"download_id": 0,
"version_external_identifier": 0,
"receipt_creation_date": "2018-09-05 05:18:33 Etc/GMT",
"receipt_creation_date_ms": "1536124713000",
"receipt_creation_date_pst": "2018-09-04 22:18:33 America/Los_Angeles",
"request_date": "2018-09-25 04:53:09 Etc/GMT",
"request_date_ms": "1537851189557",
"request_date_pst": "2018-09-24 21:53:09 America/Los_Angeles",
"original_purchase_date": "2013-08-01 07:00:00 Etc/GMT",
"original_purchase_date_ms": "1375340400000",
"original_purchase_date_pst": "2013-08-01 00:00:00 America/Los_Angeles",
"original_application_version": "1.0",
"in_app": [
{
"quantity": "1",
"product_id": "com.myApp.testpack",
"transaction_id": "1000000439689939",
"original_transaction_id": "1000000439689939",
"purchase_date": "2018-09-05 05:03:52 Etc/GMT",
"purchase_date_ms": "1536123832000",
"purchase_date_pst": "2018-09-04 22:03:52 America/Los_Angeles",
"original_purchase_date": "2018-09-05 05:03:52 Etc/GMT",
"original_purchase_date_ms": "1536123832000",
"original_purchase_date_pst": "2018-09-04 22:03:52 America/Los_Angeles",
"is_trial_period": "false"
},
{
"quantity": "1",
"product_id": "com.myApp.testpack",
"transaction_id": "1000000439690499",
"original_transaction_id": "1000000439690499",
"purchase_date": "2018-09-05 05:09:38 Etc/GMT",
"purchase_date_ms": "1536124178000",
"purchase_date_pst": "2018-09-04 22:09:38 America/Los_Angeles",
"original_purchase_date": "2018-09-05 05:09:38 Etc/GMT",
"original_purchase_date_ms": "1536124178000",
"original_purchase_date_pst": "2018-09-04 22:09:38 America/Los_Angeles",
"is_trial_period": "false"
},
{
"quantity": "1",
"product_id": "com.myApp.testpack",
"transaction_id": "1000000439692512",
"original_transaction_id": "1000000439692512",
"purchase_date": "2018-09-05 05:18:01 Etc/GMT",
"purchase_date_ms": "1536124681000",
"purchase_date_pst": "2018-09-04 22:18:01 America/Los_Angeles",
"original_purchase_date": "2018-09-05 05:18:01 Etc/GMT",
"original_purchase_date_ms": "1536124681000",
"original_purchase_date_pst": "2018-09-04 22:18:01 America/Los_Angeles",
"is_trial_period": "false"
}
]
},
"status": 0,
"environment": "Sandbox"
}
I just want to store the coupon code which was used during the transaction in my database for some analysis.
#Paulw11 mentioned in comments there is no way of knowing from the receipt or within the app itself.
I began searching for ways to fetch details from the App Analytics and found that Apple provides Reporter which can let us download the reports from the App Analytics. This is based in Java. but people have port this to another language also.
Useful Libraries:
PHP
https://github.com/fedoco/itc-reporter
Python
https://github.com/fedoco/itc-reporter
I've been consuming the new Apple Status Update Notifications in Sandbox for auto-renewable subscriptions. The latest_receipt_info field, however, doesn't appear to match iOS 7 style receipts (e.g. no in_app field).
Is this unique to only Sandbox or is this how I should expect the notifications to work in Product?
Example receipt received from Status Update Notification after verifying with Apple:
{
"auto_renew_status": 0,
"latest_expired_receipt_info": {
"original_purchase_date_pst": "2018-01-19 16:03:00 America/Los_Angeles",
"unique_identifier": "9d89432f1fae59f25c05d44553fe40438a865b9f",
"original_transaction_id": "1000000368245564",
"expires_date": "1517368991000",
"transaction_id": "1000000371718901",
"quantity": "1",
"product_id": "abc",
"bvrs": "721180.450460032",
"bid": "ab.bc",
"unique_vendor_identifier": "9982B084-BE66-4622-ACCB-6C5B3D9C4CD4",
"web_order_line_item_id": "1000000037662245",
"original_purchase_date_ms": "1516406580000",
"expires_date_formatted": "2018-01-31 03:23:11 Etc/GMT",
"purchase_date": "2018-01-31 02:53:11 Etc/GMT",
"is_in_intro_offer_period": "false",
"purchase_date_ms": "1517367191000",
"expires_date_formatted_pst": "2018-01-30 19:23:11 America/Los_Angeles",
"is_trial_period": "false",
"purchase_date_pst": "2018-01-30 18:53:11 America/Los_Angeles",
"original_purchase_date": "2018-01-20 00:03:00 Etc/GMT",
"item_id": "1326212778"
},
"status": 21006,
"auto_renew_product_id": "abc",
"receipt": {
"original_purchase_date_pst": "2018-01-19 16:03:00 America/Los_Angeles",
"unique_identifier": "9d89432f1fae59f25c05d44553fe40438a865b9f",
"original_transaction_id": "1000000368245564",
"expires_date": "1517359990000",
"transaction_id": "1000000371686472",
"quantity": "1",
"product_id": "abc",
"bvrs": "721180.450460032",
"bid": "ab.bc",
"unique_vendor_identifier": "9982B084-BE66-4622-ACCB-6C5B3D9C4CD4",
"web_order_line_item_id": "1000000037544589",
"original_purchase_date_ms": "1516406580000",
"expires_date_formatted": "2018-01-31 00:53:10 Etc/GMT",
"purchase_date": "2018-01-31 00:23:10 Etc/GMT",
"is_in_intro_offer_period": "false",
"purchase_date_ms": "1517358190000",
"expires_date_formatted_pst": "2018-01-30 16:53:10 America/Los_Angeles",
"is_trial_period": "false",
"purchase_date_pst": "2018-01-30 16:23:10 America/Los_Angeles",
"original_purchase_date": "2018-01-20 00:03:00 Etc/GMT",
"item_id": "1326212778"
},
"expiration_intent": "1",
"is_in_billing_retry_period": "0"
}