forked from Mirrors/go-iap
Merge pull request #65 from jun06t/amazon
Support custom client for Amazon
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
language: go
|
language: go
|
||||||
go:
|
go:
|
||||||
- 1.9
|
- 1.10.2
|
||||||
before_install:
|
before_install:
|
||||||
- sudo pip install codecov
|
- sudo pip install codecov
|
||||||
install:
|
install:
|
||||||
|
|||||||
8
Makefile
8
Makefile
@@ -12,10 +12,4 @@ test:
|
|||||||
go test -v ./...
|
go test -v ./...
|
||||||
|
|
||||||
cover:
|
cover:
|
||||||
go test -v -coverprofile=coverage.txt -covermode=count ./appstore
|
go test -coverprofile=coverage.txt ./...
|
||||||
go test -v -coverprofile=playstore.txt -covermode=count ./playstore
|
|
||||||
cat playstore.txt | grep -v "mode: count" >> coverage.txt
|
|
||||||
rm playstore.txt
|
|
||||||
go test -v -coverprofile=amazon.txt -covermode=count ./amazon
|
|
||||||
cat amazon.txt | grep -v "mode: count" >> coverage.txt
|
|
||||||
rm amazon.txt
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
go-iap
|
go-iap
|
||||||
======
|
======
|
||||||
|
|
||||||

|

