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"