18 Commits

Author SHA1 Message Date
sanjid133
96a12064d7 remove latest response 2020-02-17 14:30:34 +06:00
sanjid133
d8b160b5cb fix auto renew status 2020-02-11 20:27:55 +06:00
sanjid133
e811b02798 remove duplicate 2020-02-11 20:27:55 +06:00
sanjid133
5b94718573 status field not present in initial_buy 2020-02-11 20:27:55 +06:00
sanjid133
db9e8f1637 auto renew status string 2020-02-11 20:27:55 +06:00
sanjid133
2b7d6c1820 Add renewal field 2020-02-11 20:27:55 +06:00
sanjid133
d74d2cfd2c Verfiy latest receipt 2020-02-11 20:27:55 +06:00
Minhaz Ahmed Syrus
d5b4694461 Add missing subscription billing retry flag 2020-02-11 20:26:09 +06:00
Minhaz Ahmed Syrus
b0a4ee19ee Add missing notification verification fields 2020-02-11 20:26:09 +06:00
Minhaz Ahmed Syrus
202310d2c3 Add missing subscription billing retry flag 2020-02-11 20:26:09 +06:00
Minhaz Ahmed Syrus
45aa311b47 Add missing notification verification fields 2020-02-11 20:25:51 +06:00
sanjid133
51a2c89640 Add renewal field 2020-02-11 20:25:08 +06:00
sanjid133
36d117cd16 Verfiy latest receipt 2020-02-11 20:25:08 +06:00
sanjid133
e28611afcb Add missing is_upgrade field 2020-02-11 20:25:08 +06:00
Minhaz Ahmed Syrus
1fe1bd0f92 Add missing subscription billing retry flag 2020-02-11 20:25:08 +06:00
Minhaz Ahmed Syrus
f0ee201e43 Add missing notification verification fields 2020-02-11 20:24:39 +06:00
Minhaz Ahmed Syrus
312aa41817 Add missing subscription billing retry flag 2020-02-11 20:22:08 +06:00
Minhaz Ahmed Syrus
eb89375f2e Add missing notification verification fields 2020-02-11 20:22:08 +06:00
13 changed files with 206 additions and 457 deletions

View File

@@ -1,6 +1,6 @@
language: go
go:
- 1.13.x
- 1.12.5
env:
global:
- GO111MODULE=on

View File

