forked from Mirrors/go-iap
Have app store hit prod and then sandbox if 21007
According to https://developer.apple.com/library/content/technotes/tn2413/_index.html#//apple_ref/doc/uid/DTS40016228-CH1-RECEIPTURL Always verify your receipt first with the production URL; proceed to verify with the sandbox URL if you receive a 21007 status code. Following this approach ensures that you do not have to switch between URLs while your application is being tested or reviewed in the sandbox or is live in the App Store.
This commit is contained in:
@@ -31,6 +31,7 @@ type IAPClient interface {
|
||||
type Client struct {
|
||||
URL string
|
||||
TimeOut time.Duration
|
||||
SandboxURL string
|
||||
}
|
||||
|
||||
// HandleError returns error message by status code
|
||||
@@ -81,6 +82,7 @@ func New() Client {
|
||||
client := Client{
|
||||
URL: SandboxURL,
|
||||
TimeOut: time.Second * 5,
|
||||
SandboxURL: SandboxURL,
|
||||
}
|
||||
if os.Getenv("IAP_ENVIRONMENT") == "production" {
|
||||
client.URL = ProductionURL
|
||||
@@ -121,6 +123,26 @@ func (c *Client) Verify(req IAPRequest, result interface{}) error {
|
||||
defer resp.Body.Close()
|
||||
|
||||
err = json.NewDecoder(resp.Body).Decode(result)
|
||||
|
||||
if err != nil {
|
||||
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)
|
||||
if ok && r.Status == 21007 {
|
||||
b = new(bytes.Buffer)
|
||||
json.NewEncoder(b).Encode(req)
|
||||
resp, err := client.Post(c.SandboxURL, "application/json; charset=utf-8", b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return json.NewDecoder(resp.Body).Decode(result)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
"net/http/httptest"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func TestHandleError(t *testing.T) {
|
||||
@@ -91,8 +93,9 @@ func TestHandleError(t *testing.T) {
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
expected := Client{
|
||||
URL: "https://sandbox.itunes.apple.com/verifyReceipt",
|
||||
URL: SandboxURL,
|
||||
TimeOut: time.Second * 5,
|
||||
SandboxURL:SandboxURL,
|
||||
}
|
||||
|
||||
actual := New()
|
||||
@@ -103,8 +106,9 @@ func TestNew(t *testing.T) {
|
||||
|
||||
func TestNewWithEnvironment(t *testing.T) {
|
||||
expected := Client{
|
||||
URL: "https://buy.itunes.apple.com/verifyReceipt",
|
||||
URL: ProductionURL,
|
||||
TimeOut: time.Second * 5,
|
||||
SandboxURL:SandboxURL,
|
||||
}
|
||||
|
||||
os.Setenv("IAP_ENVIRONMENT", "production")
|
||||
@@ -123,7 +127,7 @@ func TestNewWithConfig(t *testing.T) {
|
||||
}
|
||||
|
||||
expected := Client{
|
||||
URL: "https://buy.itunes.apple.com/verifyReceipt",
|
||||
URL: ProductionURL,
|
||||
TimeOut: time.Second * 2,
|
||||
}
|
||||
|
||||
@@ -139,7 +143,7 @@ func TestNewWithConfigTimeout(t *testing.T) {
|
||||
}
|
||||
|
||||
expected := Client{
|
||||
URL: "https://buy.itunes.apple.com/verifyReceipt",
|
||||
URL: ProductionURL,
|
||||
TimeOut: time.Second * 5,
|
||||
}
|
||||
|
||||
@@ -149,9 +153,9 @@ func TestNewWithConfigTimeout(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerify(t *testing.T) {
|
||||
func TestVerifyTimeout(t *testing.T) {
|
||||
client := New()
|
||||
client.TimeOut = time.Millisecond * 100
|
||||
client.TimeOut = time.Millisecond
|
||||
|
||||
req := IAPRequest{
|
||||
ReceiptData: "dummy data",
|
||||
@@ -161,13 +165,172 @@ func TestVerify(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Errorf("error should be occurred because of timeout")
|
||||
}
|
||||
}
|
||||
|
||||
client = New()
|
||||
func TestVerifyBadURL(t *testing.T) {
|
||||
client := New()
|
||||
client.URL = "127.0.0.1"
|
||||
|
||||
req := IAPRequest{
|
||||
ReceiptData: "dummy data",
|
||||
}
|
||||
result := &IAPResponse{}
|
||||
err := client.Verify(req, result)
|
||||
if err == nil {
|
||||
t.Errorf("error should be occurred because the server is not real")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyBadPayload(t *testing.T) {
|
||||
s := httptest.NewServer(badPayload())
|
||||
defer s.Close()
|
||||
|
||||
client := New()
|
||||
client.URL = s.URL
|
||||
expected := &IAPResponse{
|
||||
Status: 21002,
|
||||
}
|
||||
client.Verify(req, result)
|
||||
req := IAPRequest{
|
||||
ReceiptData: "dummy data",
|
||||
}
|
||||
result := &IAPResponse{}
|
||||
|
||||
err := client.Verify(req, result)
|
||||
if err != nil {
|
||||
t.Errorf("got error %s", err)
|
||||
}
|
||||
if !reflect.DeepEqual(result, expected) {
|
||||
t.Errorf("got %v\nwant %v", result, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyBadResponse(t *testing.T) {
|
||||
s := httptest.NewServer(invalidResponse())
|
||||
defer s.Close()
|
||||
|
||||
client := New()
|
||||
client.URL = s.URL
|
||||
req := IAPRequest{
|
||||
ReceiptData: "dummy data",
|
||||
}
|
||||
result := &IAPResponse{}
|
||||
|
||||
err := client.Verify(req, result)
|
||||
if err == nil {
|
||||
t.Errorf("expected an error because Verify could not unmarshal server response")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifySandboxReceipt(t *testing.T) {
|
||||
s := httptest.NewServer(redirectToSandbox())
|
||||
defer s.Close()
|
||||
|
||||
sandboxServ := httptest.NewServer(sandboxSuccess())
|
||||
defer sandboxServ.Close()
|
||||
|
||||
client := New()
|
||||
client.URL = s.URL
|
||||
client.TimeOut = time.Second * 100
|
||||
client.SandboxURL = sandboxServ.URL
|
||||
|
||||
expected := &IAPResponse{
|
||||
Status: 0,
|
||||
}
|
||||
req := IAPRequest{
|
||||
ReceiptData: "dummy data",
|
||||
}
|
||||
result := &IAPResponse{}
|
||||
|
||||
err := client.Verify(req, result)
|
||||
if err != nil {
|
||||
t.Errorf("got error %s", err)
|
||||
}
|
||||
if !reflect.DeepEqual(result, expected) {
|
||||
t.Errorf("got %v\nwant %v", result, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifySandboxReceiptFailure(t *testing.T) {
|
||||
s := httptest.NewServer(redirectToSandbox())
|
||||
defer s.Close()
|
||||
|
||||
sandboxServ := httptest.NewServer(sandboxTimeout())
|
||||
defer sandboxServ.Close()
|
||||
|
||||
client := New()
|
||||
client.URL = s.URL
|
||||
client.TimeOut = time.Second * 100
|
||||
client.SandboxURL = sandboxServ.URL
|
||||
|
||||
req := IAPRequest{
|
||||
ReceiptData: "dummy data",
|
||||
}
|
||||
result := &IAPResponse{}
|
||||
|
||||
err := client.Verify(req, result)
|
||||
if err == nil {
|
||||
t.Errorf("expected error to be not nil since the sandbox is not responding")
|
||||
}
|
||||
}
|
||||
|
||||
func badPayload() http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if "POST" == r.Method {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(`{"status": 21002}`))
|
||||
return
|
||||
} else {
|
||||
w.Write([]byte(`unsupported request`))
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
})
|
||||
}
|
||||
|
||||
func redirectToSandbox() http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if "POST" == r.Method {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(`{"status": 21007}`))
|
||||
return
|
||||
} else {
|
||||
w.Write([]byte(`unsupported request`))
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
}
|
||||
|
||||
func sandboxSuccess() http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if "POST" == r.Method {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(`{"status": 0}`))
|
||||
return
|
||||
} else {
|
||||
w.Write([]byte(`unsupported request`))
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
}
|
||||
|
||||
func sandboxTimeout() http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Do nothing and just dont return anything either
|
||||
})
|
||||
}
|
||||
|
||||
func invalidResponse() http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if "POST" == r.Method {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(`qwerty!@#$%^`))
|
||||
return
|
||||
} else {
|
||||
w.Write([]byte(`unsupported request`))
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user