|
||||||
[](https://travis-ci.org/awa/go-iap)
|
[](https://travis-ci.org/awa/go-iap)
|
||||||
[](https://codecov.io/github/awa/go-iap?branch=master)
|
[](https://codecov.io/github/awa/go-iap?branch=master)
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
package amazon
|
package amazon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -24,13 +24,6 @@ func getSandboxURL() string {
|
|||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config is a configuration to initialize client
|
|
||||||
type Config struct {
|
|
||||||
IsProduction bool
|
|
||||||
Secret string
|
|
||||||
TimeOut time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
// The IAPResponse type has the response properties
|
// The IAPResponse type has the response properties
|
||||||
type IAPResponse struct {
|
type IAPResponse struct {
|
||||||
ReceiptID string `json:"receiptId"`
|
ReceiptID string `json:"receiptId"`
|
||||||
@@ -49,41 +42,38 @@ type IAPResponseError struct {
|
|||||||
|
|
||||||
// IAPClient is an interface to call validation API in Amazon App Store
|
// IAPClient is an interface to call validation API in Amazon App Store
|
||||||
type IAPClient interface {
|
type IAPClient interface {
|
||||||
Verify(string, string) (IAPResponse, error)
|
Verify(context.Context, string, string) (IAPResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client implements IAPClient
|
// Client implements IAPClient
|
||||||
type Client struct {
|
type Client struct {
|
||||||
URL string
|
URL string
|
||||||
Secret string
|
Secret string
|
||||||
TimeOut time.Duration
|
httpCli *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a client object
|
// New creates a client object
|
||||||
func New(secret string) IAPClient {
|
func New(secret string) *Client {
|
||||||
client := Client{
|
client := &Client{
|
||||||
URL: getSandboxURL(),
|
URL: getSandboxURL(),
|
||||||
Secret: secret,
|
Secret: secret,
|
||||||
TimeOut: time.Second * 5,
|
httpCli: http.DefaultClient,
|
||||||
}
|
}
|
||||||
if os.Getenv("IAP_ENVIRONMENT") == "production" {
|
if os.Getenv("IAP_ENVIRONMENT") == "production" {
|
||||||
client.URL = ProductionURL
|
client.URL = ProductionURL
|
||||||
}
|
}
|
||||||
|
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewWithConfig creates a client with configuration
|
// NewWithClient creates a client with a custom client.
|
||||||
func NewWithConfig(config Config) Client {
|
func NewWithClient(secret string, cli *http.Client) *Client {
|
||||||
if config.TimeOut == 0 {
|
client := &Client{
|
||||||
config.TimeOut = time.Second * 5
|
|
||||||
}
|
|
||||||
|
|
||||||
client := Client{
|
|
||||||
URL: getSandboxURL(),
|
URL: getSandboxURL(),
|
||||||
Secret: config.Secret,
|
Secret: secret,
|
||||||
TimeOut: config.TimeOut,
|
httpCli: cli,
|
||||||
}
|
}
|
||||||
if config.IsProduction {
|
if os.Getenv("IAP_ENVIRONMENT") == "production" {
|
||||||
client.URL = ProductionURL
|
client.URL = ProductionURL
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,13 +81,16 @@ func NewWithConfig(config Config) Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify sends receipts and gets validation result
|
// Verify sends receipts and gets validation result
|
||||||
func (c Client) Verify(userID string, receiptID string) (IAPResponse, error) {
|
func (c *Client) Verify(ctx context.Context, userID string, receiptID string) (IAPResponse, error) {
|
||||||
result := IAPResponse{}
|
result := IAPResponse{}
|
||||||
url := fmt.Sprintf("%v/version/1.0/verifyReceiptId/developer/%v/user/%v/receiptId/%v", c.URL, c.Secret, userID, receiptID)
|
url := fmt.Sprintf("%v/version/1.0/verifyReceiptId/developer/%v/user/%v/receiptId/%v", c.URL, c.Secret, userID, receiptID)
|
||||||
client := http.Client{
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
Timeout: c.TimeOut,
|
if err != nil {
|
||||||
|
return result, err
|
||||||
}
|
}
|
||||||
resp, err := client.Get(url)
|
req = req.WithContext(ctx)
|
||||||
|
|
||||||
|
resp, err := c.httpCli.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return result, fmt.Errorf("%v", err)
|
return result, fmt.Errorf("%v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package amazon
|
package amazon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -25,6 +26,7 @@ func TestHandle497Error(t *testing.T) {
|
|||||||
// status 400
|
// status 400
|
||||||
expected = errors.New("Purchase token/app user mismatch")
|
expected = errors.New("Purchase token/app user mismatch")
|
||||||
_, actual = client.Verify(
|
_, actual = client.Verify(
|
||||||
|
context.Background(),
|
||||||
"99FD_DL23EMhrOGDnur9-ulvqomrSg6qyLPSD3CFE=",
|
"99FD_DL23EMhrOGDnur9-ulvqomrSg6qyLPSD3CFE=",
|
||||||
"q1YqVrJSSs7P1UvMTazKz9PLTCwoTswtyEktM9JLrShIzCvOzM-LL04tiTdW0lFKASo2NDEwMjCwMDM2MTC0AIqVAsUsLd1c4l18jIxdfTOK_N1d8kqLLHVLc8oK83OLgtPNCit9AoJdjJ3dXG2BGkqUrAxrAQ",
|
"q1YqVrJSSs7P1UvMTazKz9PLTCwoTswtyEktM9JLrShIzCvOzM-LL04tiTdW0lFKASo2NDEwMjCwMDM2MTC0AIqVAsUsLd1c4l18jIxdfTOK_N1d8kqLLHVLc8oK83OLgtPNCit9AoJdjJ3dXG2BGkqUrAxrAQ",
|
||||||
)
|
)
|
||||||
@@ -47,6 +49,7 @@ func TestHandle400Error(t *testing.T) {
|
|||||||
// status 400
|
// status 400
|
||||||
expected = errors.New("Failed to parse receipt Id")
|
expected = errors.New("Failed to parse receipt Id")
|
||||||
_, actual = client.Verify(
|
_, actual = client.Verify(
|
||||||
|
context.Background(),
|
||||||
"99FD_DL23EMhrOGDnur9-ulvqomrSg6qyLPSD3CFE=",
|
"99FD_DL23EMhrOGDnur9-ulvqomrSg6qyLPSD3CFE=",
|
||||||
"q1YqVrJSSs7P1UvMTazKz9PLTCwoTswtyEktM9JLrShIzCvOzM-LL04tiTdW0lFKASo2NDEwMjCwMDM2MTC0AIqVAsUsLd1c4l18jIxdfTOK_N1d8kqLLHVLc8oK83OLgtPNCit9AoJdjJ3dXG2BGkqUrAxrAQ",
|
"q1YqVrJSSs7P1UvMTazKz9PLTCwoTswtyEktM9JLrShIzCvOzM-LL04tiTdW0lFKASo2NDEwMjCwMDM2MTC0AIqVAsUsLd1c4l18jIxdfTOK_N1d8kqLLHVLc8oK83OLgtPNCit9AoJdjJ3dXG2BGkqUrAxrAQ",
|
||||||
)
|
)
|
||||||
@@ -56,11 +59,10 @@ func TestHandle400Error(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestNew(t *testing.T) {
|
func TestNew(t *testing.T) {
|
||||||
t.Parallel()
|
expected := &Client{
|
||||||
expected := Client{
|
|
||||||
URL: SandboxURL,
|
URL: SandboxURL,
|
||||||
TimeOut: time.Second * 5,
|
|
||||||
Secret: "developerSecret",
|
Secret: "developerSecret",
|
||||||
|
httpCli: http.DefaultClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
actual := New("developerSecret")
|
actual := New("developerSecret")
|
||||||
@@ -70,11 +72,10 @@ func TestNew(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestNewWithEnvironment(t *testing.T) {
|
func TestNewWithEnvironment(t *testing.T) {
|
||||||
t.Parallel()
|
expected := &Client{
|
||||||
expected := Client{
|
|
||||||
URL: ProductionURL,
|
URL: ProductionURL,
|
||||||
TimeOut: time.Second * 5,
|
|
||||||
Secret: "developerSecret",
|
Secret: "developerSecret",
|
||||||
|
httpCli: http.DefaultClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
os.Setenv("IAP_ENVIRONMENT", "production")
|
os.Setenv("IAP_ENVIRONMENT", "production")
|
||||||
@@ -86,40 +87,20 @@ func TestNewWithEnvironment(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewWithConfig(t *testing.T) {
|
func TestNewWithClient(t *testing.T) {
|
||||||
t.Parallel()
|
expected := &Client{
|
||||||
config := Config{
|
URL: ProductionURL,
|
||||||
IsProduction: true,
|
Secret: "developerSecret",
|
||||||
Secret: "developerSecret",
|
httpCli: &http.Client{
|
||||||
TimeOut: time.Second * 2,
|
Timeout: time.Second * 2,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
os.Setenv("IAP_ENVIRONMENT", "production")
|
||||||
|
|
||||||
expected := Client{
|
cli := &http.Client{
|
||||||
URL: ProductionURL,
|
Timeout: time.Second * 2,
|
||||||
TimeOut: time.Second * 2,
|
|
||||||
Secret: "developerSecret",
|
|
||||||
}
|
}
|
||||||
|
actual := NewWithClient("developerSecret", cli)
|
||||||
actual := NewWithConfig(config)
|
|
||||||
if !reflect.DeepEqual(actual, expected) {
|
|
||||||
t.Errorf("got %v\nwant %v", actual, expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewWithConfigTimeout(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
config := Config{
|
|
||||||
IsProduction: true,
|
|
||||||
Secret: "developerSecret",
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := Client{
|
|
||||||
URL: ProductionURL,
|
|
||||||
TimeOut: time.Second * 5,
|
|
||||||
Secret: "developerSecret",
|
|
||||||
}
|
|
||||||
|
|
||||||
actual := NewWithConfig(config)
|
|
||||||
if !reflect.DeepEqual(actual, expected) {
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
t.Errorf("got %v\nwant %v", actual, expected)
|
t.Errorf("got %v\nwant %v", actual, expected)
|
||||||
}
|
}
|
||||||
@@ -143,6 +124,7 @@ func TestVerify(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
actual, _ := client.Verify(
|
actual, _ := client.Verify(
|
||||||
|
context.Background(),
|
||||||
"99FD_DL23EMhrOGDnur9-ulvqomrSg6qyLPSD3CFE=",
|
"99FD_DL23EMhrOGDnur9-ulvqomrSg6qyLPSD3CFE=",
|
||||||
"q1YqVrJSSs7P1UvMTazKz9PLTCwoTswtyEktM9JLrShIzCvOzM-LL04tiTdW0lFKASo2NDEwMjCwMDM2MTC0AIqVAsUsLd1c4l18jIxdfTOK_N1d8kqLLHVLc8oK83OLgtPNCit9AoJdjJ3dXG2BGkqUrAxrAQ",
|
"q1YqVrJSSs7P1UvMTazKz9PLTCwoTswtyEktM9JLrShIzCvOzM-LL04tiTdW0lFKASo2NDEwMjCwMDM2MTC0AIqVAsUsLd1c4l18jIxdfTOK_N1d8kqLLHVLc8oK83OLgtPNCit9AoJdjJ3dXG2BGkqUrAxrAQ",
|
||||||
)
|
)
|
||||||
@@ -158,7 +140,8 @@ func TestVerifyTimeout(t *testing.T) {
|
|||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
expected := errors.New("")
|
expected := errors.New("")
|
||||||
_, actual := client.Verify("timeout", "timeout")
|
ctx := context.Background()
|
||||||
|
_, actual := client.Verify(ctx, "timeout", "timeout")
|
||||||
if !reflect.DeepEqual(reflect.TypeOf(actual), reflect.TypeOf(expected)) {
|
if !reflect.DeepEqual(reflect.TypeOf(actual), reflect.TypeOf(expected)) {
|
||||||
t.Errorf("got %v\nwant %v", actual, expected)
|
t.Errorf("got %v\nwant %v", actual, expected)
|
||||||
}
|
}
|
||||||
@@ -172,6 +155,6 @@ func testTools(code int, body string) (*httptest.Server, *Client) {
|
|||||||
fmt.Fprintln(w, body)
|
fmt.Fprintln(w, body)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
client := &Client{URL: server.URL, TimeOut: time.Second * 2, Secret: "developerSecret"}
|
client := &Client{URL: server.URL, Secret: "developerSecret", httpCli: &http.Client{Timeout: 2 * time.Second}}
|
||||||
return server, client
|
return server, client
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user