forked from Mirrors/go-iap
add acknowledge product, refactor
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
|||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"google.golang.org/api/option"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@ import (
|
|||||||
// The IABProduct type is an interface for product service
|
// The IABProduct type is an interface for product service
|
||||||
type IABProduct interface {
|
type IABProduct interface {
|
||||||
VerifyProduct(context.Context, string, string, string) (*androidpublisher.ProductPurchase, error)
|
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
|
// The IABSubscription type is an interface for subscription service
|
||||||
@@ -32,22 +34,42 @@ type IABSubscription interface {
|
|||||||
|
|
||||||
// The Client type implements VerifySubscription method
|
// The Client type implements VerifySubscription method
|
||||||
type Client struct {
|
type Client struct {
|
||||||
httpCli *http.Client
|
service *androidpublisher.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns http client which includes the credentials to access androidpublisher API.
|
// New returns http client which includes the credentials to access androidpublisher API.
|
||||||
// You should create a service account for your project at
|
// 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.
|
// https://console.developers.google.com and download a JSON key file to set this argument.
|
||||||
func New(jsonKey []byte) (*Client, error) {
|
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)
|
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.
|
// NewWithClient returns http client which includes the custom http client.
|
||||||
func NewWithClient(jsonKey []byte, cli *http.Client) (*Client, error) {
|
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)
|
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, cli)
|
||||||
|
|
||||||
conf, err := google.JWTConfigFromJSON(jsonKey, androidpublisher.AndroidpublisherScope)
|
conf, err := google.JWTConfigFromJSON(jsonKey, androidpublisher.AndroidpublisherScope)
|
||||||
@@ -55,7 +77,12 @@ func NewWithClient(jsonKey []byte, cli *http.Client) (*Client, error) {
|
|||||||
return nil, err
|
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.
|
// AcknowledgeSubscription acknowledges a subscription purchase.
|
||||||
@@ -66,13 +93,8 @@ func (c *Client) AcknowledgeSubscription(
|
|||||||
token string,
|
token string,
|
||||||
req *androidpublisher.SubscriptionPurchasesAcknowledgeRequest,
|
req *androidpublisher.SubscriptionPurchasesAcknowledgeRequest,
|
||||||
) error {
|
) error {
|
||||||
service, err := androidpublisher.New(c.httpCli)
|
ps := androidpublisher.NewPurchasesSubscriptionsService(c.service)
|
||||||
if err != nil {
|
err := ps.Acknowledge(packageName, subscriptionID, token, req).Context(ctx).Do()
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ps := androidpublisher.NewPurchasesSubscriptionsService(service)
|
|
||||||
err = ps.Acknowledge(packageName, subscriptionID, token, req).Context(ctx).Do()
|
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -84,12 +106,7 @@ func (c *Client) VerifySubscription(
|
|||||||
subscriptionID string,
|
subscriptionID string,
|
||||||
token string,
|
token string,
|
||||||
) (*androidpublisher.SubscriptionPurchase, error) {
|
) (*androidpublisher.SubscriptionPurchase, error) {
|
||||||
service, err := androidpublisher.New(c.httpCli)
|
ps := androidpublisher.NewPurchasesSubscriptionsService(c.service)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ps := androidpublisher.NewPurchasesSubscriptionsService(service)
|
|
||||||
result, err := ps.Get(packageName, subscriptionID, token).Context(ctx).Do()
|
result, err := ps.Get(packageName, subscriptionID, token).Context(ctx).Do()
|
||||||
|
|
||||||
return result, err
|
return result, err
|
||||||
@@ -102,26 +119,24 @@ func (c *Client) VerifyProduct(
|
|||||||
productID string,
|
productID string,
|
||||||
token string,
|
token string,
|
||||||
) (*androidpublisher.ProductPurchase, error) {
|
) (*androidpublisher.ProductPurchase, error) {
|
||||||
service, err := androidpublisher.New(c.httpCli)
|
ps := androidpublisher.NewPurchasesProductsService(c.service)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ps := androidpublisher.NewPurchasesProductsService(service)
|
|
||||||
result, err := ps.Get(packageName, productID, token).Context(ctx).Do()
|
result, err := ps.Get(packageName, productID, token).Context(ctx).Do()
|
||||||
|
|
||||||
return result, err
|
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.
|
// CancelSubscription cancels a user's subscription purchase.
|
||||||
func (c *Client) CancelSubscription(ctx context.Context, packageName string, subscriptionID string, token string) error {
|
func (c *Client) CancelSubscription(ctx context.Context, packageName string, subscriptionID string, token string) error {
|
||||||
service, err := androidpublisher.New(c.httpCli)
|
ps := androidpublisher.NewPurchasesSubscriptionsService(c.service)
|
||||||
if err != nil {
|
err := ps.Cancel(packageName, subscriptionID, token).Context(ctx).Do()
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ps := androidpublisher.NewPurchasesSubscriptionsService(service)
|
|
||||||
err = ps.Cancel(packageName, subscriptionID, token).Context(ctx).Do()
|
|
||||||
|
|
||||||
return err
|
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
|
// RefundSubscription refunds a user's subscription purchase, but the subscription remains valid
|
||||||
// until its expiration time and it will continue to recur.
|
// until its expiration time and it will continue to recur.
|
||||||
func (c *Client) RefundSubscription(ctx context.Context, packageName string, subscriptionID string, token string) error {
|
func (c *Client) RefundSubscription(ctx context.Context, packageName string, subscriptionID string, token string) error {
|
||||||
service, err := androidpublisher.New(c.httpCli)
|
ps := androidpublisher.NewPurchasesSubscriptionsService(c.service)
|
||||||
if err != nil {
|
err := ps.Refund(packageName, subscriptionID, token).Context(ctx).Do()
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ps := androidpublisher.NewPurchasesSubscriptionsService(service)
|
|
||||||
err = ps.Refund(packageName, subscriptionID, token).Context(ctx).Do()
|
|
||||||
|
|
||||||
return err
|
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.
|
// RevokeSubscription refunds and immediately revokes a user's subscription purchase.
|
||||||
// Access to the subscription will be terminated immediately and it will stop recurring.
|
// 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 {
|
func (c *Client) RevokeSubscription(ctx context.Context, packageName string, subscriptionID string, token string) error {
|
||||||
service, err := androidpublisher.New(c.httpCli)
|
ps := androidpublisher.NewPurchasesSubscriptionsService(c.service)
|
||||||
if err != nil {
|
err := ps.Revoke(packageName, subscriptionID, token).Context(ctx).Do()
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ps := androidpublisher.NewPurchasesSubscriptionsService(service)
|
|
||||||
err = ps.Revoke(packageName, subscriptionID, token).Context(ctx).Do()
|
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"golang.org/x/oauth2"
|
"google.golang.org/api/androidpublisher/v3"
|
||||||
androidpublisher "google.golang.org/api/androidpublisher/v3"
|
|
||||||
"google.golang.org/appengine/urlfetch"
|
"google.golang.org/appengine/urlfetch"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -38,20 +37,12 @@ func TestNew(t *testing.T) {
|
|||||||
// Exception scenario
|
// 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}"
|
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)
|
_, err := New(dummyKey)
|
||||||
val := actual.httpCli.Transport.(*oauth2.Transport)
|
if err == nil || err.Error() != expected {
|
||||||
token, err := val.Source.Token()
|
|
||||||
if token != nil {
|
|
||||||
t.Errorf("got %#v", token)
|
|
||||||
}
|
|
||||||
if err.Error() != expected {
|
|
||||||
t.Errorf("got %v\nwant %v", err, expected)
|
t.Errorf("got %v\nwant %v", err, expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Normal scenario
|
_, err = New(jsonKey)
|
||||||
actual, _ = New(jsonKey)
|
|
||||||
val = actual.httpCli.Transport.(*oauth2.Transport)
|
|
||||||
token, err = val.Source.Token()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("got %#v", err)
|
t.Errorf("got %#v", err)
|
||||||
}
|
}
|
||||||
@@ -63,14 +54,23 @@ func TestNewWithClient(t *testing.T) {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
httpClient := urlfetch.Client(ctx)
|
httpClient := urlfetch.Client(ctx)
|
||||||
|
|
||||||
cli, _ := NewWithClient(dummyKey, httpClient)
|
_, err := NewWithClient(dummyKey, httpClient)
|
||||||
tr, _ := cli.httpCli.Transport.(*oauth2.Transport)
|
if err != nil {
|
||||||
|
|
||||||
if !reflect.DeepEqual(tr.Base, httpClient.Transport) {
|
|
||||||
t.Errorf("transport should be urlfetch's one")
|
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) {
|
func TestAcknowledgeSubscription(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
// Exception scenario
|
// Exception scenario
|
||||||
@@ -83,7 +83,7 @@ func TestAcknowledgeSubscription(t *testing.T) {
|
|||||||
}
|
}
|
||||||
err := client.AcknowledgeSubscription(ctx, "package", "subscriptionID", "purchaseToken", req)
|
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)
|
t.Errorf("got %v\nwant %v", err, expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,25 +99,13 @@ func TestVerifySubscription(t *testing.T) {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
_, err := client.VerifySubscription(ctx, "package", "subscriptionID", "purchaseToken")
|
_, 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)
|
t.Errorf("got %v\nwant %v", err, expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Normal scenario
|
// 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) {
|
func TestVerifyProduct(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
// Exception scenario
|
// Exception scenario
|
||||||
@@ -127,42 +115,37 @@ func TestVerifyProduct(t *testing.T) {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
_, err := client.VerifyProduct(ctx, "package", "productID", "purchaseToken")
|
_, err := client.VerifyProduct(ctx, "package", "productID", "purchaseToken")
|
||||||
|
|
||||||
if err.Error() != expected {
|
if err == nil || err.Error() != expected {
|
||||||
t.Errorf("got %v", err)
|
t.Errorf("got %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Normal scenario
|
// TODO Normal scenario
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVerifyProductAndroidPublisherError(t *testing.T) {
|
func TestAcknowledgeProduct(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
client := Client{nil}
|
// Exception scenario
|
||||||
expected := errors.New("client is nil")
|
expected := "googleapi: Error 400: Invalid Value, invalid"
|
||||||
ctx := context.Background()
|
|
||||||
_, actual := client.VerifyProduct(ctx, "package", "productID", "purchaseToken")
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(actual, expected) {
|
client, _ := New(jsonKey)
|
||||||
t.Errorf("got %v\nwant %v", actual, expected)
|
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) {
|
func TestCancelSubscription(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
// Exception scenario
|
|
||||||
client := &Client{nil}
|
|
||||||
expected := errors.New("client is nil")
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
client, _ := New(jsonKey)
|
||||||
|
expectedStr := "googleapi: Error 400: Invalid Value, invalid"
|
||||||
actual := client.CancelSubscription(ctx, "package", "productID", "purchaseToken")
|
actual := client.CancelSubscription(ctx, "package", "productID", "purchaseToken")
|
||||||
|
|
||||||
if !reflect.DeepEqual(actual, expected) {
|
if actual == nil || actual.Error() != expectedStr {
|
||||||
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 {
|
|
||||||
t.Errorf("got %v\nwant %v", actual, expectedStr)
|
t.Errorf("got %v\nwant %v", actual, expectedStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,21 +154,13 @@ func TestCancelSubscription(t *testing.T) {
|
|||||||
|
|
||||||
func TestRefundSubscription(t *testing.T) {
|
func TestRefundSubscription(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
// Exception scenario
|
|
||||||
client := &Client{nil}
|
|
||||||
expected := errors.New("client is nil")
|
|
||||||
ctx := context.Background()
|
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")
|
actual := client.RefundSubscription(ctx, "package", "productID", "purchaseToken")
|
||||||
|
|
||||||
if !reflect.DeepEqual(actual, expected) {
|
if actual == nil || actual.Error() != expectedStr {
|
||||||
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 {
|
|
||||||
t.Errorf("got %v\nwant %v", actual, expectedStr)
|
t.Errorf("got %v\nwant %v", actual, expectedStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,21 +169,13 @@ func TestRefundSubscription(t *testing.T) {
|
|||||||
|
|
||||||
func TestRevokeSubscription(t *testing.T) {
|
func TestRevokeSubscription(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
// Exception scenario
|
|
||||||
client := &Client{nil}
|
|
||||||
expected := errors.New("client is nil")
|
|
||||||
ctx := context.Background()
|
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")
|
actual := client.RevokeSubscription(ctx, "package", "productID", "purchaseToken")
|
||||||
|
|
||||||
if !reflect.DeepEqual(actual, expected) {
|
if actual == nil || actual.Error() != expectedStr {
|
||||||
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 {
|
|
||||||
t.Errorf("got %v\nwant %v", actual, expectedStr)
|
t.Errorf("got %v\nwant %v", actual, expectedStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user