From 2eb7bcf51ab5ffb7db937eccd29ab7bf2db1f0ef Mon Sep 17 00:00:00 2001 From: Yingjie Run Date: Thu, 20 Jul 2017 18:20:07 +0800 Subject: [PATCH 1/5] append new IAP receipt fields --- appstore/model.go | 51 +++++++++++++++++++++++++++++++++++-------- appstore/validator.go | 16 +++++++++++++- 2 files changed, 57 insertions(+), 10 deletions(-) diff --git a/appstore/model.go b/appstore/model.go index 10bc420..48fc13c 100644 --- a/appstore/model.go +++ b/appstore/model.go @@ -1,10 +1,19 @@ package appstore type ( + // https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html // The IAPRequest type has the request parameter IAPRequest struct { - ReceiptData string `json:"receipt-data"` - Password string `json:"password,omitempty"` + ReceiptData string `json:"receipt-data"` + Password string `json:"password,omitempty"` + ExcludeOldTransactions bool `json:"exclude_old_transactions,omitempty"` + } + + // The ReceiptCreationDate type indicates the date when the app receipt was created. + ReceiptCreationDate struct { + CreationDate string `json:"receipt_creation_date"` + CreationDateMS string `json:"receipt_creation_date_ms"` + CreationDatePST string `json:"receipt_creation_date_pst"` } // The RequestDate type indicates the date and time that the request was sent @@ -48,14 +57,23 @@ type ( ProductID string `json:"product_id"` TransactionID string `json:"transaction_id"` OriginalTransactionID string `json:"original_transaction_id"` - IsTrialPeriod string `json:"is_trial_period"` AppItemID string `json:"app_item_id"` VersionExternalIdentifier string `json:"version_external_identifier"` WebOrderLineItemID string `json:"web_order_line_item_id"` + + IsTrialPeriod string `json:"is_trial_period"` + SubscriptionExpirationIntent string `json:"expiration_intent"` + SubscriptionRetryFlag string `json:"is_in_billing_retry_period"` + SubscriptionAutoRenewProductID string `json:"auto_renew_product_id"` + SubscriptionAutoRenewStatus string `json:"auto_renew_status"` + SubscriptionPriceConsentStatus string `json:"price_consent_status"` + ExpiresDate + PurchaseDate OriginalPurchaseDate - ExpiresDate + CancellationDate + CancellationReason string `json:"cancellation_reason"` } // The Receipt type has whole data of receipt @@ -66,22 +84,37 @@ type ( BundleID string `json:"bundle_id"` ApplicationVersion string `json:"application_version"` DownloadID int64 `json:"download_id"` + VersionExternalIdentifier int64 `json:"version_external_identifier"` OriginalApplicationVersion string `json:"original_application_version"` InApp []InApp `json:"in_app"` + ReceiptCreationDate RequestDate OriginalPurchaseDate } + // A pending renewal may refer to a renewal that is scheduled in the future or a renewal that failed in the past for some reason. + PendingRenewalInfo struct { + SubscriptionExpirationIntent string `json:"expiration_intent"` + SubscriptionAutoRenewProductID string `json:"auto_renew_product_id"` + SubscriptionRetryFlag string `json:"is_in_billing_retry_period"` + SubscriptionAutoRenewStatus string `json:"auto_renew_status"` + SubscriptionPriceConsentStatus string `json:"price_consent_status"` + ProductID string `json:"product_id"` + } + // The IAPResponse type has the response properties // We defined each field by the current IAP response, but some fields are not mentioned // in the following Apple's document; // https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html // If you get other types or fileds from the IAP response, you should use the struct you defined. IAPResponse struct { - Status int `json:"status"` - Environment string `json:"environment"` - Receipt Receipt `json:"receipt"` - LatestReceiptInfo []InApp `json:"latest_receipt_info"` - LatestReceipt string `json:"latest_receipt"` + Status int `json:"status"` + Environment string `json:"environment"` + Receipt Receipt `json:"receipt"` + LatestReceiptInfo []InApp `json:"latest_receipt_info"` + LatestReceipt string `json:"latest_receipt"` + LatestExpiredReceiptInfo InApp `json:"latest_expired_receipt_info"` + PendingRenewalInfo []PendingRenewalInfo `json:"pending_renewal_info"` + IsRetryable bool `json:"is-retryable"` } ) diff --git a/appstore/validator.go b/appstore/validator.go index 9a660c9..bc8065a 100644 --- a/appstore/validator.go +++ b/appstore/validator.go @@ -50,17 +50,31 @@ func HandleError(status int) error { case 21003: message = "The receipt could not be authenticated." + case 21004: + message = "The shared secret you provided does not match the shared secret on file for your account." + case 21005: message = "The receipt server is not currently available." + case 21006: + // Only returned for iOS 6 style transaction receipts for auto-renewable subscriptions. + message = "This receipt is valid but the subscription has expired. When this status code is returned to your server, the receipt data is also decoded and returned as part of the response." + case 21007: message = "This receipt is from the test environment, but it was sent to the production environment for verification. Send it to the test environment instead." case 21008: message = "This receipt is from the production environment, but it was sent to the test environment for verification. Send it to the production environment instead." + case 21010: + message = "This receipt could not be authorized. Treat this the same as if a purchase was never made." + default: - message = "An unknown error occurred" + if status >= 21100 && status <= 21199 { + message = "Internal data access error." + } else { + message = "An unknown error occurred" + } } return errors.New(message) From c9721f9346db2e456b61fdc77684f3292464de17 Mon Sep 17 00:00:00 2001 From: Yingjie Run Date: Thu, 20 Jul 2017 18:32:47 +0800 Subject: [PATCH 2/5] add new errors test case --- appstore/validator_test.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/appstore/validator_test.go b/appstore/validator_test.go index 47d87af..2eed017 100644 --- a/appstore/validator_test.go +++ b/appstore/validator_test.go @@ -39,6 +39,13 @@ func TestHandleError(t *testing.T) { t.Errorf("got %v\nwant %v", actual, expected) } + // status 21004 + expected = errors.New("The shared secret you provided does not match the shared secret on file for your account.") + actual = HandleError(21004) + if !reflect.DeepEqual(actual, expected) { + t.Errorf("got %v\nwant %v", actual, expected) + } + // status 21005 expected = errors.New("The receipt server is not currently available.") actual = HandleError(21005) @@ -46,6 +53,13 @@ func TestHandleError(t *testing.T) { t.Errorf("got %v\nwant %v", actual, expected) } + // status 21006 + expected = errors.New("This receipt is valid but the subscription has expired. When this status code is returned to your server, the receipt data is also decoded and returned as part of the response.") + actual = HandleError(21006) + if !reflect.DeepEqual(actual, expected) { + t.Errorf("got %v\nwant %v", actual, expected) + } + // status 21007 expected = errors.New("This receipt is from the test environment, but it was sent to the production environment for verification. Send it to the test environment instead.") actual = HandleError(21007) @@ -60,6 +74,20 @@ func TestHandleError(t *testing.T) { t.Errorf("got %v\nwant %v", actual, expected) } + // status 21010 + expected = errors.New("This receipt could not be authorized. Treat this the same as if a purchase was never made.") + actual = HandleError(21010) + if !reflect.DeepEqual(actual, expected) { + t.Errorf("got %v\nwant %v", actual, expected) + } + + // status 21100 - 21199 + expected = errors.New("Internal data access error.") + actual = HandleError(21155) + if !reflect.DeepEqual(actual, expected) { + t.Errorf("got %v\nwant %v", actual, expected) + } + // status unknown expected = errors.New("An unknown error occurred") actual = HandleError(100) From 7808531e12b36d1c63f74962184c64ddbd3e70ad Mon Sep 17 00:00:00 2001 From: Yingjie Run Date: Fri, 21 Jul 2017 17:35:35 +0800 Subject: [PATCH 3/5] fix: error iap request fields --- appstore/model.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appstore/model.go b/appstore/model.go index 48fc13c..b75e6a6 100644 --- a/appstore/model.go +++ b/appstore/model.go @@ -6,7 +6,7 @@ type ( IAPRequest struct { ReceiptData string `json:"receipt-data"` Password string `json:"password,omitempty"` - ExcludeOldTransactions bool `json:"exclude_old_transactions,omitempty"` + ExcludeOldTransactions bool `json:"exclude-old-transactions"` } // The ReceiptCreationDate type indicates the date when the app receipt was created. From 58b1c636fd9931adf56b8f5a0fb2a6087e03657a Mon Sep 17 00:00:00 2001 From: Yingjie Run Date: Mon, 24 Jul 2017 17:43:16 +0800 Subject: [PATCH 4/5] Remove iOS 6 style transaction receipt fields --- appstore/model.go | 15 +++++++-------- appstore/validator.go | 4 ---- appstore/validator_test.go | 7 ------- 3 files changed, 7 insertions(+), 19 deletions(-) diff --git a/appstore/model.go b/appstore/model.go index b75e6a6..5aa9595 100644 --- a/appstore/model.go +++ b/appstore/model.go @@ -108,13 +108,12 @@ type ( // https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html // If you get other types or fileds from the IAP response, you should use the struct you defined. IAPResponse struct { - Status int `json:"status"` - Environment string `json:"environment"` - Receipt Receipt `json:"receipt"` - LatestReceiptInfo []InApp `json:"latest_receipt_info"` - LatestReceipt string `json:"latest_receipt"` - LatestExpiredReceiptInfo InApp `json:"latest_expired_receipt_info"` - PendingRenewalInfo []PendingRenewalInfo `json:"pending_renewal_info"` - IsRetryable bool `json:"is-retryable"` + Status int `json:"status"` + Environment string `json:"environment"` + Receipt Receipt `json:"receipt"` + LatestReceiptInfo []InApp `json:"latest_receipt_info"` + LatestReceipt string `json:"latest_receipt"` + PendingRenewalInfo []PendingRenewalInfo `json:"pending_renewal_info"` + IsRetryable bool `json:"is-retryable"` } ) diff --git a/appstore/validator.go b/appstore/validator.go index bc8065a..31c9930 100644 --- a/appstore/validator.go +++ b/appstore/validator.go @@ -56,10 +56,6 @@ func HandleError(status int) error { case 21005: message = "The receipt server is not currently available." - case 21006: - // Only returned for iOS 6 style transaction receipts for auto-renewable subscriptions. - message = "This receipt is valid but the subscription has expired. When this status code is returned to your server, the receipt data is also decoded and returned as part of the response." - case 21007: message = "This receipt is from the test environment, but it was sent to the production environment for verification. Send it to the test environment instead." diff --git a/appstore/validator_test.go b/appstore/validator_test.go index 2eed017..52436c1 100644 --- a/appstore/validator_test.go +++ b/appstore/validator_test.go @@ -53,13 +53,6 @@ func TestHandleError(t *testing.T) { t.Errorf("got %v\nwant %v", actual, expected) } - // status 21006 - expected = errors.New("This receipt is valid but the subscription has expired. When this status code is returned to your server, the receipt data is also decoded and returned as part of the response.") - actual = HandleError(21006) - if !reflect.DeepEqual(actual, expected) { - t.Errorf("got %v\nwant %v", actual, expected) - } - // status 21007 expected = errors.New("This receipt is from the test environment, but it was sent to the production environment for verification. Send it to the test environment instead.") actual = HandleError(21007) From 4b4785955acc4a9b09760e11bd5734a8463300f5 Mon Sep 17 00:00:00 2001 From: Yingjie Run Date: Tue, 25 Jul 2017 14:45:03 +0800 Subject: [PATCH 5/5] Remove the fields which are not existing in the raw response from AppStore --- appstore/model.go | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/appstore/model.go b/appstore/model.go index 5aa9595..37d7a25 100644 --- a/appstore/model.go +++ b/appstore/model.go @@ -4,9 +4,12 @@ type ( // https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html // The IAPRequest type has the request parameter IAPRequest struct { - ReceiptData string `json:"receipt-data"` - Password string `json:"password,omitempty"` - ExcludeOldTransactions bool `json:"exclude-old-transactions"` + ReceiptData string `json:"receipt-data"` + // Only used for receipts that contain auto-renewable subscriptions. + Password string `json:"password,omitempty"` + // Only used for iOS7 style app receipts that contain auto-renewable or non-renewing subscriptions. + // If value is true, response includes only the latest renewal transaction for any subscriptions. + ExcludeOldTransactions bool `json:"exclude-old-transactions"` } // The ReceiptCreationDate type indicates the date when the app receipt was created. @@ -53,20 +56,13 @@ type ( // The InApp type has the receipt attributes InApp struct { - Quantity string `json:"quantity"` - ProductID string `json:"product_id"` - TransactionID string `json:"transaction_id"` - OriginalTransactionID string `json:"original_transaction_id"` - AppItemID string `json:"app_item_id"` - VersionExternalIdentifier string `json:"version_external_identifier"` - WebOrderLineItemID string `json:"web_order_line_item_id"` + Quantity string `json:"quantity"` + ProductID string `json:"product_id"` + TransactionID string `json:"transaction_id"` + OriginalTransactionID string `json:"original_transaction_id"` + WebOrderLineItemID string `json:"web_order_line_item_id"` - IsTrialPeriod string `json:"is_trial_period"` - SubscriptionExpirationIntent string `json:"expiration_intent"` - SubscriptionRetryFlag string `json:"is_in_billing_retry_period"` - SubscriptionAutoRenewProductID string `json:"auto_renew_product_id"` - SubscriptionAutoRenewStatus string `json:"auto_renew_status"` - SubscriptionPriceConsentStatus string `json:"price_consent_status"` + IsTrialPeriod string `json:"is_trial_period"` ExpiresDate PurchaseDate