Skip to content

Commit 8ddfbdb

Browse files
authored
Merge pull request #114 from jun06t/handle-appstore-http-error
Handle http status 5xx error
2 parents 052ce72 + dd8af1c commit 8ddfbdb

File tree

4 files changed

+87
-67
lines changed

4 files changed

+87
-67
lines changed

.travis.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
language: go
22
go:
3-
- 1.12.5
3+
- 1.13.x
44
env:
55
global:
66
- GO111MODULE=on

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
go-iap
22
======
33

4-
![](https://img.shields.io/badge/golang-1.12-blue.svg?style=flat)
4+
![](https://img.shields.io/badge/golang-1.13-blue.svg?style=flat)
55
[![Build Status](https://travis-ci.org/awa/go-iap.svg?branch=master)](https://travis-ci.org/awa/go-iap)
66
[![codecov.io](https://codecov.io/github/awa/go-iap/coverage.svg?branch=master)](https://codecov.io/github/awa/go-iap?branch=master)
77

appstore/validator.go

+35-22
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"context"
66
"encoding/json"
77
"errors"
8+
"fmt"
89
"io/ioutil"
910
"net/http"
1011
"time"
@@ -33,47 +34,53 @@ type Client struct {
3334
httpCli *http.Client
3435
}
3536

37+
var (
38+
ErrAppStoreServer = errors.New("AppStore server error")
39+
40+
ErrInvalidJSON = errors.New("The App Store could not read the JSON object you provided.")
41+
ErrInvalidReceiptData = errors.New("The data in the receipt-data property was malformed or missing.")
42+
ErrReceiptUnauthenticated = errors.New("The receipt could not be authenticated.")
43+
ErrInvalidSharedSecret = errors.New("The shared secret you provided does not match the shared secret on file for your account.")
44+
ErrServerUnavailable = errors.New("The receipt server is not currently available.")
45+
ErrReceiptIsForTest = 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.")
46+
ErrReceiptIsForProduction = errors.New("This receipt is from the production environment, but it was sent to the test environment for verification. Send it to the production environment instead.")
47+
ErrReceiptUnauthorized = errors.New("This receipt could not be authorized. Treat this the same as if a purchase was never made.")
48+
49+
ErrInternalDataAccessError = errors.New("Internal data access error.")
50+
ErrUnknown = errors.New("An unknown error occurred")
51+
)
52+
3653
// HandleError returns error message by status code
3754
func HandleError(status int) error {
38-
var message string
39-
55+
var e error
4056
switch status {
4157
case 0:
4258
return nil
43-
4459
case 21000:
45-
message = "The App Store could not read the JSON object you provided."
46-
60+
e = ErrInvalidJSON
4761
case 21002:
48-
message = "The data in the receipt-data property was malformed or missing."
49-
62+
e = ErrInvalidReceiptData
5063
case 21003:
51-
message = "The receipt could not be authenticated."
52-
64+
e = ErrReceiptUnauthenticated
5365
case 21004:
54-
message = "The shared secret you provided does not match the shared secret on file for your account."
55-
66+
e = ErrInvalidSharedSecret
5667
case 21005:
57-
message = "The receipt server is not currently available."
58-
68+
e = ErrServerUnavailable
5969
case 21007:
60-
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."
61-
70+
e = ErrReceiptIsForTest
6271
case 21008:
63-
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."
64-
72+
e = ErrReceiptIsForProduction
6573
case 21010:
66-
message = "This receipt could not be authorized. Treat this the same as if a purchase was never made."
67-
74+
e = ErrReceiptUnauthorized
6875
default:
6976
if status >= 21100 && status <= 21199 {
70-
message = "Internal data access error."
77+
e = ErrInternalDataAccessError
7178
} else {
72-
message = "An unknown error occurred"
79+
e = ErrUnknown
7380
}
7481
}
7582

76-
return errors.New(message)
83+
return fmt.Errorf("status %d: %w", status, e)
7784
}
7885

7986
// New creates a client object
@@ -115,6 +122,9 @@ func (c *Client) Verify(ctx context.Context, reqBody IAPRequest, result interfac
115122
return err
116123
}
117124
defer resp.Body.Close()
125+
if resp.StatusCode >= 500 {
126+
return fmt.Errorf("Received http status code %d from the App Store: %w", resp.StatusCode, ErrAppStoreServer)
127+
}
118128
return c.parseResponse(resp, result, ctx, reqBody)
119129
}
120130

@@ -153,6 +163,9 @@ func (c *Client) parseResponse(resp *http.Response, result interface{}, ctx cont
153163
return err
154164
}
155165
defer resp.Body.Close()
166+
if resp.StatusCode >= 500 {
167+
return fmt.Errorf("Received http status code %d from the App Store Sandbox: %w", resp.StatusCode, ErrAppStoreServer)
168+
}
156169

157170
return json.NewDecoder(resp.Body).Decode(result)
158171
}

appstore/validator_test.go

+50-43
Original file line numberDiff line numberDiff line change
@@ -26,60 +26,60 @@ func TestHandleError(t *testing.T) {
2626
{
2727
name: "status 21000",
2828
in: 21000,
29-
out: errors.New("The App Store could not read the JSON object you provided."),
29+
out: ErrInvalidJSON,
3030
},
3131
{
3232
name: "status 21002",
3333
in: 21002,
34-
out: errors.New("The data in the receipt-data property was malformed or missing."),
34+
out: ErrInvalidReceiptData,
3535
},
3636
{
3737
name: "status 21003",
3838
in: 21003,
39-
out: errors.New("The receipt could not be authenticated."),
39+
out: ErrReceiptUnauthenticated,
4040
},
4141
{
4242
name: "status 21004",
4343
in: 21004,
44-
out: errors.New("The shared secret you provided does not match the shared secret on file for your account."),
44+
out: ErrInvalidSharedSecret,
4545
},
4646
{
4747
name: "status 21005",
4848
in: 21005,
49-
out: errors.New("The receipt server is not currently available."),
49+
out: ErrServerUnavailable,
5050
},
5151
{
5252
name: "status 21007",
5353
in: 21007,
54-
out: 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."),
54+
out: ErrReceiptIsForTest,
5555
},
5656
{
5757
name: "status 21008",
5858
in: 21008,
59-
out: errors.New("This receipt is from the production environment, but it was sent to the test environment for verification. Send it to the production environment instead."),
59+
out: ErrReceiptIsForProduction,
6060
},
6161
{
6262
name: "status 21010",
6363
in: 21010,
64-
out: errors.New("This receipt could not be authorized. Treat this the same as if a purchase was never made."),
64+
out: ErrReceiptUnauthorized,
6565
},
6666
{
6767
name: "status 21100 ~ 21199",
6868
in: 21100,
69-
out: errors.New("Internal data access error."),
69+
out: ErrInternalDataAccessError,
7070
},
7171
{
7272
name: "status unknown",
7373
in: 100,
74-
out: errors.New("An unknown error occurred"),
74+
out: ErrUnknown,
7575
},
7676
}
7777

7878
for _, v := range tests {
7979
t.Run(v.name, func(t *testing.T) {
8080
out := HandleError(v.in)
8181

82-
if !reflect.DeepEqual(out, v.out) {
82+
if !errors.Is(out, v.out) {
8383
t.Errorf("input: %d\ngot: %v\nwant: %v\n", v.in, out, v.out)
8484
}
8585
})
@@ -180,29 +180,30 @@ func TestResponses(t *testing.T) {
180180
result := &IAPResponse{}
181181

182182
type testCase struct {
183+
name string
183184
testServer *httptest.Server
184185
sandboxServ *httptest.Server
185186
expected *IAPResponse
186187
}
187188

188189
testCases := []testCase{
189-
// VerifySandboxReceipt
190190
{
191+
name: "VerifySandboxReceipt",
191192
testServer: httptest.NewServer(serverWithResponse(http.StatusOK, `{"status": 21007}`)),
192193
sandboxServ: httptest.NewServer(serverWithResponse(http.StatusOK, `{"status": 0}`)),
193194
expected: &IAPResponse{
194195
Status: 0,
195196
},
196197
},
197-
// VerifyBadPayload
198198
{
199+
name: "VerifyBadPayload",
199200
testServer: httptest.NewServer(serverWithResponse(http.StatusOK, `{"status": 21002}`)),
200201
expected: &IAPResponse{
201202
Status: 21002,
202203
},
203204
},
204-
// SuccessPayload
205205
{
206+
name: "SuccessPayload",
206207
testServer: httptest.NewServer(serverWithResponse(http.StatusBadRequest, `{"status": 0}`)),
207208
expected: &IAPResponse{
208209
Status: 0,
@@ -213,57 +214,65 @@ func TestResponses(t *testing.T) {
213214
client := New()
214215
client.SandboxURL = "localhost"
215216

216-
for i, tc := range testCases {
217-
defer tc.testServer.Close()
218-
client.ProductionURL = tc.testServer.URL
219-
if tc.sandboxServ != nil {
220-
client.SandboxURL = tc.sandboxServ.URL
221-
}
217+
for _, tc := range testCases {
218+
t.Run(tc.name, func(t *testing.T) {
219+
defer tc.testServer.Close()
220+
client.ProductionURL = tc.testServer.URL
221+
if tc.sandboxServ != nil {
222+
client.SandboxURL = tc.sandboxServ.URL
223+
}
222224

223-
ctx := context.Background()
224-
err := client.Verify(ctx, req, result)
225-
if err != nil {
226-
t.Errorf("Test case %d - %s", i, err.Error())
227-
}
228-
if !reflect.DeepEqual(result, tc.expected) {
229-
t.Errorf("Test case %d - got %v\nwant %v", i, result, tc.expected)
230-
}
225+
ctx := context.Background()
226+
err := client.Verify(ctx, req, result)
227+
if err != nil {
228+
t.Errorf("%s", err)
229+
}
230+
if !reflect.DeepEqual(result, tc.expected) {
231+
t.Errorf("got %v\nwant %v", result, tc.expected)
232+
}
233+
})
231234
}
232235
}
233236

234-
func TestErrors(t *testing.T) {
237+
func TestHttpStatusErrors(t *testing.T) {
235238
req := IAPRequest{
236239
ReceiptData: "dummy data",
237240
}
238241
result := &IAPResponse{}
239242

240243
type testCase struct {
244+
name string
241245
testServer *httptest.Server
246+
err error
242247
}
243248

244249
testCases := []testCase{
245-
// VerifySandboxReceiptFailure
246250
{
247-
testServer: httptest.NewServer(serverWithResponse(http.StatusOK, `{"status": 21007}`)),
251+
name: "status 200",
252+
testServer: httptest.NewServer(serverWithResponse(http.StatusOK, `{"status": 21000}`)),
253+
err: nil,
248254
},
249-
// VerifyBadResponse
250255
{
256+
name: "status 500",
251257
testServer: httptest.NewServer(serverWithResponse(http.StatusInternalServerError, `qwerty!@#$%^`)),
258+
err: ErrAppStoreServer,
252259
},
253260
}
254261

255262
client := New()
256263
client.SandboxURL = "localhost"
257264

258-
for i, tc := range testCases {
259-
defer tc.testServer.Close()
260-
client.ProductionURL = tc.testServer.URL
265+
for _, tc := range testCases {
266+
t.Run(tc.name, func(t *testing.T) {
267+
defer tc.testServer.Close()
268+
client.ProductionURL = tc.testServer.URL
261269

262-
ctx := context.Background()
263-
err := client.Verify(ctx, req, result)
264-
if err == nil {
265-
t.Errorf("Test case %d - expected error to be not nil since the sandbox is not responding", i)
266-
}
270+
ctx := context.Background()
271+
err := client.Verify(ctx, req, result)
272+
if !errors.Is(err, tc.err) {
273+
t.Errorf("expected error to be not nil since the sandbox is not responding")
274+
}
275+
})
267276
}
268277
}
269278

@@ -297,12 +306,10 @@ func serverWithResponse(statusCode int, response string) http.Handler {
297306
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
298307
if "POST" == r.Method {
299308
w.Header().Set("Content-Type", "application/json")
309+
w.WriteHeader(statusCode)
300310
w.Write([]byte(response))
301-
return
302311
} else {
303312
w.Write([]byte(`unsupported request`))
304313
}
305-
306-
w.WriteHeader(statusCode)
307314
})
308315
}

0 commit comments

Comments
 (0)