From 99e9a4e75919669b967c683d4d9315720aa93968 Mon Sep 17 00:00:00 2001 From: tsuji_jumpei Date: Thu, 4 Dec 2014 22:35:01 +0900 Subject: [PATCH] Added the subscription validator for in app billing --- .travis.yml | 3 +- Makefile | 3 + appstore/validator.go | 8 +-- appstore/validator_test.go | 4 +- playstore/validator.go | 127 ++++++++++++++++++++++++++++++++++++ playstore/validator_test.go | 107 ++++++++++++++++++++++++++++++ 6 files changed, 244 insertions(+), 8 deletions(-) create mode 100644 playstore/validator.go create mode 100644 playstore/validator_test.go diff --git a/.travis.yml b/.travis.yml index 5339cb9..dc16968 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,8 +4,7 @@ go: before_install: sudo pip install codecov install: -- go get code.google.com/p/go.tools/cmd/cover -- go get ./... +- make setup script: - make cover after_success: diff --git a/Makefile b/Makefile index 5d9b45c..673751c 100644 --- a/Makefile +++ b/Makefile @@ -12,4 +12,7 @@ test: cover: go test -v -coverprofile=coverage.txt -covermode=count ./appstore + go test -v -coverprofile=playstore.txt -covermode=count ./playstore + cat playstore.txt | grep -v "mode: count" >> coverage.txt + rm playstore.txt diff --git a/appstore/validator.go b/appstore/validator.go index e9c26af..dd1fb90 100644 --- a/appstore/validator.go +++ b/appstore/validator.go @@ -68,8 +68,8 @@ func HandleError(status int) error { } // New creates a client object -func New() *Client { - client := &Client{ +func New() Client { + client := Client{ URL: sandboxURL, TimeOut: time.Second * 5, } @@ -80,12 +80,12 @@ func New() *Client { } // NewWithConfig creates a client with configuration -func NewWithConfig(config Config) *Client { +func NewWithConfig(config Config) Client { if config.TimeOut == 0 { config.TimeOut = time.Second * 5 } - client := &Client{ + client := Client{ URL: sandboxURL, TimeOut: config.TimeOut, } diff --git a/appstore/validator_test.go b/appstore/validator_test.go index db67415..1af6d9a 100644 --- a/appstore/validator_test.go +++ b/appstore/validator_test.go @@ -68,7 +68,7 @@ func TestHandleError(t *testing.T) { } func TestNew(t *testing.T) { - expected := &Client{ + expected := Client{ URL: "https://sandbox.itunes.apple.com/verifyReceipt", TimeOut: time.Second * 5, } @@ -85,7 +85,7 @@ func TestNewWithConfig(t *testing.T) { TimeOut: time.Second * 2, } - expected := &Client{ + expected := Client{ URL: "https://buy.itunes.apple.com/verifyReceipt", TimeOut: time.Second * 2, } diff --git a/playstore/validator.go b/playstore/validator.go new file mode 100644 index 0000000..97164ea --- /dev/null +++ b/playstore/validator.go @@ -0,0 +1,127 @@ +package playstore + +import ( + "errors" + "net" + "net/http" + "os" + "time" + + "code.google.com/p/goauth2/oauth" + "code.google.com/p/google-api-go-client/androidpublisher/v2" +) + +const ( + scope = "https://www.googleapis.com/auth/androidpublisher" + authURL = "https://accounts.google.com/o/oauth2/auth" + tokenURL = "https://accounts.google.com/o/oauth2/token" + + timeout = time.Second * 5 +) + +var defaultConfig *oauth.Config +var defaultTimeout = timeout + +// Init initializes the global configuration +func Init() error { + defaultConfig = &oauth.Config{ + Scope: scope, + AuthURL: authURL, + TokenURL: tokenURL, + } + + clientID := os.Getenv("IAB_CLIENT_ID") + if clientID != "" { + defaultConfig.ClientId = clientID + } + if defaultConfig.ClientId == "" { + return errors.New("Client ID is required") + } + + clientSecret := os.Getenv("IAB_CLIENT_SECRET") + if clientSecret != "" { + defaultConfig.ClientSecret = clientSecret + } + if defaultConfig.ClientSecret == "" { + return errors.New("Client Secret Key is required") + } + + return nil +} + +// InitWithConfig initializes the global configuration with parameters +func InitWithConfig(config *oauth.Config) error { + if config.ClientId == "" { + return errors.New("Client ID is required") + } + + if config.ClientSecret == "" { + return errors.New("Client Secret Key is required") + } + + if config.Scope == "" { + config.Scope = scope + } + + if config.AuthURL == "" { + config.AuthURL = authURL + } + + if config.TokenURL == "" { + config.TokenURL = tokenURL + } + + defaultConfig = config + + return nil +} + +// SetTimeout sets dial timeout duration +func SetTimeout(t time.Duration) { + defaultTimeout = t +} + +// The IABClient type is an interface to verify purchase token +type IABClient interface { + VerifySubscription(string, string, string) (*androidpublisher.SubscriptionPurchase, error) +} + +// The Client type implements VerifySubscription method +type Client struct { + httpClient *http.Client +} + +// New returns http client which has oauth token +func New(token *oauth.Token) Client { + t := &oauth.Transport{ + Token: token, + Config: defaultConfig, + Transport: &http.Transport{ + Dial: dialTimeout, + }, + } + + httpClient := t.Client() + return Client{httpClient} +} + +// VerifySubscription Verifies subscription status +func (c *Client) VerifySubscription( + packageName string, + subscriptionID string, + token string, +) (*androidpublisher.SubscriptionPurchase, error) { + service, err := androidpublisher.New(c.httpClient) + if err != nil { + return nil, err + } + + ps := androidpublisher.NewPurchasesSubscriptionsService(service) + result, err := ps.Get(packageName, subscriptionID, token).Do() + + return result, err +} + +func dialTimeout(network, addr string) (net.Conn, error) { + return net.DialTimeout(network, addr, defaultTimeout) +} diff --git a/playstore/validator_test.go b/playstore/validator_test.go new file mode 100644 index 0000000..343d356 --- /dev/null +++ b/playstore/validator_test.go @@ -0,0 +1,107 @@ +package playstore + +import ( + "os" + "reflect" + "testing" + "time" + + "code.google.com/p/goauth2/oauth" +) + +func TestInit(t *testing.T) { + expected := &oauth.Config{ + ClientId: "dummyId", + ClientSecret: "dummySecret", + Scope: "https://www.googleapis.com/auth/androidpublisher", + AuthURL: "https://accounts.google.com/o/oauth2/auth", + TokenURL: "https://accounts.google.com/o/oauth2/token", + } + os.Setenv("IAB_CLIENT_ID", "dummyId") + os.Setenv("IAB_CLIENT_SECRET", "dummySecret") + Init() + os.Clearenv() + actual := defaultConfig + if !reflect.DeepEqual(actual, expected) { + t.Errorf("got %v\nwant %v", actual, expected) + } +} + +func TestInitWithConfig(t *testing.T) { + expected := &oauth.Config{ + ClientId: "dummyId", + ClientSecret: "dummySecret", + Scope: "https://www.googleapis.com/auth/androidpublisher", + AuthURL: "https://accounts.google.com/o/oauth2/auth", + TokenURL: "https://accounts.google.com/o/oauth2/token", + } + + config := &oauth.Config{ + ClientId: "dummyId", + ClientSecret: "dummySecret", + Scope: "https://www.googleapis.com/auth/androidpublisher", + AuthURL: "https://accounts.google.com/o/oauth2/auth", + TokenURL: "https://accounts.google.com/o/oauth2/token", + } + InitWithConfig(config) + actual := defaultConfig + if !reflect.DeepEqual(actual, expected) { + t.Errorf("got %v\nwant %v", actual, expected) + } +} + +func TestNew(t *testing.T) { + // Initialize config + _config := &oauth.Config{ + ClientId: "dummyId", + ClientSecret: "dummySecret", + } + InitWithConfig(_config) + + token := &oauth.Token{ + AccessToken: "accessToken", + RefreshToken: "refreshToken", + Expiry: time.Unix(1234567890, 0).UTC(), + } + + actual := New(token) + val, _ := actual.httpClient.Transport.(*oauth.Transport) + + if !reflect.DeepEqual(val.Config, _config) { + t.Errorf("got %v\nwant %v", val.Config, _config) + } + + if !reflect.DeepEqual(val.Token, token) { + t.Errorf("got %v\nwant %v", val.Token, token) + } +} + +func TestSetTimeout(t *testing.T) { + timeout := time.Second * 3 + SetTimeout(timeout) + + if defaultTimeout != timeout { + t.Errorf("got %#v\nwant %#v", defaultTimeout, timeout) + } +} + +func TestVerifySubscription(t *testing.T) { + Init() + + // Exception scenario + token := &oauth.Token{ + AccessToken: "accessToken", + RefreshToken: "refreshToken", + Expiry: time.Unix(1234567890, 0).UTC(), + } + + client := New(token) + expected := "Get https://www.googleapis.com/androidpublisher/v2/applications/package/purchases/subscriptions/subscriptionID/tokens/purchaseToken?alt=json: OAuthError: updateToken: Unexpected HTTP status 400 Bad Request" + _, err := client.VerifySubscription("package", "subscriptionID", "purchaseToken") + + if err.Error() != expected { + t.Errorf("got %v", err) + } + + // TODO Nomal scenario +}