How To Validate Purchase Receipt With The App Store

Introduction

Verifying a receipt with the app store is a way to ensure that a purchase made on the App Store is legitimate. This process is typically used by the developers to validate in-app purchases made by users within their apps. To verify a receipt, the developer will first need to obtain the receipt data from the user's device. This can be done by sending a request to the App Store's receipt validation endpoint, which will return the receipt data in JSON format.

Agenda for the article

  • Configure prerequisites in the appsettings.json file
  • To verify the purchase receipt with App Store

Configure Prerequisites

First, you will need to obtain an API key from the App Store. This can usually be found in the developer portal for the specific store given here. We have two different APIs for the Sandbox environment and for the Production environment. Sandbox API is used for testing purposes and to test whether your method is working fine or not. So we will be needing three things as given below

  • App Store endpoint
  • Purchase receipt from the App Store
  • A secret key shared by your App Store

You will add these endpoints and secret keys to the web.config file as given below

<!--API for sandbox used for testing-->
<add key="VerifyReceiptIOS" value="https://sandbox.itunes.apple.com/verifyReceipt" />
<add key="VerifyReceiptKey" value="XXXXXXXXXXXXdc3152f4c637" />
	 
<!--API for production-->
<!--<add key="VerifyReceiptIOS" value = "https://buy.itunes.apple.com/verifyReceipt"/>-->
<!--<add key="VerifyReceiptKey" value="" />-->

In the above code, you have added an API endpoint and a secret key shared by App Store. Now, you will create a function in your code that will make a request to the App Store API using an API key. This function should take in the purchase receipt as a parameter.

Verify Receipt with App Store API

Now, you will create a method with a string parameter as a receipt as given below

