From 132768c0fe81e5727f6115f37bf7699f3ff97dad Mon Sep 17 00:00:00 2001 From: Timothy Lock Date: Wed, 17 Jan 2018 16:02:52 -0500 Subject: [PATCH] Use new struct to check status & Remove IAP_ENVIRONMENT Use a new struct called `HttpStatusResponse` to check the status code returned in the receipt. This is ios-type independent and will redirect the receipt to sandbox if needed. Following Apple's recommendation, always hit production and then sandbox. There is no more need to specify which environment to hit. --- appstore/model.go | 8 +++++++- appstore/validator.go | 34 +++++++++++-------------------- appstore/validator_test.go | 41 ++++++++++++++++++-------------------- 3 files changed, 38 insertions(+), 45 deletions(-) diff --git a/appstore/model.go b/appstore/model.go index 37d7a25..9f45d4f 100644 --- a/appstore/model.go +++ b/appstore/model.go @@ -102,7 +102,7 @@ type ( // 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. + // If you get other types or fields from the IAP response, you should use the struct you defined. IAPResponse struct { Status int `json:"status"` Environment string `json:"environment"` @@ -112,4 +112,10 @@ type ( PendingRenewalInfo []PendingRenewalInfo `json:"pending_renewal_info"` IsRetryable bool `json:"is-retryable"` } + + // The HttpStatusResponse struct contains the status code returned by the store + // Used as a workaround to detect when to hit the production appstore or sandbox appstore regardless of receipt type + HttpStatusResponse struct { + Status int `json:"status"` + } ) diff --git a/appstore/validator.go b/appstore/validator.go index 02e4922..daaacbb 100644 --- a/appstore/validator.go +++ b/appstore/validator.go @@ -5,7 +5,6 @@ import ( "encoding/json" "errors" "net/http" - "os" "time" ) @@ -18,8 +17,7 @@ const ( // Config is a configuration to initialize client type Config struct { - IsProduction bool - TimeOut time.Duration + TimeOut time.Duration } // IAPClient is an interface to call validation API in App Store @@ -29,9 +27,9 @@ type IAPClient interface { // Client implements IAPClient type Client struct { - URL string - TimeOut time.Duration - SandboxURL string + ProductionURL string + SandboxURL string + TimeOut time.Duration } // HandleError returns error message by status code @@ -80,12 +78,9 @@ func HandleError(status int) error { // New creates a client object func New() Client { client := Client{ - URL: SandboxURL, - TimeOut: time.Second * 5, - SandboxURL: SandboxURL, - } - if os.Getenv("IAP_ENVIRONMENT") == "production" { - client.URL = ProductionURL + ProductionURL: ProductionURL, + SandboxURL: SandboxURL, + TimeOut: time.Second * 5, } return client } @@ -97,11 +92,9 @@ func NewWithConfig(config Config) Client { } client := Client{ - URL: SandboxURL, - TimeOut: config.TimeOut, - } - if config.IsProduction { - client.URL = ProductionURL + ProductionURL: ProductionURL, + SandboxURL: SandboxURL, + TimeOut: config.TimeOut, } return client @@ -116,7 +109,7 @@ func (c *Client) Verify(req IAPRequest, result interface{}) error { b := new(bytes.Buffer) json.NewEncoder(b).Encode(req) - resp, err := client.Post(c.URL, "application/json; charset=utf-8", b) + resp, err := client.Post(c.ProductionURL, "application/json; charset=utf-8", b) if err != nil { return err } @@ -127,11 +120,8 @@ func (c *Client) Verify(req IAPRequest, result interface{}) error { return err } - // Always verify your receipt first with the production URL; proceed to verify with the sandbox URL if you receive - // a 21007 status code - // // https://developer.apple.com/library/content/technotes/tn2413/_index.html#//apple_ref/doc/uid/DTS40016228-CH1-RECEIPTURL - r, ok := result.(*IAPResponse) + r, ok := result.(*HttpStatusResponse) if ok && r.Status == 21007 { b = new(bytes.Buffer) json.NewEncoder(b).Encode(req) diff --git a/appstore/validator_test.go b/appstore/validator_test.go index f6a338a..2b2f831 100644 --- a/appstore/validator_test.go +++ b/appstore/validator_test.go @@ -2,12 +2,12 @@ package appstore import ( "errors" + "net/http" + "net/http/httptest" "os" "reflect" "testing" "time" - "net/http/httptest" - "net/http" ) func TestHandleError(t *testing.T) { @@ -93,9 +93,9 @@ func TestHandleError(t *testing.T) { func TestNew(t *testing.T) { expected := Client{ - URL: SandboxURL, - TimeOut: time.Second * 5, - SandboxURL:SandboxURL, + ProductionURL: SandboxURL, + TimeOut: time.Second * 5, + SandboxURL: SandboxURL, } actual := New() @@ -106,9 +106,9 @@ func TestNew(t *testing.T) { func TestNewWithEnvironment(t *testing.T) { expected := Client{ - URL: ProductionURL, - TimeOut: time.Second * 5, - SandboxURL:SandboxURL, + ProductionURL: ProductionURL, + TimeOut: time.Second * 5, + SandboxURL: SandboxURL, } os.Setenv("IAP_ENVIRONMENT", "production") @@ -122,13 +122,12 @@ func TestNewWithEnvironment(t *testing.T) { func TestNewWithConfig(t *testing.T) { config := Config{ - IsProduction: true, - TimeOut: time.Second * 2, + TimeOut: time.Second * 2, } expected := Client{ - URL: ProductionURL, - TimeOut: time.Second * 2, + ProductionURL: ProductionURL, + TimeOut: time.Second * 2, } actual := NewWithConfig(config) @@ -138,13 +137,11 @@ func TestNewWithConfig(t *testing.T) { } func TestNewWithConfigTimeout(t *testing.T) { - config := Config{ - IsProduction: true, - } + config := Config{} expected := Client{ - URL: ProductionURL, - TimeOut: time.Second * 5, + ProductionURL: ProductionURL, + TimeOut: time.Second * 5, } actual := NewWithConfig(config) @@ -169,7 +166,7 @@ func TestVerifyTimeout(t *testing.T) { func TestVerifyBadURL(t *testing.T) { client := New() - client.URL = "127.0.0.1" + client.ProductionURL = "127.0.0.1" req := IAPRequest{ ReceiptData: "dummy data", @@ -186,7 +183,7 @@ func TestVerifyBadPayload(t *testing.T) { defer s.Close() client := New() - client.URL = s.URL + client.ProductionURL = s.URL expected := &IAPResponse{ Status: 21002, } @@ -209,7 +206,7 @@ func TestVerifyBadResponse(t *testing.T) { defer s.Close() client := New() - client.URL = s.URL + client.ProductionURL = s.URL req := IAPRequest{ ReceiptData: "dummy data", } @@ -229,7 +226,7 @@ func TestVerifySandboxReceipt(t *testing.T) { defer sandboxServ.Close() client := New() - client.URL = s.URL + client.ProductionURL = s.URL client.TimeOut = time.Second * 100 client.SandboxURL = sandboxServ.URL @@ -255,7 +252,7 @@ func TestVerifySandboxReceiptFailure(t *testing.T) { defer s.Close() client := New() - client.URL = s.URL + client.ProductionURL = s.URL client.TimeOut = time.Second * 100 client.SandboxURL = "localhost"