@@ -1,22 +1,15 @@
.PHONEY: all
.PHONEY: all setup test cover
all: setup cover
.PHONEY: setup
setup:
go get golang.org/x/tools/cmd/cover
go get google.golang.org/appengine/urlfetch
go get ./...
go get golang.org/x/tools/cmd/cover
go get google.golang.org/appengine/urlfetch
go get ./...
.PHONEY: test
test:
go test -v ./...
go test -v ./...
.PHONEY: cover
cover:
go test -coverprofile=coverage.txt ./...
.PHONEY: generate
generate:
rm -rf ./appstore/mocks/*
rm -rf ./playstore/mocks/*
go generate ./...
go test -coverprofile=coverage.txt ./...

View File

@@ -1,7 +1,7 @@
go-iap
======
![](https://img.shields.io/badge/golang-1.13-blue.svg?style=flat)
![](https://img.shields.io/badge/golang-1.12-blue.svg?style=flat)
[![Build Status](https://travis-ci.org/awa/go-iap.svg?branch=master)](https://travis-ci.org/awa/go-iap)
[![codecov.io](https://codecov.io/github/awa/go-iap/coverage.svg?branch=master)](https://codecov.io/github/awa/go-iap?branch=master)

View File

@@ -1,49 +0,0 @@
// 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)
}

View File

@@ -150,19 +150,6 @@ type (
IsRetryable bool `json:"is-retryable,omitempty"`
}
// The IAPLatestResponse type has the response properties
// If you use latest_receipt as token to verify, response should be like following struct
IAPLatestResponse struct {
Status int `json:"status,omitempty"`
Receipt InApp `json:"receipt"`
LatestReceiptInfo InApp `json:"latest_receipt_info,omitempty"`
LatestExpiredReceiptInfo InApp `json:"latest_expired_receipt_info,omitempty"`
LatestReceipt string `json:"latest_receipt,omitempty"`
SubscriptionAutoRenewStatus interface{} `json:"auto_renew_status,omitempty"`
SubscriptionAutoRenewProductID string `json:"auto_renew_product_id,omitempty"`
SubscriptionRetryFlag string `json:"is_in_billing_retry_period,omitempty"`
}
// The HttpStatusResponse struct contains the status code returned by the store
// Used as a workaround to detect when to hit the production appstore or sandbox appstore regardless of receipt type
StatusResponse struct {

View File

@@ -19,8 +19,6 @@ const (
NotificationTypeDidChangeRenewalPreference NotificationType = "DID_CHANGE_RENEWAL_PREF"
// Customer changed the subscription renewal status. Current active plan is not affected.
NotificationTypeDidChangeRenewalStatus NotificationType = "DID_CHANGE_RENEWAL_STATUS"
// Subscription failed to renew due to a billing issue.
NotificationTypeDidFailToRenew NotificationType = "DID_FAIL_TO_RENEW"
)
type NotificationEnvironment string
@@ -56,7 +54,6 @@ type NotificationReceipt struct {
OriginalPurchaseDate
NotificationExpiresDate
CancellationDate
CancellationReason string `json:"cancellation_reason,omitempty"`
}
type NotificationUnifiedReceipt struct {
@@ -85,19 +82,19 @@ type SubscriptionNotification struct {
ExpirationIntent string `json:"expiration_intent"`
// Auto renew info
AutoRenewStatus string `json:"auto_renew_status"` // false or true
AutoRenewStatus string `json:"auto_renew_status"` // false or true
AutoRenewProductID string `json:"auto_renew_product_id"`
// HACK (msyrus): Separate Subscriptiton Notification from Notification verification response
Status int `json:"status,omitempty"`
Receipt NotificationReceipt `json:"recipt"`
SubscriptionRetryFlag string `json:"is_in_billing_retry_period"`
SubscriptionRetryFlag string `json:"is_in_billing_retry_period,omitempty"`
// Posted if the notification_type is RENEWAL or INTERACTIVE_RENEWAL, and only if the renewal is successful.
// Posted also if the notification_type is INITIAL_BUY.
// Not posted for notification_type CANCEL.
// Deprecated: use UnifiedReceipt.LatestReceipt instead. See details: https://developer.apple.com/documentation/appstoreservernotifications/ .
LatestReceipt string `json:"latest_receipt"`
LatestReceipt string `json:"latest_receipt"`
// Deprecated: use UnifiedReceipt.LatestReceiptInfo instead. See details: https://developer.apple.com/documentation/appstoreservernotifications/ .
LatestReceiptInfo NotificationReceipt `json:"latest_receipt_info"`
@@ -106,11 +103,10 @@ type SubscriptionNotification struct {
// Posted only if the notification_type is RENEWAL or CANCEL or if renewal failed and subscription expired.
// Deprecated: see details: https://developer.apple.com/documentation/appstoreservernotifications/ .
LatestExpiredReceipt string `json:"latest_expired_receipt"`
LatestExpiredReceipt string `json:"latest_expired_receipt"`
// Deprecated: see details: https://developer.apple.com/documentation/appstoreservernotifications/ .
LatestExpiredReceiptInfo NotificationReceipt `json:"latest_expired_receipt_info"`
// Posted only if the notification_type is CANCEL.
CancellationDate
CancellationReason string `json:"cancellation_reason,omitempty"`
}

View File

@@ -5,14 +5,11 @@ import (
"context"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"time"
)
//go:generate mockgen -destination=mocks/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"
@@ -34,53 +31,47 @@ type Client struct {
httpCli *http.Client
}
var (
ErrAppStoreServer = errors.New("AppStore server error")
ErrInvalidJSON = errors.New("The App Store could not read the JSON object you provided.")
ErrInvalidReceiptData = errors.New("The data in the receipt-data property was malformed or missing.")
ErrReceiptUnauthenticated = errors.New("The receipt could not be authenticated.")
ErrInvalidSharedSecret = errors.New("The shared secret you provided does not match the shared secret on file for your account.")
ErrServerUnavailable = errors.New("The receipt server is not currently available.")
ErrReceiptIsForTest = errors.New("This receipt is from the test environment, but it was sent to the production environment for verification. Send it to the test environment instead.")
ErrReceiptIsForProduction = errors.New("This receipt is from the production environment, but it was sent to the test environment for verification. Send it to the production environment instead.")
ErrReceiptUnauthorized = errors.New("This receipt could not be authorized. Treat this the same as if a purchase was never made.")
ErrInternalDataAccessError = errors.New("Internal data access error.")
ErrUnknown = errors.New("An unknown error occurred")
)
// HandleError returns error message by status code
func HandleError(status int) error {
var e error
var message string
switch status {
case 0:
return nil
case 21000:
e = ErrInvalidJSON
message = "The App Store could not read the JSON object you provided."
case 21002:
e = ErrInvalidReceiptData
message = "The data in the receipt-data property was malformed or missing."
case 21003:
e = ErrReceiptUnauthenticated
message = "The receipt could not be authenticated."
case 21004:
e = ErrInvalidSharedSecret
message = "The shared secret you provided does not match the shared secret on file for your account."
case 21005:
e = ErrServerUnavailable
message = "The receipt server is not currently available."
case 21007:
e = ErrReceiptIsForTest
message = "This receipt is from the test environment, but it was sent to the production environment for verification. Send it to the test environment instead."
case 21008:
e = ErrReceiptIsForProduction
message = "This receipt is from the production environment, but it was sent to the test environment for verification. Send it to the production environment instead."
case 21010:
e = ErrReceiptUnauthorized
message = "This receipt could not be authorized. Treat this the same as if a purchase was never made."
default:
if status >= 21100 && status <= 21199 {
e = ErrInternalDataAccessError
message = "Internal data access error."
} else {
e = ErrUnknown
message = "An unknown error occurred"
}
}
return fmt.Errorf("status %d: %w", status, e)
return errors.New(message)
}
// New creates a client object
@@ -122,9 +113,6 @@ func (c *Client) Verify(ctx context.Context, reqBody IAPRequest, result interfac
return "", err
}
defer resp.Body.Close()
if resp.StatusCode >= 500 {
return "", fmt.Errorf("Received http status code %d from the App Store: %w", resp.StatusCode, ErrAppStoreServer)
}
return c.parseResponse(resp, result, ctx, reqBody)
}
@@ -163,10 +151,6 @@ func (c *Client) parseResponse(resp *http.Response, result interface{}, ctx cont
return "", err
}
defer resp.Body.Close()
if resp.StatusCode >= 500 {
return Sandbox, fmt.Errorf("Received http status code %d from the App Store Sandbox: %w", resp.StatusCode, ErrAppStoreServer)
}
// 21007 is found when the receipt is from the test environment
return Sandbox, json.NewDecoder(resp.Body).Decode(result)
}

View File

@@ -26,52 +26,52 @@ func TestHandleError(t *testing.T) {
{
name: "status 21000",
in: 21000,
out: ErrInvalidJSON,
out: errors.New("The App Store could not read the JSON object you provided."),
},
{
name: "status 21002",
in: 21002,
out: ErrInvalidReceiptData,
out: errors.New("The data in the receipt-data property was malformed or missing."),
},
{
name: "status 21003",
in: 21003,
out: ErrReceiptUnauthenticated,
out: errors.New("The receipt could not be authenticated."),
},
{
name: "status 21004",
in: 21004,
out: ErrInvalidSharedSecret,
out: errors.New("The shared secret you provided does not match the shared secret on file for your account."),
},
{
name: "status 21005",
in: 21005,
out: ErrServerUnavailable,
out: errors.New("The receipt server is not currently available."),
},
{
name: "status 21007",
in: 21007,
out: ErrReceiptIsForTest,
out: errors.New("This receipt is from the test environment, but it was sent to the production environment for verification. Send it to the test environment instead."),
},
{
name: "status 21008",
in: 21008,
out: ErrReceiptIsForProduction,
out: errors.New("This receipt is from the production environment, but it was sent to the test environment for verification. Send it to the production environment instead."),
},
{
name: "status 21010",
in: 21010,
out: ErrReceiptUnauthorized,
out: errors.New("This receipt could not be authorized. Treat this the same as if a purchase was never made."),
},
{
name: "status 21100 ~ 21199",
in: 21100,
out: ErrInternalDataAccessError,
out: errors.New("Internal data access error."),
},
{
name: "status unknown",
in: 100,
out: ErrUnknown,
out: errors.New("An unknown error occurred"),
},
}
@@ -79,7 +79,7 @@ func TestHandleError(t *testing.T) {
t.Run(v.name, func(t *testing.T) {
out := HandleError(v.in)
if !errors.Is(out, v.out) {
if !reflect.DeepEqual(out, v.out) {
t.Errorf("input: %d\ngot: %v\nwant: %v\n", v.in, out, v.out)
}
})
@@ -180,30 +180,29 @@ func TestResponses(t *testing.T) {
result := &IAPResponse{}
type testCase struct {
name string
testServer *httptest.Server
sandboxServ *httptest.Server
expected *IAPResponse
}
testCases := []testCase{
// VerifySandboxReceipt
{
name: "VerifySandboxReceipt",
testServer: httptest.NewServer(serverWithResponse(http.StatusOK, `{"status": 21007}`)),
sandboxServ: httptest.NewServer(serverWithResponse(http.StatusOK, `{"status": 0}`)),
expected: &IAPResponse{
Status: 0,
},
},
// VerifyBadPayload
{
name: "VerifyBadPayload",
testServer: httptest.NewServer(serverWithResponse(http.StatusOK, `{"status": 21002}`)),
expected: &IAPResponse{
Status: 21002,
},
},
// SuccessPayload
{
name: "SuccessPayload",
testServer: httptest.NewServer(serverWithResponse(http.StatusBadRequest, `{"status": 0}`)),
expected: &IAPResponse{
Status: 0,
@@ -214,65 +213,57 @@ func TestResponses(t *testing.T) {
client := New()
client.SandboxURL = "localhost"
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
defer tc.testServer.Close()
client.ProductionURL = tc.testServer.URL
if tc.sandboxServ != nil {
client.SandboxURL = tc.sandboxServ.URL
}
for i, tc := range testCases {
defer tc.testServer.Close()
client.ProductionURL = tc.testServer.URL
if tc.sandboxServ != nil {
client.SandboxURL = tc.sandboxServ.URL
}
ctx := context.Background()
err := client.Verify(ctx, req, result)
if err != nil {
t.Errorf("%s", err)
}
if !reflect.DeepEqual(result, tc.expected) {
t.Errorf("got %v\nwant %v", result, tc.expected)
}
})
ctx := context.Background()
err := client.Verify(ctx, req, result)
if err != nil {
t.Errorf("Test case %d - %s", i, err.Error())
}
if !reflect.DeepEqual(result, tc.expected) {
t.Errorf("Test case %d - got %v\nwant %v", i, result, tc.expected)
}
}
}
func TestHttpStatusErrors(t *testing.T) {
func TestErrors(t *testing.T) {
req := IAPRequest{
ReceiptData: "dummy data",
}
result := &IAPResponse{}
type testCase struct {
name string
testServer *httptest.Server
err error
}
testCases := []testCase{
// VerifySandboxReceiptFailure
{
name: "status 200",
testServer: httptest.NewServer(serverWithResponse(http.StatusOK, `{"status": 21000}`)),
err: nil,
testServer: httptest.NewServer(serverWithResponse(http.StatusOK, `{"status": 21007}`)),
},
// VerifyBadResponse
{
name: "status 500",
testServer: httptest.NewServer(serverWithResponse(http.StatusInternalServerError, `qwerty!@#$%^`)),
err: ErrAppStoreServer,
},
}
client := New()
client.SandboxURL = "localhost"
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
defer tc.testServer.Close()
client.ProductionURL = tc.testServer.URL
for i, tc := range testCases {
defer tc.testServer.Close()
client.ProductionURL = tc.testServer.URL
ctx := context.Background()
err := client.Verify(ctx, req, result)
if !errors.Is(err, tc.err) {
t.Errorf("expected error to be not nil since the sandbox is not responding")
}
})
ctx := context.Background()
err := client.Verify(ctx, req, result)
if err == nil {
t.Errorf("Test case %d - expected error to be not nil since the sandbox is not responding", i)
}
}
}
@@ -306,10 +297,12 @@ func serverWithResponse(statusCode int, response string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if "POST" == r.Method {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
w.Write([]byte(response))
return
} else {
w.Write([]byte(`unsupported request`))
}
w.WriteHeader(statusCode)
})
}

9
go.mod
View File

@@ -1,16 +1,15 @@
module git.deineagentur.com/DeineAgenturUG/go-iap
module github.com/awa/go-iap
go 1.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-20190620200207-3b0461eec859 // indirect
golang.org/x/net v0.0.0-20190522155817-f3200d17e092 // 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.5
google.golang.org/appengine v1.6.0
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101 // indirect
google.golang.org/grpc v1.21.0 // indirect
)

17
go.sum
View File

@@ -8,10 +8,7 @@ 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=
@@ -45,9 +42,8 @@ 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-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/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/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=
@@ -56,13 +52,11 @@ 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=
@@ -74,7 +68,6 @@ 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=
@@ -82,8 +75,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.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
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/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=
@@ -98,5 +91,3 @@ 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=

View File

@@ -1,158 +0,0 @@
// 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)
}

View File

@@ -11,19 +11,14 @@ import (
"net/http"
"time"
"google.golang.org/api/option"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
androidpublisher "google.golang.org/api/androidpublisher/v3"
)
//go:generate mockgen -destination=mocks/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
@@ -37,41 +32,22 @@ type IABSubscription interface {
// The Client type implements VerifySubscription method
type Client struct {
service *androidpublisher.Service
httpCli *http.Client
}
// 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) {
c := &http.Client{Timeout: 10 * time.Second}
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, c)
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, &http.Client{Timeout: 10 * time.Second})
conf, err := google.JWTConfigFromJSON(jsonKey, androidpublisher.AndroidpublisherScope)
if err != nil {
return nil, 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
return &Client{conf.Client(ctx)}, 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)
@@ -79,12 +55,7 @@ func NewWithClient(jsonKey []byte, cli *http.Client) (*Client, error) {
return nil, err
}
service, err := androidpublisher.NewService(ctx, option.WithHTTPClient(conf.Client(ctx)))
if err != nil {
return nil, err
}
return &Client{service}, err
return &Client{conf.Client(ctx)}, err
}
// AcknowledgeSubscription acknowledges a subscription purchase.
@@ -95,8 +66,13 @@ func (c *Client) AcknowledgeSubscription(
token string,
req *androidpublisher.SubscriptionPurchasesAcknowledgeRequest,
) error {
ps := androidpublisher.NewPurchasesSubscriptionsService(c.service)
err := ps.Acknowledge(packageName, subscriptionID, token, req).Context(ctx).Do()
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()
return err
}
@@ -108,7 +84,12 @@ func (c *Client) VerifySubscription(
subscriptionID string,
token string,
) (*androidpublisher.SubscriptionPurchase, error) {
ps := androidpublisher.NewPurchasesSubscriptionsService(c.service)
service, err := androidpublisher.New(c.httpCli)
if err != nil {
return nil, err
}
ps := androidpublisher.NewPurchasesSubscriptionsService(service)
result, err := ps.Get(packageName, subscriptionID, token).Context(ctx).Do()
return result, err
@@ -121,24 +102,26 @@ func (c *Client) VerifyProduct(
productID string,
token string,
) (*androidpublisher.ProductPurchase, error) {
ps := androidpublisher.NewPurchasesProductsService(c.service)
service, err := androidpublisher.New(c.httpCli)
if err != nil {
return nil, err
}
ps := androidpublisher.NewPurchasesProductsService(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 {
ps := androidpublisher.NewPurchasesSubscriptionsService(c.service)
err := ps.Cancel(packageName, subscriptionID, token).Context(ctx).Do()
service, err := androidpublisher.New(c.httpCli)
if err != nil {
return err
}
ps := androidpublisher.NewPurchasesSubscriptionsService(service)
err = ps.Cancel(packageName, subscriptionID, token).Context(ctx).Do()
return err
}
@@ -146,8 +129,13 @@ 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 {
ps := androidpublisher.NewPurchasesSubscriptionsService(c.service)
err := ps.Refund(packageName, subscriptionID, token).Context(ctx).Do()
service, err := androidpublisher.New(c.httpCli)
if err != nil {
return err
}
ps := androidpublisher.NewPurchasesSubscriptionsService(service)
err = ps.Refund(packageName, subscriptionID, token).Context(ctx).Do()
return err
}
@@ -155,8 +143,13 @@ 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 {
ps := androidpublisher.NewPurchasesSubscriptionsService(c.service)
err := ps.Revoke(packageName, subscriptionID, token).Context(ctx).Do()
service, err := androidpublisher.New(c.httpCli)
if err != nil {
return err
}
ps := androidpublisher.NewPurchasesSubscriptionsService(service)
err = ps.Revoke(packageName, subscriptionID, token).Context(ctx).Do()
return err
}

View File

@@ -7,7 +7,8 @@ import (
"reflect"
"testing"
"google.golang.org/api/androidpublisher/v3"
"golang.org/x/oauth2"
androidpublisher "google.golang.org/api/androidpublisher/v3"
"google.golang.org/appengine/urlfetch"
)
@@ -35,19 +36,22 @@ func TestNew(t *testing.T) {
t.Parallel()
// Exception scenario
expected := "oauth2: cannot fetch token: 400 Bad Request\nResponse: {\"error\":\"invalid_grant\",\"error_description\":\"Invalid JWT: iss field missing.\"}"
expected := "oauth2: cannot fetch token: 400 Bad Request\nResponse: {\n \"error\": \"invalid_grant\",\n \"error_description\": \"Invalid issuer: Not a service account.\"\n}"
_, err := New(dummyKey)
if err == nil || err.Error() != expected {
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 {
t.Errorf("got %v\nwant %v", err, expected)
}
_, actual := New(nil)
if actual == nil || actual.Error() != "unexpected end of JSON input" {
t.Errorf("got %v\nwant %v", actual, expected)
}
_, err = New(jsonKey)
// TODO Normal scenario
actual, _ = New(jsonKey)
val = actual.httpCli.Transport.(*oauth2.Transport)
token, err = val.Source.Token()
if err != nil {
t.Errorf("got %#v", err)
}
@@ -59,31 +63,14 @@ func TestNewWithClient(t *testing.T) {
ctx := context.Background()
httpClient := urlfetch.Client(ctx)
_, err := NewWithClient(dummyKey, httpClient)
if err != nil {
cli, _ := NewWithClient(dummyKey, httpClient)
tr, _ := cli.httpCli.Transport.(*oauth2.Transport)
if !reflect.DeepEqual(tr.Base, httpClient.Transport) {
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
@@ -96,7 +83,7 @@ func TestAcknowledgeSubscription(t *testing.T) {
}
err := client.AcknowledgeSubscription(ctx, "package", "subscriptionID", "purchaseToken", req)
if err == nil || err.Error() != expected {
if err.Error() != expected {
t.Errorf("got %v\nwant %v", err, expected)
}
@@ -112,13 +99,25 @@ func TestVerifySubscription(t *testing.T) {
ctx := context.Background()
_, err := client.VerifySubscription(ctx, "package", "subscriptionID", "purchaseToken")
if err == nil || err.Error() != expected {
if 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
@@ -128,37 +127,42 @@ func TestVerifyProduct(t *testing.T) {
ctx := context.Background()
_, err := client.VerifyProduct(ctx, "package", "productID", "purchaseToken")
if err == nil || err.Error() != expected {
if err.Error() != expected {
t.Errorf("got %v", err)
}
// TODO Normal scenario
}
func TestAcknowledgeProduct(t *testing.T) {
func TestVerifyProductAndroidPublisherError(t *testing.T) {
t.Parallel()
// Exception scenario
expected := "googleapi: Error 400: Invalid Value, invalid"
client, _ := New(jsonKey)
client := Client{nil}
expected := errors.New("client is nil")
ctx := context.Background()
err := client.AcknowledgeProduct(ctx, "package", "productID", "purchaseToken", "")
_, actual := client.VerifyProduct(ctx, "package", "productID", "purchaseToken")
if err == nil || err.Error() != expected {
t.Errorf("got %v", err)
if !reflect.DeepEqual(actual, expected) {
t.Errorf("got %v\nwant %v", actual, expected)
}
// 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 actual == nil || actual.Error() != expectedStr {
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 {
t.Errorf("got %v\nwant %v", actual, expectedStr)
}
@@ -167,13 +171,21 @@ 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 actual == nil || actual.Error() != expectedStr {
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 {
t.Errorf("got %v\nwant %v", actual, expectedStr)
}
@@ -182,13 +194,21 @@ 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 actual == nil || actual.Error() != expectedStr {
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 {
t.Errorf("got %v\nwant %v", actual, expectedStr)
}