public async Task<int> CheckReceiptWithAppStore(string receiptData)
{
    string responseStr = null;
    int proUserStatus = 1;
    string uri = ConfigurationManager.AppSettings["VerifyReceiptIOS"];
    string secretKey = ConfigurationManager.AppSettings["VerifyReceiptKey"];
    var json = new JObject(new JProperty("exclude-old-transactions", true), new 
     JProperty("receipt-data", receiptData), new JProperty("password", 
     secretKey)).ToString();
    using (var httpClient = new HttpClient())
    {

        if (receiptData != null)
        {
            HttpContent content = new StringContent(json);
            try
            {
                
                Task<HttpResponseMessage> getResponse = httpClient.PostAsync(uri, content);
                HttpResponseMessage response = await getResponse;
                responseStr = await response.Content.ReadAsStringAsync();
                ValidateResponse vres = JsonConvert.DeserializeObject<ValidateResponse>(responseStr);
                //if the receipt is valid having status 0
                if (vres.status == 0)
                {
                     //checks if the receipt has expired or not
                    if (DateTime.Parse(vres.latest_receipt_info[0].expires_date.Substring(0,18)) < DateTime.UtcNow)
                    {
                        proUserStatus = 2;
                    }
                    else
                    {
                        proUserStatus = 0;
                    }
                }
                else
                {
                    proUserStatus = vres.status;
                }
                
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
    return proUserStatus;
}

Within the function, use the HttpClient class to make a request to the App Store's API endpoint for verifying the purchase receipts. The App Store will return a response indicating whether the receipt is valid or not. Parse the response and return the appropriate result and make sure to implement proper error handling.  With this, you can also verify whether the subscription has expired or not as given in line no 26.

The response that you will get in responseStr variable is given below

{
	"environment": "Sandbox",
	"receipt": {
		"receipt_type": "ProductionSandbox",
		"adam_id": 0,
		"app_item_id": 0,
		"bundle_id": "",
		"application_version": "1.0",
		"download_id": 0,
		"version_external_identifier": 0,
		"receipt_creation_date": "2023-01-17 11:19:06 Etc/GMT",
		"receipt_creation_date_ms": "1673954346000",
		"receipt_creation_date_pst": "2023-01-17 03:19:06 America/Los_Angeles",
		"request_date": "2023-01-24 09:01:19 Etc/GMT",
		"request_date_ms": "1674550879131",
		"request_date_pst": "2023-01-24 01:01:19 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": "",
				"transaction_id": "2000000154",
				"original_transaction_id": "20000001",
				"purchase_date": "2022-09-15 05:18:31 Etc/GMT",
				"purchase_date_ms": "1663219111000",
				"purchase_date_pst": "2022-09-14 22:18:31 America/Los_Angeles",
				"original_purchase_date": "2022-09-15 05:18:33 Etc/GMT",
				"original_purchase_date_ms": "1663219113000",
				"original_purchase_date_pst": "2022-09-14 22:18:33 America/Los_Angeles",
				"expires_date": "2022-09-15 06:18:31 Etc/GMT",
				"expires_date_ms": "1663222711000",
				"expires_date_pst": "2022-09-14 23:18:31 America/Los_Angeles",
				"web_order_line_item_id": "20000008966",
				"is_trial_period": "false",
				"is_in_intro_offer_period": "false",
				"in_app_ownership_type": "PURCHASED"
			},
			
		]
	},
	"latest_receipt_info": [
		{
			"quantity": "1",
			"product_id": "c",
			"transaction_id": "2000000252433",
			"original_transaction_id": "2000154976692",
			"purchase_date": "2023-01-17 22:19:50 Etc/GMT",
			"purchase_date_ms": "1673993000",
			"purchase_date_pst": "2023-01-17 14:19:50 America/Los_Angeles",
			"original_purchase_date": "2022-09-15 05:18:33 Etc/GMT",
			"original_purchase_date_ms": "1663219113000",
			"original_purchase_date_pst": "2022-09-14 22:18:33 America/Los_Angeles",
			"expires_date": "2023-01-173:19:50 Etc/GMT",
			"expires_date_ms": "16739970000",
			"expires_date_pst": "2023-01-17 15:19:50 America/Los_Angeles",
			"web_order_line_item_id": "20000018963633",
			"is_trial_period": "false",
			"is_in_intro_offer_period": "false",
			"in_app_ownership_type": "PURCHASED",
			"subscription_group_identifier": "21018013"
		},
		{
			"quantity": "1",
			"product_id": "",
			"transaction_id": "2000000252192",
			"original_transaction_id": "2000156504614",
			"purchase_date": "2023-01-17 14:36:39 Etc/GMT",
			"purchase_date_ms": "1673966199000",
			"purchase_date_pst": "2023-01-17 06:36:39 America/Los_Angeles",
			"original_purchase_date": "2022-09-16 11:33:38 Etc/GMT",
			"original_purchase_date_ms": "16628018000",
			"original_purchase_date_pst": "2022-09-16 04:33:38 America/Los_Angeles",
			"expires_date": "2023-01-17 14:41:39 Etc/GMT",
			"expires_date_ms": "16739664900",
			"expires_date_pst": "2023-01-17 06:41:39 America/Los_Angeles",
			"web_order_line_item_id": "20000018941613",
			"is_trial_period": "false",
			"is_in_intro_offer_period": "false",
			"in_app_ownership_type": "PURCHASED",
			"subscription_group_identifier": "21018012"
		}
	],
	"latest_receipt": "KgBM",
	"pending_renewal_info": [
		{
			"expiration_intent": "1",
			"auto_renew_product_id": "",
			"is_in_billing_retry_period": "0",
			"product_id": "",
			"original_transaction_id": "2000006504614",
			"auto_renew_status": "0"
		},
		{
			"expiration_intent": "1",
			"auto_renew_product_id": "",
			"is_in_billing_retry_period": "0",
			"product_id": "com.honeydew.year",
			"original_transaction_id": "200000076692",
			"auto_renew_status": "0"
		}
	],
	"status": 0
}

If the status is 0 i.e. receipt is valid and you are getting a valid response. 

Multiple Status Codes

Depending upon the receipt you will get multiple status codes as given below.

  • 21000: The request to the App Store was not made using the HTTP POST request method.
  • 21001: The status code is no longer sent by the App Store.
  • 21002: The data in the receipt-data property was malformed or the service experienced a temporary issue.
  • 21003: The receipt could not be authenticated.
  • 21004: The shared secret you provided does not match the shared secret on the file for your account. 
  • 21005: The receipt server was temporarily unable to provide the receipt.
  • 21006: The receipt is valid but the subscription has expired.
  • 21007: This receipt is from the test environment.
  • 21008: This receipt is from the production environment.
  • 21009: Internal data access error.
  • 21010: The user account cannot be found or deleted.

Conclusion

To verify a purchase receipt using App Store API, you will need to make a request to the App Store server with the receipt data. The receipt data can be obtained from the user's device and should be sent in the body of the request as a JSON object. Also, you need to handle the response from the server, which will contain information about the purchase such as the product ID, the transaction ID, and the purchase date. If the receipt is valid, the response will also contain a "status" key with the value 0. Verifying receipts with the App Store is an important step in ensuring the security and integrity of in-app purchases. It helps protect both developer and the user and ensures that only legitimate purchases are processed.

Thank You and Stay Tuned for More

Popular Articles from my Account


Similar Articles