-
Notifications
You must be signed in to change notification settings - Fork 248
/
error.go
172 lines (157 loc) · 9.59 KB
/
error.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
package api
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
)
type Error struct {
// Only errorCode and errorMessage are returned by App Store Server API.
errorCode int
errorMessage string
// retryAfter is the number of seconds after which the client can retry the request.
// This field is only set to the `Retry-After` header if you receive the HTTP 429 error, that informs you when you can next send a request.
retryAfter int64
}
func newError(errorCode int, errorMessage string) *Error {
return &Error{
errorCode: errorCode,
errorMessage: errorMessage,
}
}
type appStoreAPIErrorResp struct {
ErrorCode int `json:"errorCode"`
ErrorMessage string `json:"errorMessage"`
}
func newAppStoreAPIError(b []byte, hd http.Header) (*Error, bool) {
if len(b) == 0 {
return nil, false
}
var rErr appStoreAPIErrorResp
if err := json.Unmarshal(b, &rErr); err != nil {
return nil, false
}
if rErr.ErrorCode == 0 {
return nil, false
}
if rErr.ErrorCode == 4290000 {
retryAfter, err := strconv.ParseInt(hd.Get("Retry-After"), 10, 64)
if err == nil {
return &Error{errorCode: rErr.ErrorCode, errorMessage: rErr.ErrorMessage, retryAfter: retryAfter}, true
}
}
return &Error{errorCode: rErr.ErrorCode, errorMessage: rErr.ErrorMessage}, true
}
func newErrorFromJSON(b []byte) (*Error, bool) {
if len(b) == 0 {
return nil, false
}
var rErr appStoreAPIErrorResp
if err := json.Unmarshal(b, &rErr); err != nil {
return nil, false
}
if rErr.ErrorCode == 0 {
return nil, false
}
return &Error{errorCode: rErr.ErrorCode, errorMessage: rErr.ErrorMessage}, true
}
func (e *Error) Error() string {
return fmt.Sprintf("errorCode: %d, errorMessage: %s", e.errorCode, e.errorMessage)
}
func (e *Error) As(target interface{}) bool {
if targetErr, ok := target.(*Error); ok {
*targetErr = *e
return true
}
return false
}
func (e *Error) Is(target error) bool {
if other, ok := target.(*Error); ok && other.errorCode == e.errorCode {
return true
}
return false
}
func (e *Error) ErrorCode() int {
return e.errorCode
}
func (e *Error) ErrorMessage() string {
return e.errorMessage
}
func (e *Error) RetryAfter() int64 {
return e.retryAfter
}
func (e *Error) Retryable() bool {
// NOTE:
// RateLimitExceededError[1] could also be considered as a retryable error.
// But limits are enforced on an hourly basis[2], so you should handle exceeded rate limits gracefully instead of retrying immediately.
// Refs:
// [1] https://developer.apple.com/documentation/appstoreserverapi/ratelimitexceedederror
// [2] https://developer.apple.com/documentation/appstoreserverapi/identifying_rate_limits
switch e.errorCode {
case 4040002, 4040004, 5000001, 4040006:
return true
default:
return false
}
}
// All Error lists in https://developer.apple.com/documentation/appstoreserverapi/error_codes.
var (
// Retryable errors
AccountNotFoundRetryableError = newError(4040002, "Account not found. Please try again.")
AppNotFoundRetryableError = newError(4040004, "App not found. Please try again.")
GeneralInternalRetryableError = newError(5000001, "An unknown error occurred. Please try again.")
OriginalTransactionIdNotFoundRetryableError = newError(4040006, "Original transaction id not found. Please try again.")
// Errors
AccountNotFoundError = newError(4040001, "Account not found.")
AppNotFoundError = newError(4040003, "App not found.")
FamilySharedSubscriptionExtensionIneligibleError = newError(4030007, "Subscriptions that users obtain through Family Sharing can't get a renewal date extension directly.")
GeneralInternalError = newError(5000000, "An unknown error occurred.")
GeneralBadRequestError = newError(4000000, "Bad request.")
InvalidAppIdentifierError = newError(4000002, "Invalid request app identifier.")
InvalidEmptyStorefrontCountryCodeListError = newError(4000027, "Invalid request. If provided, the list of storefront country codes must not be empty.")
InvalidExtendByDaysError = newError(4000009, "Invalid extend by days value.")
InvalidExtendReasonCodeError = newError(4000010, "Invalid extend reason code.")
InvalidOriginalTransactionIdError = newError(4000008, "Invalid original transaction id.")
InvalidRequestIdentifierError = newError(4000011, "Invalid request identifier.")
InvalidRequestRevisionError = newError(4000005, "Invalid request revision.")
InvalidRevokedError = newError(4000030, "Invalid request. The revoked parameter is invalid.")
InvalidStatusError = newError(4000031, "Invalid request. The status parameter is invalid.")
InvalidStorefrontCountryCodeError = newError(4000028, "Invalid request. A storefront country code was invalid.")
InvalidTransactionIdError = newError(4000006, "Invalid transaction id.")
OriginalTransactionIdNotFoundError = newError(4040005, "Original transaction id not found.")
RateLimitExceededError = newError(4290000, "Rate limit exceeded.")
StatusRequestNotFoundError = newError(4040009, "The server didn't find a subscription-renewal-date extension request for this requestIdentifier and productId combination.")
SubscriptionExtensionIneligibleError = newError(4030004, "Forbidden - subscription state ineligible for extension.")
SubscriptionMaxExtensionError = newError(4030005, "Forbidden - subscription has reached maximum extension count.")
TransactionIdNotFoundError = newError(4040010, "Transaction id not found.")
// Notification test and history errors
InvalidEndDateError = newError(4000016, "Invalid request. The end date is not a timestamp value represented in milliseconds.")
InvalidNotificationTypeError = newError(4000018, "Invalid request. The notification type or subtype is invalid.")
InvalidPaginationTokenError = newError(4000014, "Invalid request. The pagination token is invalid.")
InvalidStartDateError = newError(4000015, "Invalid request. The start date is not a timestamp value represented in milliseconds.")
InvalidTestNotificationTokenError = newError(4000020, "Invalid request. The test notification token is invalid.")
InvalidInAppOwnershipTypeError = newError(4000026, "Invalid request. The in-app ownership type parameter is invalid.")
InvalidProductIdError = newError(4000023, "Invalid request. The product id parameter is invalid.")
InvalidProductTypeError = newError(4000022, "Invalid request. The product type parameter is invalid.")
InvalidSortError = newError(4000021, "Invalid request. The sort parameter is invalid.")
InvalidSubscriptionGroupIdentifierError = newError(4000024, "Invalid request. The subscription group identifier parameter is invalid.")
MultipleFiltersSuppliedError = newError(4000019, "Invalid request. Supply either a transaction id or a notification type, but not both.")
PaginationTokenExpiredError = newError(4000017, "Invalid request. The pagination token is expired.")
ServerNotificationURLNotFoundError = newError(4040007, "No App Store Server Notification URL found for provided app. Check that a URL is configured in App Store Connect for this environment.")
StartDateAfterEndDateError = newError(4000013, "Invalid request. The end date precedes the start date or the dates are the same.")
StartDateTooFarInPastError = newError(4000012, "Invalid request. The start date is earlier than the allowed start date.")
TestNotificationNotFoundError = newError(4040008, "Either the test notification token is expired or the notification and status are not yet available.")
InvalidAccountTenureError = newError(4000032, "Invalid request. The account tenure field is invalid.")
InvalidAppAccountTokenError = newError(4000033, "Invalid request. The app account token field must contain a valid UUID or an empty string.")
InvalidConsumptionStatusError = newError(4000034, "Invalid request. The consumption status field is invalid.")
InvalidCustomerConsentedError = newError(4000035, "Invalid request. The customer consented field is required and must indicate the customer consented")
InvalidDeliveryStatusError = newError(4000036, "Invalid request. The delivery status field is invalid")
InvalidLifetimeDollarsPurchasedError = newError(4000037, "Invalid request. The lifetime dollars purchased field is invalid")
InvalidLifetimeDollarsRefundedError = newError(4000038, "Invalid request. The lifetime dollars refunded field is invalid")
InvalidPlatformError = newError(4000039, "Invalid request. The platform field is invalid")
InvalidPlayTimeError = newError(4000040, "Invalid request. The playtime field is invalid")
InvalidSampleContentProvidedError = newError(4000041, "Invalid request. The sample content provided field is invalid")
InvalidUserStatusError = newError(4000042, "Invalid request. The user status field is invalid")
InvalidTransactionNotConsumableError = newError(4000043, "Invalid request. The transaction id parameter must represent a consumable in-app purchase")
InvalidTransactionTypeNotSupportedError = newError(4000047, "Invalid request. The transaction id doesn't represent a supported in-app purchase type")
)