diff --git a/playstore/validator.go b/playstore/validator.go index cffbeb1..4642040 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" @@ -19,6 +20,7 @@ import ( // 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 +34,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 +77,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 +93,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 +106,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 +119,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 +144,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 +153,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..4fc98b4 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,12 @@ 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() + _, err = New(jsonKey) if err != nil { t.Errorf("got %#v", err) } @@ -63,14 +54,23 @@ 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 TestNewWithNoClient(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) + } + +} + func TestAcknowledgeSubscription(t *testing.T) { t.Parallel() // Exception scenario @@ -83,7 +83,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 +99,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 +115,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 +154,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 +169,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) }