diff --git a/appstore/model.go b/appstore/model.go index 10bc420..37d7a25 100644 --- a/appstore/model.go +++ b/appstore/model.go @@ -1,10 +1,22 @@ 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"` + // 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. + 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 @@ -44,18 +56,20 @@ 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"` - 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"` + 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"` + ExpiresDate + PurchaseDate OriginalPurchaseDate - ExpiresDate + CancellationDate + CancellationReason string `json:"cancellation_reason"` } // The Receipt type has whole data of receipt @@ -66,22 +80,36 @@ 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"` + PendingRenewalInfo []PendingRenewalInfo `json:"pending_renewal_info"` + IsRetryable bool `json:"is-retryable"` } ) diff --git a/appstore/validator.go b/appstore/validator.go index 9a660c9..31c9930 100644 --- a/appstore/validator.go +++ b/appstore/validator.go @@ -50,6 +50,9 @@ 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." @@ -59,8 +62,15 @@ func HandleError(status int) error { 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) diff --git a/appstore/validator_test.go b/appstore/validator_test.go index 47d87af..52436c1 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) @@ -60,6 +67,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)