candy_check 0.1.0.pre → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.rubocop.yml +23 -0
- data/.ruby-version +1 -1
- data/.travis.yml +7 -8
- data/Guardfile +42 -0
- data/MIGRATION_GUIDE_0_2_0.md +141 -0
- data/README.md +86 -26
- data/Rakefile +1 -1
- data/candy_check.gemspec +33 -25
- data/lib/candy_check/app_store/receipt_collection.rb +5 -3
- data/lib/candy_check/app_store/subscription_verification.rb +25 -1
- data/lib/candy_check/app_store/verification.rb +1 -1
- data/lib/candy_check/app_store/verifier.rb +11 -11
- data/lib/candy_check/cli/app.rb +16 -33
- data/lib/candy_check/cli/commands/play_store.rb +12 -13
- data/lib/candy_check/play_store.rb +20 -10
- data/lib/candy_check/play_store/acknowledger.rb +19 -0
- data/lib/candy_check/play_store/android_publisher_service.rb +6 -0
- data/lib/candy_check/play_store/product_acknowledgements/acknowledgement.rb +45 -0
- data/lib/candy_check/play_store/product_acknowledgements/response.rb +24 -0
- data/lib/candy_check/play_store/product_purchases/product_purchase.rb +90 -0
- data/lib/candy_check/play_store/product_purchases/product_verification.rb +53 -0
- data/lib/candy_check/play_store/subscription_purchases/subscription_purchase.rb +154 -0
- data/lib/candy_check/play_store/subscription_purchases/subscription_verification.rb +55 -0
- data/lib/candy_check/play_store/verification_failure.rb +8 -6
- data/lib/candy_check/play_store/verifier.rb +24 -49
- data/lib/candy_check/utils/config.rb +5 -3
- data/lib/candy_check/version.rb +1 -1
- data/spec/app_store/receipt_collection_spec.rb +33 -0
- data/spec/app_store/subscription_verification_spec.rb +35 -2
- data/spec/app_store/verifier_spec.rb +24 -5
- data/spec/candy_check_spec.rb +2 -2
- data/spec/cli/commands/play_store_spec.rb +10 -43
- data/spec/fixtures/play_store/random_dummy_key.json +12 -0
- data/spec/fixtures/vcr_cassettes/play_store/product_acknowledgements/acknowledged.yml +105 -0
- data/spec/fixtures/vcr_cassettes/play_store/product_acknowledgements/already_acknowledged.yml +124 -0
- data/spec/fixtures/vcr_cassettes/play_store/product_acknowledgements/refunded.yml +122 -0
- data/spec/fixtures/vcr_cassettes/play_store/product_purchases/permission_denied.yml +196 -0
- data/spec/fixtures/vcr_cassettes/play_store/product_purchases/response_with_empty_body.yml +183 -0
- data/spec/fixtures/vcr_cassettes/play_store/product_purchases/valid_but_not_consumed.yml +122 -0
- data/spec/fixtures/vcr_cassettes/play_store/subscription_purchases/permission_denied.yml +196 -0
- data/spec/fixtures/vcr_cassettes/play_store/subscription_purchases/valid_but_expired.yml +127 -0
- data/spec/play_store/acknowledger_spec.rb +48 -0
- data/spec/play_store/product_acknowledgements/acknowledgement_spec.rb +54 -0
- data/spec/play_store/product_acknowledgements/response_spec.rb +66 -0
- data/spec/play_store/product_purchases/product_purchase_spec.rb +110 -0
- data/spec/play_store/product_purchases/product_verification_spec.rb +49 -0
- data/spec/play_store/subscription_purchases/subscription_purchase_spec.rb +237 -0
- data/spec/play_store/subscription_purchases/subscription_verification_spec.rb +65 -0
- data/spec/play_store/verification_failure_spec.rb +18 -18
- data/spec/play_store/verifier_spec.rb +32 -96
- data/spec/spec_helper.rb +32 -10
- metadata +175 -75
- data/lib/candy_check/play_store/client.rb +0 -126
- data/lib/candy_check/play_store/config.rb +0 -51
- data/lib/candy_check/play_store/discovery_repository.rb +0 -33
- data/lib/candy_check/play_store/receipt.rb +0 -81
- data/lib/candy_check/play_store/subscription.rb +0 -138
- data/lib/candy_check/play_store/subscription_verification.rb +0 -30
- data/lib/candy_check/play_store/verification.rb +0 -48
- data/spec/fixtures/api_cache.dump +0 -1
- data/spec/fixtures/play_store/api_cache.dump +0 -1
- data/spec/fixtures/play_store/auth_failure.txt +0 -18
- data/spec/fixtures/play_store/auth_success.txt +0 -20
- data/spec/fixtures/play_store/discovery.txt +0 -2841
- data/spec/fixtures/play_store/dummy.p12 +0 -0
- data/spec/fixtures/play_store/empty.txt +0 -17
- data/spec/fixtures/play_store/products_failure.txt +0 -29
- data/spec/fixtures/play_store/products_success.txt +0 -22
- data/spec/play_store/client_spec.rb +0 -125
- data/spec/play_store/config_spec.rb +0 -96
- data/spec/play_store/discovery_respository_spec.rb +0 -31
- data/spec/play_store/receipt_spec.rb +0 -88
- data/spec/play_store/subscription_spec.rb +0 -138
- data/spec/play_store/subscription_verification_spec.rb +0 -98
- data/spec/play_store/verification_spec.rb +0 -82
@@ -1,126 +0,0 @@
|
|
1
|
-
module CandyCheck
|
2
|
-
module PlayStore
|
3
|
-
# A client which uses the official Google API SDK to authenticate
|
4
|
-
# and request product information from Google's API.
|
5
|
-
#
|
6
|
-
# @example Usage
|
7
|
-
# config = ClientConfig.new({...})
|
8
|
-
# client = Client.new(config)
|
9
|
-
# client.boot! # a single time
|
10
|
-
# client.verify('my.bundle', 'product_1', 'a-very-long-secure-token')
|
11
|
-
# # ... multiple calls from now on
|
12
|
-
# client.verify('my.bundle', 'product_1', 'another-long-token')
|
13
|
-
class Client
|
14
|
-
# Error thrown if the discovery of the API wasn't successful
|
15
|
-
class DiscoveryError < RuntimeError; end
|
16
|
-
|
17
|
-
# API endpoint
|
18
|
-
API_URL = 'https://accounts.google.com/o/oauth2/token'.freeze
|
19
|
-
# API scope for Android services
|
20
|
-
API_SCOPE = 'https://www.googleapis.com/auth/androidpublisher'.freeze
|
21
|
-
# API discovery namespace
|
22
|
-
API_DISCOVER = 'androidpublisher'.freeze
|
23
|
-
# API version
|
24
|
-
API_VERSION = 'v2'.freeze
|
25
|
-
|
26
|
-
# Initializes a client using a configuration.
|
27
|
-
# @param config [ClientConfig]
|
28
|
-
def initialize(config)
|
29
|
-
@config = config
|
30
|
-
end
|
31
|
-
|
32
|
-
# Boots a client by discovering the API's services and then authorizes
|
33
|
-
# by fetching an access token.
|
34
|
-
# If the config has a cache_file the client tries to load discovery
|
35
|
-
def boot!
|
36
|
-
@api_client = Google::APIClient.new(
|
37
|
-
application_name: config.application_name,
|
38
|
-
application_version: config.application_version
|
39
|
-
)
|
40
|
-
discover!
|
41
|
-
authorize!
|
42
|
-
end
|
43
|
-
|
44
|
-
# Calls the remote API to load the product information for a specific
|
45
|
-
# combination of parameter which should be loaded from the client.
|
46
|
-
# @param package [String] the app's package name
|
47
|
-
# @param product_id [String] the app's item id
|
48
|
-
# @param token [String] the purchase token
|
49
|
-
# @return [Hash] result of the API call
|
50
|
-
def verify(package, product_id, token)
|
51
|
-
parameters = {
|
52
|
-
'packageName' => package,
|
53
|
-
'productId' => product_id,
|
54
|
-
'token' => token
|
55
|
-
}
|
56
|
-
execute(parameters, rpc.purchases.products.get)
|
57
|
-
end
|
58
|
-
|
59
|
-
# Calls the remote API to load the product information for a specific
|
60
|
-
# combination of parameter which should be loaded from the client.
|
61
|
-
# @param package [String] the app's package name
|
62
|
-
# @param subscription_id [String] the app's item id
|
63
|
-
# @param token [String] the purchase token
|
64
|
-
# @return [Hash] result of the API call
|
65
|
-
def verify_subscription(package, subscription_id, token)
|
66
|
-
parameters = {
|
67
|
-
'packageName' => package,
|
68
|
-
'subscriptionId' => subscription_id,
|
69
|
-
'token' => token
|
70
|
-
}
|
71
|
-
execute(parameters, rpc.purchases.subscriptions.get)
|
72
|
-
end
|
73
|
-
|
74
|
-
private
|
75
|
-
|
76
|
-
attr_reader :config, :api_client, :rpc
|
77
|
-
|
78
|
-
# Execute api call through the API Client's HTTP command class
|
79
|
-
# @param parameters [hash] the parameters to send to the command
|
80
|
-
# @param api_method [Method] which api method to call
|
81
|
-
# @return [hash] the data response, as a hash
|
82
|
-
def execute(parameters, api_method)
|
83
|
-
api_client.execute(
|
84
|
-
api_method: api_method,
|
85
|
-
parameters: parameters
|
86
|
-
).data.to_hash
|
87
|
-
end
|
88
|
-
|
89
|
-
def discover!
|
90
|
-
@rpc = load_discover_dump || request_discover
|
91
|
-
validate_rpc!
|
92
|
-
write_discover_dump
|
93
|
-
end
|
94
|
-
|
95
|
-
def request_discover
|
96
|
-
api_client.discovered_api(API_DISCOVER, API_VERSION)
|
97
|
-
end
|
98
|
-
|
99
|
-
def authorize!
|
100
|
-
api_client.authorization = Signet::OAuth2::Client.new(
|
101
|
-
token_credential_uri: API_URL,
|
102
|
-
audience: API_URL,
|
103
|
-
scope: API_SCOPE,
|
104
|
-
issuer: config.issuer,
|
105
|
-
signing_key: config.api_key
|
106
|
-
)
|
107
|
-
api_client.authorization.fetch_access_token!
|
108
|
-
end
|
109
|
-
|
110
|
-
def validate_rpc!
|
111
|
-
return if rpc.purchases.products.get
|
112
|
-
raise DiscoveryError, 'Unable to get the API discovery'
|
113
|
-
rescue NoMethodError
|
114
|
-
raise DiscoveryError, 'Unable to get the API discovery'
|
115
|
-
end
|
116
|
-
|
117
|
-
def load_discover_dump
|
118
|
-
DiscoveryRepository.new(config.cache_file).load
|
119
|
-
end
|
120
|
-
|
121
|
-
def write_discover_dump
|
122
|
-
DiscoveryRepository.new(config.cache_file).save(rpc)
|
123
|
-
end
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|
@@ -1,51 +0,0 @@
|
|
1
|
-
module CandyCheck
|
2
|
-
module PlayStore
|
3
|
-
# Configure the usage of the official Google API SDK client
|
4
|
-
class Config < Utils::Config
|
5
|
-
# @return [String] your application name
|
6
|
-
attr_reader :application_name
|
7
|
-
# @return [String] your application's version
|
8
|
-
attr_reader :application_version
|
9
|
-
# @return [String] an optional file to cache the discovery API result
|
10
|
-
attr_reader :cache_file
|
11
|
-
# @return [String] your issuer's service account e-mail
|
12
|
-
attr_reader :issuer
|
13
|
-
# @return [String] the path to your local *.p12 certificate file
|
14
|
-
attr_reader :key_file
|
15
|
-
# @return [String] the secret to load your certificate file
|
16
|
-
attr_reader :key_secret
|
17
|
-
|
18
|
-
# Initializes a new configuration from a hash
|
19
|
-
# @param attributes [Hash]
|
20
|
-
# @example Initialize with a discovery cache file
|
21
|
-
# ClientConfig.new(
|
22
|
-
# application_name: 'YourApplication',
|
23
|
-
# application_version: '1.0',
|
24
|
-
# cache_file: 'tmp/google_api_cache',
|
25
|
-
# issuer: 'abcdefg@developer.gserviceaccount.com',
|
26
|
-
# key_file: 'local/google.p12',
|
27
|
-
# key_secret: 'notasecret'
|
28
|
-
# )
|
29
|
-
def initialize(attributes)
|
30
|
-
super
|
31
|
-
end
|
32
|
-
|
33
|
-
# @return [String] the decrypted API key from Google
|
34
|
-
def api_key
|
35
|
-
@api_key ||= begin
|
36
|
-
Google::APIClient::KeyUtils.load_from_pkcs12(key_file, key_secret)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
private
|
41
|
-
|
42
|
-
def validate!
|
43
|
-
validates_presence(:application_name)
|
44
|
-
validates_presence(:application_version)
|
45
|
-
validates_presence(:issuer)
|
46
|
-
validates_presence(:key_file)
|
47
|
-
validates_presence(:key_secret)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
@@ -1,33 +0,0 @@
|
|
1
|
-
module CandyCheck
|
2
|
-
module PlayStore
|
3
|
-
# A file-based repository to cache a local copy of the Google API
|
4
|
-
# discovery as suggested by Google.
|
5
|
-
# @see https://github.com/google/google-api-ruby-client
|
6
|
-
class DiscoveryRepository
|
7
|
-
# Create a new instance bound to a single file path
|
8
|
-
# @param file_path [String] to save and load the cached copy
|
9
|
-
def initialize(file_path)
|
10
|
-
@file_path = file_path
|
11
|
-
end
|
12
|
-
|
13
|
-
# Tries to load a cached copy of the discovery API. Me be nil if
|
14
|
-
# no cached version is available
|
15
|
-
# @return [Google::APIClient::API]
|
16
|
-
def load
|
17
|
-
return unless @file_path && File.exist?(@file_path)
|
18
|
-
File.open(@file_path, 'rb') do |file|
|
19
|
-
return Marshal.load(file)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
# Tries to save a local copy of the discovery API.
|
24
|
-
# @param discovery [Google::APIClient::API]
|
25
|
-
def save(discovery)
|
26
|
-
return unless @file_path && discovery
|
27
|
-
File.open(@file_path, 'wb') do |file|
|
28
|
-
Marshal.dump(discovery, file)
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
@@ -1,81 +0,0 @@
|
|
1
|
-
module CandyCheck
|
2
|
-
module PlayStore
|
3
|
-
# Describes a successful response from the Google verification server
|
4
|
-
class Receipt
|
5
|
-
include Utils::AttributeReader
|
6
|
-
|
7
|
-
# @return [Hash] the raw attributes returned from the server
|
8
|
-
attr_reader :attributes
|
9
|
-
|
10
|
-
# Purchased product (0 is purchased, don't ask me why)
|
11
|
-
# @see https://developers.google.com/android-publisher/api-ref/purchases/products
|
12
|
-
PURCHASE_STATE_PURCHASED = 0
|
13
|
-
|
14
|
-
# A consumed product
|
15
|
-
CONSUMPTION_STATE_CONSUMED = 1
|
16
|
-
|
17
|
-
# Initializes a new instance which bases on a JSON result
|
18
|
-
# from Google API servers
|
19
|
-
# @param attributes [Hash]
|
20
|
-
def initialize(attributes)
|
21
|
-
@attributes = attributes
|
22
|
-
end
|
23
|
-
|
24
|
-
# A product may be purchased or canceled. Ensure a receipt
|
25
|
-
# is valid before granting some candy
|
26
|
-
# @return [Boolean]
|
27
|
-
def valid?
|
28
|
-
purchase_state == PURCHASE_STATE_PURCHASED
|
29
|
-
end
|
30
|
-
|
31
|
-
# A purchased product may already be consumed. In this case you
|
32
|
-
# should grant candy even if it's valid.
|
33
|
-
# @return [Boolean]
|
34
|
-
def consumed?
|
35
|
-
consumption_state == CONSUMPTION_STATE_CONSUMED
|
36
|
-
end
|
37
|
-
|
38
|
-
# The purchase state of the order. Possible values are:
|
39
|
-
# * 0: Purchased
|
40
|
-
# * 1: Cancelled
|
41
|
-
# @return [Fixnum]
|
42
|
-
def purchase_state
|
43
|
-
read_integer('purchaseState')
|
44
|
-
end
|
45
|
-
|
46
|
-
# The consumption state of the inapp product. Possible values are:
|
47
|
-
# * 0: Yet to be consumed
|
48
|
-
# * 1: Consumed
|
49
|
-
# @return [Fixnum]
|
50
|
-
def consumption_state
|
51
|
-
read_integer('consumptionState')
|
52
|
-
end
|
53
|
-
|
54
|
-
# The developer payload which was used when buying the product
|
55
|
-
# @return [String]
|
56
|
-
def developer_payload
|
57
|
-
read('developerPayload')
|
58
|
-
end
|
59
|
-
|
60
|
-
# This kind represents an inappPurchase object in the androidpublisher
|
61
|
-
# service.
|
62
|
-
# @return [String]
|
63
|
-
def kind
|
64
|
-
read('kind')
|
65
|
-
end
|
66
|
-
|
67
|
-
# The time the product was purchased, in milliseconds since the
|
68
|
-
# epoch (Jan 1, 1970)
|
69
|
-
# @return [Fixnum]
|
70
|
-
def purchase_time_millis
|
71
|
-
read_integer('purchaseTimeMillis')
|
72
|
-
end
|
73
|
-
|
74
|
-
# The date and time the product was purchased
|
75
|
-
# @return [DateTime]
|
76
|
-
def purchased_at
|
77
|
-
read_datetime_from_millis('purchaseTimeMillis')
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
@@ -1,138 +0,0 @@
|
|
1
|
-
module CandyCheck
|
2
|
-
module PlayStore
|
3
|
-
# Describes a succeful subscription validation
|
4
|
-
class Subscription
|
5
|
-
include Utils::AttributeReader
|
6
|
-
|
7
|
-
# @return [Hash] the raw attributes returned from the server
|
8
|
-
attr_reader :attributes
|
9
|
-
|
10
|
-
# The payment of the subscription is pending (paymentState)
|
11
|
-
PAYMENT_PENDING = 0
|
12
|
-
# The payment of the subscript is received (paymentState)
|
13
|
-
PAYMENT_RECEIVED = 1
|
14
|
-
# The subscription was canceled by the user (cancelReason)
|
15
|
-
PAYMENT_CANCELED = 0
|
16
|
-
# The payment failed during processing (cancelReason)
|
17
|
-
PAYMENT_FAILED = 1
|
18
|
-
|
19
|
-
# Initializes a new instance which bases on a JSON result
|
20
|
-
# from Google's servers
|
21
|
-
# @param attributes [Hash]
|
22
|
-
def initialize(attributes)
|
23
|
-
@attributes = attributes
|
24
|
-
end
|
25
|
-
|
26
|
-
# Check if the expiration date is passed
|
27
|
-
# @return [bool]
|
28
|
-
def expired?
|
29
|
-
overdue_days > 0
|
30
|
-
end
|
31
|
-
|
32
|
-
# Check if in trial. This is actually not given by Google, but we assume
|
33
|
-
# that it is a trial going on if the paid amount is 0 and
|
34
|
-
# renewal is activated.
|
35
|
-
# @return [bool]
|
36
|
-
def trial?
|
37
|
-
price_is_zero = price_amount_micros == 0
|
38
|
-
price_is_zero && payment_received?
|
39
|
-
end
|
40
|
-
|
41
|
-
# see if payment is ok
|
42
|
-
# @return [bool]
|
43
|
-
def payment_received?
|
44
|
-
payment_state == PAYMENT_RECEIVED
|
45
|
-
end
|
46
|
-
|
47
|
-
# see if payment is pending
|
48
|
-
# @return [bool]
|
49
|
-
def payment_pending?
|
50
|
-
payment_state == PAYMENT_PENDING
|
51
|
-
end
|
52
|
-
|
53
|
-
# see if payment has failed according to Google
|
54
|
-
# @return [bool]
|
55
|
-
def payment_failed?
|
56
|
-
cancel_reason == PAYMENT_FAILED
|
57
|
-
end
|
58
|
-
|
59
|
-
# see if this the user has canceled its subscription
|
60
|
-
# @return [bool]
|
61
|
-
def canceled_by_user?
|
62
|
-
cancel_reason == PAYMENT_CANCELED
|
63
|
-
end
|
64
|
-
|
65
|
-
# Get number of overdue days. If this is negative, it is not overdue.
|
66
|
-
# @return [Integer]
|
67
|
-
def overdue_days
|
68
|
-
(Date.today - expires_at.to_date).to_i
|
69
|
-
end
|
70
|
-
|
71
|
-
# Get the auto renewal status as given by Google
|
72
|
-
# @return [bool] true if renewing automatically, false otherwise
|
73
|
-
def auto_renewing?
|
74
|
-
read_bool('autoRenewing')
|
75
|
-
end
|
76
|
-
|
77
|
-
# Get the payment state as given by Google
|
78
|
-
# @return [Integer]
|
79
|
-
def payment_state
|
80
|
-
read_integer('paymentState')
|
81
|
-
end
|
82
|
-
|
83
|
-
# Get the price amount for the subscription in micros in the payd currency
|
84
|
-
# @return [Integer]
|
85
|
-
def price_amount_micros
|
86
|
-
read_integer('priceAmountMicros')
|
87
|
-
end
|
88
|
-
|
89
|
-
# Get the cancel reason, as given by Google
|
90
|
-
# @return [Integer]
|
91
|
-
def cancel_reason
|
92
|
-
read_integer('cancelReason')
|
93
|
-
end
|
94
|
-
|
95
|
-
# Get the kind of subscription as stored in the android publisher service
|
96
|
-
# @return [String]
|
97
|
-
def kind
|
98
|
-
read('kind')
|
99
|
-
end
|
100
|
-
|
101
|
-
# Get developer-specified supplemental information about the order
|
102
|
-
# @return [String]
|
103
|
-
def developer_payload
|
104
|
-
read('developerPayload')
|
105
|
-
end
|
106
|
-
|
107
|
-
# Get the currency code in ISO 4217 format, e.g. "GBP" for British pounds
|
108
|
-
# @return [String]
|
109
|
-
def price_currency_code
|
110
|
-
read('priceCurrencyCode')
|
111
|
-
end
|
112
|
-
|
113
|
-
# Get start time for subscription in milliseconds since Epoch
|
114
|
-
# @return [Integer]
|
115
|
-
def start_time_millis
|
116
|
-
read_integer('startTimeMillis')
|
117
|
-
end
|
118
|
-
|
119
|
-
# Get expiry time for subscription in milliseconds since Epoch
|
120
|
-
# @return [Integer]
|
121
|
-
def expiry_time_millis
|
122
|
-
read_integer('expiryTimeMillis')
|
123
|
-
end
|
124
|
-
|
125
|
-
# Get start time in UTC
|
126
|
-
# @return [DateTime]
|
127
|
-
def starts_at
|
128
|
-
read_datetime_from_millis('startTimeMillis')
|
129
|
-
end
|
130
|
-
|
131
|
-
# Get expiration time in UTC
|
132
|
-
# @return [DateTime]
|
133
|
-
def expires_at
|
134
|
-
read_datetime_from_millis('expiryTimeMillis')
|
135
|
-
end
|
136
|
-
end
|
137
|
-
end
|
138
|
-
end
|
@@ -1,30 +0,0 @@
|
|
1
|
-
module CandyCheck
|
2
|
-
module PlayStore
|
3
|
-
# Verifies a purchase token against the Google API
|
4
|
-
# The call return either an {Receipt} or an {VerificationFailure}
|
5
|
-
class SubscriptionVerification < Verification
|
6
|
-
# Performs the verification against the remote server
|
7
|
-
# @return [Subscription] if successful
|
8
|
-
# @return [VerificationFailure] otherwise
|
9
|
-
def call!
|
10
|
-
verify!
|
11
|
-
if valid?
|
12
|
-
Subscription.new(@response)
|
13
|
-
else
|
14
|
-
VerificationFailure.new(@response['error'])
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
private
|
19
|
-
|
20
|
-
def valid?
|
21
|
-
ok_kind = @response['kind'] == 'androidpublisher#subscriptionPurchase'
|
22
|
-
@response && @response['expiryTimeMillis'] && ok_kind
|
23
|
-
end
|
24
|
-
|
25
|
-
def verify!
|
26
|
-
@response = @client.verify_subscription(package, product_id, token)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|