diff --git a/appstore/mocks/mock_appstore.go b/appstore/mocks/mock_appstore.go new file mode 100644 index 0000000..f5a38eb --- /dev/null +++ b/appstore/mocks/mock_appstore.go @@ -0,0 +1,49 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/awa/go-iap/appstore (interfaces: IAPClient) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + appstore "github.com/awa/go-iap/appstore" + gomock "github.com/golang/mock/gomock" + reflect "reflect" +) + +// MockIAPClient is a mock of IAPClient interface +type MockIAPClient struct { + ctrl *gomock.Controller + recorder *MockIAPClientMockRecorder +} + +// MockIAPClientMockRecorder is the mock recorder for MockIAPClient +type MockIAPClientMockRecorder struct { + mock *MockIAPClient +} + +// NewMockIAPClient creates a new mock instance +func NewMockIAPClient(ctrl *gomock.Controller) *MockIAPClient { + mock := &MockIAPClient{ctrl: ctrl} + mock.recorder = &MockIAPClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockIAPClient) EXPECT() *MockIAPClientMockRecorder { + return m.recorder +} + +// Verify mocks base method +func (m *MockIAPClient) Verify(arg0 context.Context, arg1 appstore.IAPRequest, arg2 interface{}) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Verify", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// Verify indicates an expected call of Verify +func (mr *MockIAPClientMockRecorder) Verify(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Verify", reflect.TypeOf((*MockIAPClient)(nil).Verify), arg0, arg1, arg2) +} diff --git a/appstore/validator.go b/appstore/validator.go index 4786f01..301ce59 100644 --- a/appstore/validator.go +++ b/appstore/validator.go @@ -10,6 +10,8 @@ import ( "time" ) +//go:generate mockgen -destination=mocks/mock_appstore.go -package=mocks github.com/awa/go-iap/appstore IAPClient + const ( // SandboxURL is the endpoint for sandbox environment. SandboxURL string = "https://sandbox.itunes.apple.com/verifyReceipt" diff --git a/go.mod b/go.mod index 3ffbc37..a170cd5 100644 --- a/go.mod +++ b/go.mod @@ -4,12 +4,13 @@ go 1.12 require ( cloud.google.com/go v0.39.0 // indirect + github.com/golang/mock v1.4.0 go.opencensus.io v0.22.0 // indirect - golang.org/x/net v0.0.0-20190522155817-f3200d17e092 // indirect + golang.org/x/net v0.0.0-20190620200207-3b0461eec859 // indirect golang.org/x/oauth2 v0.0.0-20190523182746-aaccbc9213b0 golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1 // indirect google.golang.org/api v0.5.1-0.20190526001144-9f3a303b451f - google.golang.org/appengine v1.6.0 + google.golang.org/appengine v1.6.5 google.golang.org/genproto v0.0.0-20190530194941-fb225487d101 // indirect google.golang.org/grpc v1.21.0 // indirect ) diff --git a/go.sum b/go.sum index 19d2eed..04a3d3f 100644 --- a/go.sum +++ b/go.sum @@ -8,7 +8,10 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.4.0 h1:Rd1kQnQu0Hq3qvJppYSG0HtP+f5LPPUiDswTLiEegLg= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= @@ -42,8 +45,9 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190523182746-aaccbc9213b0 h1:xFEXbcD0oa/xhqQmMXztdZ0bWvexAWds+8c1gRN8nu0= @@ -52,11 +56,13 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1 h1:R4dVlxdmKenVdMRS/tTspEpSTRWINYrHD8ySIU9yCIU= golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -68,6 +74,7 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= google.golang.org/api v0.5.0 h1:lj9SyhMzyoa38fgFF0oO2T6pjs5IzkLPKfVtxpyCRMM= google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.5.1-0.20190526001144-9f3a303b451f h1:tAgkkP6ovjCY8HraRtpXwh0CVqHwGqEAVLHwXyfFjIM= @@ -75,8 +82,8 @@ google.golang.org/api v0.5.1-0.20190526001144-9f3a303b451f/go.mod h1:8k5glujaEP+ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.0 h1:Tfd7cKwKbFRsI8RMAD3oqqw7JPFRrvFlOsfbgVkjOOw= -google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19 h1:Lj2SnHtxkRGJDqnGaSjo+CCdIieEnwVazbOXILwQemk= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -91,3 +98,5 @@ google.golang.org/grpc v1.21.0 h1:G+97AoqBnmZIT91cLG/EkCoK9NSelj64P8bOHHNmGn0= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/playstore/mocks/mock_playstore.go b/playstore/mocks/mock_playstore.go new file mode 100644 index 0000000..b2e1bd6 --- /dev/null +++ b/playstore/mocks/mock_playstore.go @@ -0,0 +1,158 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/awa/go-iap/playstore (interfaces: IABProduct,IABSubscription) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + gomock "github.com/golang/mock/gomock" + v3 "google.golang.org/api/androidpublisher/v3" + reflect "reflect" +) + +// MockIABProduct is a mock of IABProduct interface +type MockIABProduct struct { + ctrl *gomock.Controller + recorder *MockIABProductMockRecorder +} + +// MockIABProductMockRecorder is the mock recorder for MockIABProduct +type MockIABProductMockRecorder struct { + mock *MockIABProduct +} + +// NewMockIABProduct creates a new mock instance +func NewMockIABProduct(ctrl *gomock.Controller) *MockIABProduct { + mock := &MockIABProduct{ctrl: ctrl} + mock.recorder = &MockIABProductMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockIABProduct) EXPECT() *MockIABProductMockRecorder { + return m.recorder +} + +// AcknowledgeProduct mocks base method +func (m *MockIABProduct) AcknowledgeProduct(arg0 context.Context, arg1, arg2, arg3, arg4 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AcknowledgeProduct", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(error) + return ret0 +} + +// AcknowledgeProduct indicates an expected call of AcknowledgeProduct +func (mr *MockIABProductMockRecorder) AcknowledgeProduct(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AcknowledgeProduct", reflect.TypeOf((*MockIABProduct)(nil).AcknowledgeProduct), arg0, arg1, arg2, arg3, arg4) +} + +// VerifyProduct mocks base method +func (m *MockIABProduct) VerifyProduct(arg0 context.Context, arg1, arg2, arg3 string) (*v3.ProductPurchase, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "VerifyProduct", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*v3.ProductPurchase) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// VerifyProduct indicates an expected call of VerifyProduct +func (mr *MockIABProductMockRecorder) VerifyProduct(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyProduct", reflect.TypeOf((*MockIABProduct)(nil).VerifyProduct), arg0, arg1, arg2, arg3) +} + +// MockIABSubscription is a mock of IABSubscription interface +type MockIABSubscription struct { + ctrl *gomock.Controller + recorder *MockIABSubscriptionMockRecorder +} + +// MockIABSubscriptionMockRecorder is the mock recorder for MockIABSubscription +type MockIABSubscriptionMockRecorder struct { + mock *MockIABSubscription +} + +// NewMockIABSubscription creates a new mock instance +func NewMockIABSubscription(ctrl *gomock.Controller) *MockIABSubscription { + mock := &MockIABSubscription{ctrl: ctrl} + mock.recorder = &MockIABSubscriptionMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockIABSubscription) EXPECT() *MockIABSubscriptionMockRecorder { + return m.recorder +} + +// AcknowledgeSubscription mocks base method +func (m *MockIABSubscription) AcknowledgeSubscription(arg0 context.Context, arg1, arg2, arg3 string, arg4 *v3.SubscriptionPurchasesAcknowledgeRequest) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AcknowledgeSubscription", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(error) + return ret0 +} + +// AcknowledgeSubscription indicates an expected call of AcknowledgeSubscription +func (mr *MockIABSubscriptionMockRecorder) AcknowledgeSubscription(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AcknowledgeSubscription", reflect.TypeOf((*MockIABSubscription)(nil).AcknowledgeSubscription), arg0, arg1, arg2, arg3, arg4) +} + +// CancelSubscription mocks base method +func (m *MockIABSubscription) CancelSubscription(arg0 context.Context, arg1, arg2, arg3 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CancelSubscription", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(error) + return ret0 +} + +// CancelSubscription indicates an expected call of CancelSubscription +func (mr *MockIABSubscriptionMockRecorder) CancelSubscription(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CancelSubscription", reflect.TypeOf((*MockIABSubscription)(nil).CancelSubscription), arg0, arg1, arg2, arg3) +} + +// RefundSubscription mocks base method +func (m *MockIABSubscription) RefundSubscription(arg0 context.Context, arg1, arg2, arg3 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RefundSubscription", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(error) + return ret0 +} + +// RefundSubscription indicates an expected call of RefundSubscription +func (mr *MockIABSubscriptionMockRecorder) RefundSubscription(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RefundSubscription", reflect.TypeOf((*MockIABSubscription)(nil).RefundSubscription), arg0, arg1, arg2, arg3) +} + +// RevokeSubscription mocks base method +func (m *MockIABSubscription) RevokeSubscription(arg0 context.Context, arg1, arg2, arg3 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RevokeSubscription", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(error) + return ret0 +} + +// RevokeSubscription indicates an expected call of RevokeSubscription +func (mr *MockIABSubscriptionMockRecorder) RevokeSubscription(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RevokeSubscription", reflect.TypeOf((*MockIABSubscription)(nil).RevokeSubscription), arg0, arg1, arg2, arg3) +} + +// VerifySubscription mocks base method +func (m *MockIABSubscription) VerifySubscription(arg0 context.Context, arg1, arg2, arg3 string) (*v3.SubscriptionPurchase, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "VerifySubscription", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*v3.SubscriptionPurchase) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// VerifySubscription indicates an expected call of VerifySubscription +func (mr *MockIABSubscriptionMockRecorder) VerifySubscription(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifySubscription", reflect.TypeOf((*MockIABSubscription)(nil).VerifySubscription), arg0, arg1, arg2, arg3) +} diff --git a/playstore/validator.go b/playstore/validator.go index cffbeb1..5a6e1ba 100644 --- a/playstore/validator.go +++ b/playstore/validator.go @@ -8,6 +8,7 @@ import ( "crypto/x509" "encoding/base64" "fmt" + "google.golang.org/api/option" "net/http" "time" @@ -16,9 +17,12 @@ import ( androidpublisher "google.golang.org/api/androidpublisher/v3" ) +//go:generate mockgen -destination=mocks/mock_playstore.go -package=mocks github.com/awa/go-iap/playstore IABProduct,IABSubscription + // The IABProduct type is an interface for product service type IABProduct interface { VerifyProduct(context.Context, string, string, string) (*androidpublisher.ProductPurchase, error) + AcknowledgeProduct(context.Context, string, string, string, string) error } // The IABSubscription type is an interface for subscription service @@ -32,22 +36,42 @@ type IABSubscription interface { // The Client type implements VerifySubscription method type Client struct { - httpCli *http.Client + service *androidpublisher.Service } // New returns http client which includes the credentials to access androidpublisher API. // You should create a service account for your project at // https://console.developers.google.com and download a JSON key file to set this argument. func New(jsonKey []byte) (*Client, error) { - ctx := context.WithValue(context.Background(), oauth2.HTTPClient, &http.Client{Timeout: 10 * time.Second}) + c := &http.Client{Timeout: 10 * time.Second} + ctx := context.WithValue(context.Background(), oauth2.HTTPClient, c) + conf, err := google.JWTConfigFromJSON(jsonKey, androidpublisher.AndroidpublisherScope) + if err != nil { + return nil, err + } - return &Client{conf.Client(ctx)}, err + val := conf.Client(ctx).Transport.(*oauth2.Transport) + _, err = val.Source.Token() + if err != nil { + return nil, err + } + + service, err := androidpublisher.NewService(ctx, option.WithHTTPClient(conf.Client(ctx))) + if err != nil { + return nil, err + } + + return &Client{service}, err } // NewWithClient returns http client which includes the custom http client. func NewWithClient(jsonKey []byte, cli *http.Client) (*Client, error) { + if cli == nil { + return nil, fmt.Errorf("client is nil") + } + ctx := context.WithValue(context.Background(), oauth2.HTTPClient, cli) conf, err := google.JWTConfigFromJSON(jsonKey, androidpublisher.AndroidpublisherScope) @@ -55,7 +79,12 @@ func NewWithClient(jsonKey []byte, cli *http.Client) (*Client, error) { return nil, err } - return &Client{conf.Client(ctx)}, err + service, err := androidpublisher.NewService(ctx, option.WithHTTPClient(conf.Client(ctx))) + if err != nil { + return nil, err + } + + return &Client{service}, err } // AcknowledgeSubscription acknowledges a subscription purchase. @@ -66,13 +95,8 @@ func (c *Client) AcknowledgeSubscription( token string, req *androidpublisher.SubscriptionPurchasesAcknowledgeRequest, ) error { - service, err := androidpublisher.New(c.httpCli) - if err != nil { - return err - } - - ps := androidpublisher.NewPurchasesSubscriptionsService(service) - err = ps.Acknowledge(packageName, subscriptionID, token, req).Context(ctx).Do() + ps := androidpublisher.NewPurchasesSubscriptionsService(c.service) + err := ps.Acknowledge(packageName, subscriptionID, token, req).Context(ctx).Do() return err } @@ -84,12 +108,7 @@ func (c *Client) VerifySubscription( subscriptionID string, token string, ) (*androidpublisher.SubscriptionPurchase, error) { - service, err := androidpublisher.New(c.httpCli) - if err != nil { - return nil, err - } - - ps := androidpublisher.NewPurchasesSubscriptionsService(service) + ps := androidpublisher.NewPurchasesSubscriptionsService(c.service) result, err := ps.Get(packageName, subscriptionID, token).Context(ctx).Do() return result, err @@ -102,26 +121,24 @@ func (c *Client) VerifyProduct( productID string, token string, ) (*androidpublisher.ProductPurchase, error) { - service, err := androidpublisher.New(c.httpCli) - if err != nil { - return nil, err - } - - ps := androidpublisher.NewPurchasesProductsService(service) + ps := androidpublisher.NewPurchasesProductsService(c.service) result, err := ps.Get(packageName, productID, token).Context(ctx).Do() return result, err } +func (c *Client) AcknowledgeProduct(ctx context.Context, packageName, productID, token, developerPayload string) error { + ps := androidpublisher.NewPurchasesProductsService(c.service) + acknowledgeRequest := &androidpublisher.ProductPurchasesAcknowledgeRequest{DeveloperPayload: developerPayload} + err := ps.Acknowledge(packageName, productID, token, acknowledgeRequest).Context(ctx).Do() + + return err +} + // CancelSubscription cancels a user's subscription purchase. func (c *Client) CancelSubscription(ctx context.Context, packageName string, subscriptionID string, token string) error { - service, err := androidpublisher.New(c.httpCli) - if err != nil { - return err - } - - ps := androidpublisher.NewPurchasesSubscriptionsService(service) - err = ps.Cancel(packageName, subscriptionID, token).Context(ctx).Do() + ps := androidpublisher.NewPurchasesSubscriptionsService(c.service) + err := ps.Cancel(packageName, subscriptionID, token).Context(ctx).Do() return err } @@ -129,13 +146,8 @@ func (c *Client) CancelSubscription(ctx context.Context, packageName string, sub // RefundSubscription refunds a user's subscription purchase, but the subscription remains valid // until its expiration time and it will continue to recur. func (c *Client) RefundSubscription(ctx context.Context, packageName string, subscriptionID string, token string) error { - service, err := androidpublisher.New(c.httpCli) - if err != nil { - return err - } - - ps := androidpublisher.NewPurchasesSubscriptionsService(service) - err = ps.Refund(packageName, subscriptionID, token).Context(ctx).Do() + ps := androidpublisher.NewPurchasesSubscriptionsService(c.service) + err := ps.Refund(packageName, subscriptionID, token).Context(ctx).Do() return err } @@ -143,13 +155,8 @@ func (c *Client) RefundSubscription(ctx context.Context, packageName string, sub // RevokeSubscription refunds and immediately revokes a user's subscription purchase. // Access to the subscription will be terminated immediately and it will stop recurring. func (c *Client) RevokeSubscription(ctx context.Context, packageName string, subscriptionID string, token string) error { - service, err := androidpublisher.New(c.httpCli) - if err != nil { - return err - } - - ps := androidpublisher.NewPurchasesSubscriptionsService(service) - err = ps.Revoke(packageName, subscriptionID, token).Context(ctx).Do() + ps := androidpublisher.NewPurchasesSubscriptionsService(c.service) + err := ps.Revoke(packageName, subscriptionID, token).Context(ctx).Do() return err } diff --git a/playstore/validator_test.go b/playstore/validator_test.go index 709a4b1..b40ba52 100644 --- a/playstore/validator_test.go +++ b/playstore/validator_test.go @@ -7,8 +7,7 @@ import ( "reflect" "testing" - "golang.org/x/oauth2" - androidpublisher "google.golang.org/api/androidpublisher/v3" + "google.golang.org/api/androidpublisher/v3" "google.golang.org/appengine/urlfetch" ) @@ -38,20 +37,17 @@ func TestNew(t *testing.T) { // Exception scenario expected := "oauth2: cannot fetch token: 400 Bad Request\nResponse: {\n \"error\": \"invalid_grant\",\n \"error_description\": \"Invalid issuer: Not a service account.\"\n}" - actual, _ := New(dummyKey) - val := actual.httpCli.Transport.(*oauth2.Transport) - token, err := val.Source.Token() - if token != nil { - t.Errorf("got %#v", token) - } - if err.Error() != expected { + _, err := New(dummyKey) + if err == nil || err.Error() != expected { t.Errorf("got %v\nwant %v", err, expected) } - // TODO Normal scenario - actual, _ = New(jsonKey) - val = actual.httpCli.Transport.(*oauth2.Transport) - token, err = val.Source.Token() + _, actual := New(nil) + if actual == nil || actual.Error() != "unexpected end of JSON input" { + t.Errorf("got %v\nwant %v", actual, expected) + } + + _, err = New(jsonKey) if err != nil { t.Errorf("got %#v", err) } @@ -63,14 +59,31 @@ func TestNewWithClient(t *testing.T) { ctx := context.Background() httpClient := urlfetch.Client(ctx) - cli, _ := NewWithClient(dummyKey, httpClient) - tr, _ := cli.httpCli.Transport.(*oauth2.Transport) - - if !reflect.DeepEqual(tr.Base, httpClient.Transport) { + _, err := NewWithClient(dummyKey, httpClient) + if err != nil { t.Errorf("transport should be urlfetch's one") } } +func TestNewWithClientErrors(t *testing.T) { + t.Parallel() + expected := errors.New("client is nil") + + _, actual := NewWithClient(dummyKey, nil) + if !reflect.DeepEqual(actual, expected) { + t.Errorf("got %v\nwant %v", actual, expected) + } + + ctx := context.Background() + httpClient := urlfetch.Client(ctx) + + _, actual = NewWithClient(nil, httpClient) + if actual == nil || actual.Error() != "unexpected end of JSON input" { + t.Errorf("got %v\nwant %v", actual, expected) + } + +} + func TestAcknowledgeSubscription(t *testing.T) { t.Parallel() // Exception scenario @@ -83,7 +96,7 @@ func TestAcknowledgeSubscription(t *testing.T) { } err := client.AcknowledgeSubscription(ctx, "package", "subscriptionID", "purchaseToken", req) - if err.Error() != expected { + if err == nil || err.Error() != expected { t.Errorf("got %v\nwant %v", err, expected) } @@ -99,25 +112,13 @@ func TestVerifySubscription(t *testing.T) { ctx := context.Background() _, err := client.VerifySubscription(ctx, "package", "subscriptionID", "purchaseToken") - if err.Error() != expected { + if err == nil || err.Error() != expected { t.Errorf("got %v\nwant %v", err, expected) } // TODO Normal scenario } -func TestVerifySubscriptionAndroidPublisherError(t *testing.T) { - t.Parallel() - client := Client{nil} - expected := errors.New("client is nil") - ctx := context.Background() - _, actual := client.VerifySubscription(ctx, "package", "subscriptionID", "purchaseToken") - - if !reflect.DeepEqual(actual, expected) { - t.Errorf("got %v\nwant %v", actual, expected) - } -} - func TestVerifyProduct(t *testing.T) { t.Parallel() // Exception scenario @@ -127,42 +128,37 @@ func TestVerifyProduct(t *testing.T) { ctx := context.Background() _, err := client.VerifyProduct(ctx, "package", "productID", "purchaseToken") - if err.Error() != expected { + if err == nil || err.Error() != expected { t.Errorf("got %v", err) } // TODO Normal scenario } -func TestVerifyProductAndroidPublisherError(t *testing.T) { +func TestAcknowledgeProduct(t *testing.T) { t.Parallel() - client := Client{nil} - expected := errors.New("client is nil") - ctx := context.Background() - _, actual := client.VerifyProduct(ctx, "package", "productID", "purchaseToken") + // Exception scenario + expected := "googleapi: Error 400: Invalid Value, invalid" - if !reflect.DeepEqual(actual, expected) { - t.Errorf("got %v\nwant %v", actual, expected) + client, _ := New(jsonKey) + ctx := context.Background() + err := client.AcknowledgeProduct(ctx, "package", "productID", "purchaseToken", "") + + if err == nil || err.Error() != expected { + t.Errorf("got %v", err) } + + // TODO Normal scenario } func TestCancelSubscription(t *testing.T) { t.Parallel() - // Exception scenario - client := &Client{nil} - expected := errors.New("client is nil") ctx := context.Background() + client, _ := New(jsonKey) + expectedStr := "googleapi: Error 400: Invalid Value, invalid" actual := client.CancelSubscription(ctx, "package", "productID", "purchaseToken") - if !reflect.DeepEqual(actual, expected) { - t.Errorf("got %v\nwant %v", actual, expected) - } - - client, _ = New(jsonKey) - expectedStr := "googleapi: Error 400: Invalid Value, invalid" - actual = client.CancelSubscription(ctx, "package", "productID", "purchaseToken") - - if actual.Error() != expectedStr { + if actual == nil || actual.Error() != expectedStr { t.Errorf("got %v\nwant %v", actual, expectedStr) } @@ -171,21 +167,13 @@ func TestCancelSubscription(t *testing.T) { func TestRefundSubscription(t *testing.T) { t.Parallel() - // Exception scenario - client := &Client{nil} - expected := errors.New("client is nil") + ctx := context.Background() + client, _ := New(jsonKey) + expectedStr := "googleapi: Error 404: No application was found for the given package name., applicationNotFound" actual := client.RefundSubscription(ctx, "package", "productID", "purchaseToken") - if !reflect.DeepEqual(actual, expected) { - t.Errorf("got %v\nwant %v", actual, expected) - } - - client, _ = New(jsonKey) - expectedStr := "googleapi: Error 404: No application was found for the given package name., applicationNotFound" - actual = client.RefundSubscription(ctx, "package", "productID", "purchaseToken") - - if actual.Error() != expectedStr { + if actual == nil || actual.Error() != expectedStr { t.Errorf("got %v\nwant %v", actual, expectedStr) } @@ -194,21 +182,13 @@ func TestRefundSubscription(t *testing.T) { func TestRevokeSubscription(t *testing.T) { t.Parallel() - // Exception scenario - client := &Client{nil} - expected := errors.New("client is nil") + ctx := context.Background() + client, _ := New(jsonKey) + expectedStr := "googleapi: Error 404: No application was found for the given package name., applicationNotFound" actual := client.RevokeSubscription(ctx, "package", "productID", "purchaseToken") - if !reflect.DeepEqual(actual, expected) { - t.Errorf("got %v\nwant %v", actual, expected) - } - - client, _ = New(jsonKey) - expectedStr := "googleapi: Error 404: No application was found for the given package name., applicationNotFound" - actual = client.RevokeSubscription(ctx, "package", "productID", "purchaseToken") - - if actual.Error() != expectedStr { + if actual == nil || actual.Error() != expectedStr { t.Errorf("got %v\nwant %v", actual, expectedStr) }