candy_check 0.0.5 → 0.1.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -2
- data/.ruby-version +1 -1
- data/.travis.yml +2 -0
- data/README.md +18 -2
- data/candy_check.gemspec +5 -5
- data/lib/candy_check/app_store.rb +2 -0
- data/lib/candy_check/app_store/receipt.rb +12 -0
- data/lib/candy_check/app_store/receipt_collection.rb +41 -0
- data/lib/candy_check/app_store/subscription_verification.rb +26 -0
- data/lib/candy_check/app_store/verifier.rb +18 -3
- data/lib/candy_check/play_store.rb +2 -0
- data/lib/candy_check/play_store/client.rb +36 -12
- data/lib/candy_check/play_store/subscription.rb +138 -0
- data/lib/candy_check/play_store/subscription_verification.rb +30 -0
- data/lib/candy_check/play_store/verifier.rb +20 -2
- data/lib/candy_check/utils/attribute_reader.rb +8 -0
- data/lib/candy_check/version.rb +1 -1
- data/spec/app_store/receipt_collection_spec.rb +65 -0
- data/spec/app_store/receipt_spec.rb +12 -1
- data/spec/app_store/subscription_verification_spec.rb +78 -0
- data/spec/app_store/verification_spec.rb +0 -10
- data/spec/app_store/verifier_spec.rb +33 -0
- data/spec/play_store/client_spec.rb +21 -0
- data/spec/play_store/subscription_spec.rb +138 -0
- data/spec/play_store/subscription_verification_spec.rb +98 -0
- data/spec/play_store/verifier_spec.rb +16 -0
- metadata +27 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 45e40e19795d3d805ea61453fe6bea1d5e94ec3a
|
4
|
+
data.tar.gz: 9ba798923e9c36b324aa93c860f6ea75687e56bc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f6e4a4bd35ab65c5ad309052f8b27408b3bd570701a39674f9b6120900242ebe2199bf5814788a8fa9572774a8686cc3b57908eeda35223a5c7786b4e921181e
|
7
|
+
data.tar.gz: 8f3e4cd0adff6af8067667294fb439e05a840e69c9b24d73c0af0a607d348d4a8ffcbe3392e469fd2fab36e26155241739936ae91a8c1509f32595f7a1bf299d
|
data/.rubocop.yml
CHANGED
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.1
|
1
|
+
2.3.1
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -18,8 +18,7 @@ gem install candy_check
|
|
18
18
|
|
19
19
|
## Introduction
|
20
20
|
|
21
|
-
This gem tries to simplify the process of server-side in-app purchase validation for Apple's AppStore and
|
22
|
-
Google's PlayStore.
|
21
|
+
This gem tries to simplify the process of server-side in-app purchase and subscription validation for Apple's AppStore and Google's PlayStore.
|
23
22
|
|
24
23
|
### AppStore
|
25
24
|
|
@@ -75,6 +74,15 @@ verifier.verify(your_receipt_data, your_secret)
|
|
75
74
|
|
76
75
|
Please see the class documenations [`CandyCheck::AppStore::Receipt`](http://www.rubydoc.info/github/jnbt/candy_check/master/CandyCheck/AppStore/Receipt) and [`CandyCheck::AppStore::VerificationFailure`](http://www.rubydoc.info/github/jnbt/candy_check/master/CandyCheck/AppStore/VerificationFailure) for further details about the responses.
|
77
76
|
|
77
|
+
For **subscription verification**, Apple also returns a list of the user's purchases. Essentially, this is a collection of receipts. To verify a subscription, do the following:
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
# ... create your verifier as above
|
81
|
+
verifier.verify_subscription(your_receipt_data, your_secret) # => ReceiptCollection or VerificationFailure
|
82
|
+
```
|
83
|
+
|
84
|
+
Please see the class documentation for [`CandyCheck::AppStore::ReceiptCollection`](http://www.rubydoc.info/github/jnbt/candy_check/master/CandyCheck/AppStore/ReceiptCollection) for further details.
|
85
|
+
|
78
86
|
### PlayStore
|
79
87
|
|
80
88
|
First initialize and **boot** a verifier instance for your application. This loads the API discovery and
|
@@ -102,6 +110,14 @@ verifier.verify(package, product_id, token) # => Receipt or VerificationFailure
|
|
102
110
|
|
103
111
|
Please see the class documenations [`CandyCheck::PlayStore::Receipt`](http://www.rubydoc.info/github/jnbt/candy_check/master/CandyCheck/PlayStore/Receipt) and [`CandyCheck::PlayStore::VerificationFailure`](http://www.rubydoc.info/github/jnbt/candy_check/master/CandyCheck/PlayStore/VerificationFailure) for further details about the responses.
|
104
112
|
|
113
|
+
In order to **verify a subscription** from the Play Store, do the following:
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
verifier.verify_subscription(package, subscription_id, token) # => Subscription or VerificationFailure
|
117
|
+
```
|
118
|
+
|
119
|
+
Please see documenation for [`CandyCheck::PlayStore::Subscription`](http://www.rubydoc.info/github/jnbt/candy_check/master/CandyCheck/PlayStore/Subscription) for further details.
|
120
|
+
|
105
121
|
## CLI
|
106
122
|
|
107
123
|
This gem ships with an executable to verify in-app purchases directly from your terminal:
|
data/candy_check.gemspec
CHANGED
@@ -23,12 +23,12 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.add_dependency 'google-api-client', '~> 0.8.6'
|
24
24
|
spec.add_dependency 'thor', '~> 0.19'
|
25
25
|
|
26
|
-
spec.add_development_dependency 'rubocop', '~> 0.
|
26
|
+
spec.add_development_dependency 'rubocop', '~> 0.39'
|
27
27
|
spec.add_development_dependency 'inch', '~> 0.5'
|
28
28
|
spec.add_development_dependency 'bundler', '~> 1.7'
|
29
|
-
spec.add_development_dependency 'rake', '~>
|
30
|
-
spec.add_development_dependency 'coveralls', '~> 0.
|
31
|
-
spec.add_development_dependency 'minitest', '~> 5.
|
29
|
+
spec.add_development_dependency 'rake', '~> 11.1'
|
30
|
+
spec.add_development_dependency 'coveralls', '~> 0.8'
|
31
|
+
spec.add_development_dependency 'minitest', '~> 5.9'
|
32
32
|
spec.add_development_dependency 'minitest-around', '~> 0.3'
|
33
|
-
spec.add_development_dependency 'webmock', '~> 1
|
33
|
+
spec.add_development_dependency 'webmock', '~> 2.1'
|
34
34
|
end
|
@@ -1,7 +1,9 @@
|
|
1
1
|
require 'candy_check/app_store/client'
|
2
2
|
require 'candy_check/app_store/config'
|
3
3
|
require 'candy_check/app_store/receipt'
|
4
|
+
require 'candy_check/app_store/receipt_collection'
|
4
5
|
require 'candy_check/app_store/verification'
|
6
|
+
require 'candy_check/app_store/subscription_verification'
|
5
7
|
require 'candy_check/app_store/verification_failure'
|
6
8
|
require 'candy_check/app_store/verifier'
|
7
9
|
|
@@ -84,6 +84,18 @@ module CandyCheck
|
|
84
84
|
def cancellation_date
|
85
85
|
read_datetime_from_string('cancellation_date')
|
86
86
|
end
|
87
|
+
|
88
|
+
# The date of a subscription's expiration
|
89
|
+
# @return [DateTime]
|
90
|
+
def expires_date
|
91
|
+
read_datetime_from_string('expires_date')
|
92
|
+
end
|
93
|
+
|
94
|
+
# rubocop:disable PredicateName
|
95
|
+
def is_trial_period
|
96
|
+
# rubocop:enable PredicateName
|
97
|
+
read_bool('is_trial_period')
|
98
|
+
end
|
87
99
|
end
|
88
100
|
end
|
89
101
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module CandyCheck
|
2
|
+
module AppStore
|
3
|
+
# Store multiple {Receipt}s in order to perform collective operation on them
|
4
|
+
class ReceiptCollection
|
5
|
+
# Multiple receipts as in verfication response
|
6
|
+
# @return [Array<Receipt>]
|
7
|
+
attr_reader :receipts
|
8
|
+
|
9
|
+
# Initializes a new instance which bases on a JSON result
|
10
|
+
# from Apple's verification server
|
11
|
+
# @param attributes [Array<Hash>]
|
12
|
+
def initialize(attributes)
|
13
|
+
@receipts = attributes.map { |r| Receipt.new(r) }
|
14
|
+
end
|
15
|
+
|
16
|
+
# Check if the latest expiration date is passed
|
17
|
+
# @return [bool]
|
18
|
+
def expired?
|
19
|
+
overdue_days > 0
|
20
|
+
end
|
21
|
+
|
22
|
+
# Check if in trial
|
23
|
+
# @return [bool]
|
24
|
+
def trial?
|
25
|
+
@receipts.last.is_trial_period
|
26
|
+
end
|
27
|
+
|
28
|
+
# Get latest expiration date
|
29
|
+
# @return [DateTime]
|
30
|
+
def expires_at
|
31
|
+
@receipts.last.expires_date
|
32
|
+
end
|
33
|
+
|
34
|
+
# Get number of overdue days. If this is negative, it is not overdue.
|
35
|
+
# @return [Integer]
|
36
|
+
def overdue_days
|
37
|
+
(Date.today - expires_at.to_date).to_i
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module CandyCheck
|
2
|
+
module AppStore
|
3
|
+
# Verifies a latest_receipt_info block against a verification server.
|
4
|
+
# The call return either an {ReceiptCollection} or a {VerificationFailure}
|
5
|
+
class SubscriptionVerification < CandyCheck::AppStore::Verification
|
6
|
+
# Performs the verification against the remote server
|
7
|
+
# @return [ReceiptCollection] if successful
|
8
|
+
# @return [VerificationFailure] otherwise
|
9
|
+
def call!
|
10
|
+
verify!
|
11
|
+
if valid?
|
12
|
+
ReceiptCollection.new(@response['latest_receipt_info'])
|
13
|
+
else
|
14
|
+
VerificationFailure.fetch(@response['status'])
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def valid?
|
21
|
+
status_is_ok = @response['status'] == STATUS_OK
|
22
|
+
@response && status_is_ok && @response['latest_receipt_info']
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -30,6 +30,23 @@ module CandyCheck
|
|
30
30
|
# @return [Receipt] if successful
|
31
31
|
# @return [VerificationFailure] otherwise
|
32
32
|
def verify(receipt_data, secret = nil)
|
33
|
+
@verifier = Verification
|
34
|
+
fetch_receipt_information(receipt_data, secret)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Calls a subscription verification for the given input
|
38
|
+
# @param receipt_data [String] the raw data to be verified
|
39
|
+
# @param secret [string] the optional shared secret
|
40
|
+
# @return [ReceiptCollection] if successful
|
41
|
+
# @return [Verification] otherwise
|
42
|
+
def verify_subscription(receipt_data, secret = nil)
|
43
|
+
@verifier = SubscriptionVerification
|
44
|
+
fetch_receipt_information(receipt_data, secret)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def fetch_receipt_information(receipt_data, secret = nil)
|
33
50
|
default_endpoint, opposite_endpoint = endpoints
|
34
51
|
result = call_for(default_endpoint, receipt_data, secret)
|
35
52
|
if should_retry?(result)
|
@@ -38,10 +55,8 @@ module CandyCheck
|
|
38
55
|
result
|
39
56
|
end
|
40
57
|
|
41
|
-
private
|
42
|
-
|
43
58
|
def call_for(endpoint_url, receipt_data, secret)
|
44
|
-
|
59
|
+
@verifier.new(endpoint_url, receipt_data, secret).call!
|
45
60
|
end
|
46
61
|
|
47
62
|
def should_retry?(result)
|
@@ -4,7 +4,9 @@ require 'candy_check/play_store/discovery_repository'
|
|
4
4
|
require 'candy_check/play_store/client'
|
5
5
|
require 'candy_check/play_store/config'
|
6
6
|
require 'candy_check/play_store/receipt'
|
7
|
+
require 'candy_check/play_store/subscription'
|
7
8
|
require 'candy_check/play_store/verification'
|
9
|
+
require 'candy_check/play_store/subscription_verification'
|
8
10
|
require 'candy_check/play_store/verification_failure'
|
9
11
|
require 'candy_check/play_store/verifier'
|
10
12
|
|
@@ -26,14 +26,14 @@ module CandyCheck
|
|
26
26
|
# Initializes a client using a configuration.
|
27
27
|
# @param config [ClientConfig]
|
28
28
|
def initialize(config)
|
29
|
-
|
29
|
+
@config = config
|
30
30
|
end
|
31
31
|
|
32
32
|
# Boots a client by discovering the API's services and then authorizes
|
33
33
|
# by fetching an access token.
|
34
34
|
# If the config has a cache_file the client tries to load discovery
|
35
35
|
def boot!
|
36
|
-
|
36
|
+
@api_client = Google::APIClient.new(
|
37
37
|
application_name: config.application_name,
|
38
38
|
application_version: config.application_version
|
39
39
|
)
|
@@ -48,22 +48,46 @@ module CandyCheck
|
|
48
48
|
# @param token [String] the purchase token
|
49
49
|
# @return [Hash] result of the API call
|
50
50
|
def verify(package, product_id, token)
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
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)
|
59
72
|
end
|
60
73
|
|
61
74
|
private
|
62
75
|
|
63
|
-
|
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
|
64
88
|
|
65
89
|
def discover!
|
66
|
-
|
90
|
+
@rpc = load_discover_dump || request_discover
|
67
91
|
validate_rpc!
|
68
92
|
write_discover_dump
|
69
93
|
end
|
@@ -0,0 +1,138 @@
|
|
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
|
@@ -0,0 +1,30 @@
|
|
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
|
@@ -19,7 +19,7 @@ module CandyCheck
|
|
19
19
|
|
20
20
|
# Boot the module
|
21
21
|
def boot!
|
22
|
-
boot_error('You\'re only allowed to boot the verifier once') if
|
22
|
+
boot_error('You\'re only allowed to boot the verifier once') if booted?
|
23
23
|
@client = Client.new(config)
|
24
24
|
@client.boot!
|
25
25
|
end
|
@@ -36,10 +36,28 @@ module CandyCheck
|
|
36
36
|
verification.call!
|
37
37
|
end
|
38
38
|
|
39
|
+
# Contacts the Google API and requests the product state
|
40
|
+
# @param package [String] to query
|
41
|
+
# @param subscription_id [String] to query
|
42
|
+
# @param token [String] to use for authentication
|
43
|
+
# @return [Receipt] if successful
|
44
|
+
# @return [VerificationFailure] otherwise
|
45
|
+
def verify_subscription(package, subscription_id, token)
|
46
|
+
check_boot!
|
47
|
+
v = SubscriptionVerification.new(
|
48
|
+
@client, package, subscription_id, token
|
49
|
+
)
|
50
|
+
v.call!
|
51
|
+
end
|
52
|
+
|
39
53
|
private
|
40
54
|
|
55
|
+
def booted?
|
56
|
+
instance_variable_defined?(:@client)
|
57
|
+
end
|
58
|
+
|
41
59
|
def check_boot!
|
42
|
-
return if
|
60
|
+
return if booted?
|
43
61
|
boot_error 'You need to boot the verifier service first: '\
|
44
62
|
'CandyCheck::PlayStore::Verifier#boot!'
|
45
63
|
end
|
@@ -18,6 +18,14 @@ module CandyCheck
|
|
18
18
|
(val = read(field)) && val.to_i
|
19
19
|
end
|
20
20
|
|
21
|
+
# @return [bool] if value is either 'true' or 'false'
|
22
|
+
# @return [nil] if value is not 'true'/'false'
|
23
|
+
def read_bool(field)
|
24
|
+
val = read(field).to_s
|
25
|
+
return nil unless %w(false true).include?(val)
|
26
|
+
val == 'true'
|
27
|
+
end
|
28
|
+
|
21
29
|
def read_datetime_from_string(field)
|
22
30
|
(val = read(field)) && DateTime.parse(val)
|
23
31
|
end
|
data/lib/candy_check/version.rb
CHANGED
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CandyCheck::AppStore::ReceiptCollection do
|
4
|
+
subject { CandyCheck::AppStore::ReceiptCollection.new(attributes) }
|
5
|
+
|
6
|
+
describe 'overdue subscription' do
|
7
|
+
let(:attributes) do
|
8
|
+
[{
|
9
|
+
'expires_date' => '2014-04-15 12:52:40 Etc/GMT',
|
10
|
+
'expires_date_pst' => '2014-04-15 05:52:40 America/Los_Angeles',
|
11
|
+
'is_trial_period' => 'false'
|
12
|
+
}, {
|
13
|
+
'expires_date' => '2015-04-15 12:52:40 Etc/GMT',
|
14
|
+
'expires_date_pst' => '2015-04-15 05:52:40 America/Los_Angeles',
|
15
|
+
'is_trial_period' => 'false'
|
16
|
+
}]
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'is expired' do
|
20
|
+
subject.expired?.must_be_true
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'is not a trial' do
|
24
|
+
subject.trial?.must_be_false
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'has positive overdue days' do
|
28
|
+
overdue = subject.overdue_days
|
29
|
+
overdue.must_be_instance_of Fixnum
|
30
|
+
assert overdue > 0
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'has a last expires date' do
|
34
|
+
expected = DateTime.new(2015, 4, 15, 12, 52, 40)
|
35
|
+
subject.expires_at.must_equal expected
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe 'unexpired trial subscription' do
|
40
|
+
two_days_from_now = DateTime.now + 2
|
41
|
+
|
42
|
+
let(:attributes) do
|
43
|
+
[{
|
44
|
+
'expires_date' => '2016-04-15 12:52:40 Etc/GMT',
|
45
|
+
'is_trial_period' => 'true'
|
46
|
+
}, {
|
47
|
+
'expires_date' =>
|
48
|
+
two_days_from_now.strftime('%Y-%m-%d %H:%M:%S Etc/GMT'),
|
49
|
+
'is_trial_period' => 'true'
|
50
|
+
}]
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'has not expired' do
|
54
|
+
subject.expired?.must_be_false
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'it is a trial' do
|
58
|
+
subject.trial?.must_be_true
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'expires in two days' do
|
62
|
+
subject.overdue_days.must_equal(-2)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -23,7 +23,9 @@ describe CandyCheck::AppStore::Receipt do
|
|
23
23
|
'purchase_date_pst' => '2015-01-09 03:40:46' \
|
24
24
|
' America/Los_Angeles',
|
25
25
|
'bid' => 'some.test.app',
|
26
|
-
'original_purchase_date_ms' => '1420717246868'
|
26
|
+
'original_purchase_date_ms' => '1420717246868',
|
27
|
+
'expires_date' => '2016-06-09 13:59:40 Etc/GMT',
|
28
|
+
'is_trial_period' => 'false'
|
27
29
|
}
|
28
30
|
end
|
29
31
|
|
@@ -77,6 +79,15 @@ describe CandyCheck::AppStore::Receipt do
|
|
77
79
|
it 'returns raw attributes' do
|
78
80
|
subject.attributes.must_be_same_as attributes
|
79
81
|
end
|
82
|
+
|
83
|
+
it 'returns the subscription expiration date' do
|
84
|
+
expected = DateTime.new(2016, 6, 9, 13, 59, 40)
|
85
|
+
subject.expires_date.must_equal expected
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'returns the trial status' do
|
89
|
+
subject.is_trial_period.must_be_false
|
90
|
+
end
|
80
91
|
end
|
81
92
|
|
82
93
|
describe 'valid transaction' do
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CandyCheck::AppStore::SubscriptionVerification do
|
4
|
+
subject do
|
5
|
+
CandyCheck::AppStore::SubscriptionVerification.new(endpoint, data, secret)
|
6
|
+
end
|
7
|
+
let(:endpoint) { 'https://some.endpoint' }
|
8
|
+
let(:data) { 'some_data' }
|
9
|
+
let(:secret) { 'some_secret' }
|
10
|
+
|
11
|
+
it 'returns a verification failure for status != 0' do
|
12
|
+
with_mocked_response('status' => 21_000) do |client, recorded|
|
13
|
+
result = subject.call!
|
14
|
+
client.receipt_data.must_equal data
|
15
|
+
client.secret.must_equal secret
|
16
|
+
|
17
|
+
recorded.first.must_equal [endpoint]
|
18
|
+
|
19
|
+
result.must_be_instance_of CandyCheck::AppStore::VerificationFailure
|
20
|
+
result.code.must_equal 21_000
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'returns a verification failure when receipt is missing' do
|
25
|
+
with_mocked_response({}) do |client, recorded|
|
26
|
+
result = subject.call!
|
27
|
+
client.receipt_data.must_equal data
|
28
|
+
client.secret.must_equal secret
|
29
|
+
|
30
|
+
recorded.first.must_equal [endpoint]
|
31
|
+
|
32
|
+
result.must_be_instance_of CandyCheck::AppStore::VerificationFailure
|
33
|
+
result.code.must_equal(-1)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'returns a collection of receipt when status is 0 and receipts exists' do
|
38
|
+
response = {
|
39
|
+
'status' => 0,
|
40
|
+
'latest_receipt_info' => [
|
41
|
+
{ 'item_id' => 'some_id' },
|
42
|
+
{ 'item_id' => 'some_other_id' }
|
43
|
+
]
|
44
|
+
}
|
45
|
+
with_mocked_response(response) do
|
46
|
+
result = subject.call!
|
47
|
+
result.must_be_instance_of CandyCheck::AppStore::ReceiptCollection
|
48
|
+
result.receipts.must_be_instance_of Array
|
49
|
+
last = result.receipts.last
|
50
|
+
last.must_be_instance_of CandyCheck::AppStore::Receipt
|
51
|
+
last.item_id.must_equal('some_other_id')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
DummyClient = Struct.new(:response) do
|
58
|
+
attr_reader :receipt_data, :secret
|
59
|
+
|
60
|
+
def verify(receipt_data, secret)
|
61
|
+
@receipt_data = receipt_data
|
62
|
+
@secret = secret
|
63
|
+
response
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def with_mocked_response(response)
|
68
|
+
recorded = []
|
69
|
+
dummy = DummyClient.new(response)
|
70
|
+
stub = proc do |*args|
|
71
|
+
recorded << args
|
72
|
+
dummy
|
73
|
+
end
|
74
|
+
CandyCheck::AppStore::Client.stub :new, stub do
|
75
|
+
yield dummy, recorded
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -43,16 +43,6 @@ describe CandyCheck::AppStore::Verification do
|
|
43
43
|
|
44
44
|
private
|
45
45
|
|
46
|
-
DummyClient = Struct.new(:response) do
|
47
|
-
attr_reader :receipt_data, :secret
|
48
|
-
|
49
|
-
def verify(receipt_data, secret)
|
50
|
-
@receipt_data = receipt_data
|
51
|
-
@secret = secret
|
52
|
-
response
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
46
|
def with_mocked_response(response)
|
57
47
|
recorded = []
|
58
48
|
dummy = DummyClient.new(response)
|
@@ -9,6 +9,7 @@ describe CandyCheck::AppStore::Verifier do
|
|
9
9
|
let(:data) { 'some_data' }
|
10
10
|
let(:secret) { 'some_secret' }
|
11
11
|
let(:receipt) { CandyCheck::AppStore::Receipt.new({}) }
|
12
|
+
let(:receipt_collection) { CandyCheck::AppStore::ReceiptCollection.new({}) }
|
12
13
|
let(:production_endpoint) do
|
13
14
|
'https://buy.itunes.apple.com/verifyReceipt'
|
14
15
|
end
|
@@ -80,6 +81,38 @@ describe CandyCheck::AppStore::Verifier do
|
|
80
81
|
end
|
81
82
|
end
|
82
83
|
|
84
|
+
describe 'subscription' do
|
85
|
+
let(:environment) { :production }
|
86
|
+
|
87
|
+
it 'uses production endpoint without retry on success' do
|
88
|
+
with_mocked_verifier(receipt_collection) do
|
89
|
+
subject.verify_subscription(
|
90
|
+
data, secret
|
91
|
+
).must_be_same_as receipt_collection
|
92
|
+
assert_recorded([production_endpoint, data, secret])
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'only uses production endpoint for normal failures' do
|
97
|
+
failure = get_failure(21_000)
|
98
|
+
with_mocked_verifier(failure) do
|
99
|
+
subject.verify_subscription(data, secret).must_be_same_as failure
|
100
|
+
assert_recorded([production_endpoint, data, secret])
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'retries production endpoint for redirect error' do
|
105
|
+
failure = get_failure(21_007)
|
106
|
+
with_mocked_verifier(failure, receipt) do
|
107
|
+
subject.verify_subscription(data, secret).must_be_same_as receipt
|
108
|
+
assert_recorded(
|
109
|
+
[production_endpoint, data, secret],
|
110
|
+
[sandbox_endpoint, data, secret]
|
111
|
+
)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
83
116
|
private
|
84
117
|
|
85
118
|
def with_mocked_verifier(*results)
|
@@ -64,6 +64,20 @@ describe CandyCheck::PlayStore::Client do
|
|
64
64
|
result['error']['errors'].size.must_equal 1
|
65
65
|
end
|
66
66
|
|
67
|
+
it 'returns the products call result\'s data even if it is a failure' \
|
68
|
+
' when verifying subscription' do
|
69
|
+
bootup!
|
70
|
+
|
71
|
+
mock_subscriptions_request!('products_failure.txt')
|
72
|
+
result = subject.verify_subscription('the_package', 'the_id', 'the_token')
|
73
|
+
result.must_be_instance_of Hash
|
74
|
+
|
75
|
+
result['error']['code'].must_equal 401
|
76
|
+
result['error']['message'].must_equal 'The current user has insufficient' \
|
77
|
+
' permissions to perform the requested operation.'
|
78
|
+
result['error']['errors'].size.must_equal 1
|
79
|
+
end
|
80
|
+
|
67
81
|
it 'returns the products call result\'s data for a successful call' do
|
68
82
|
bootup!
|
69
83
|
mock_request!('products_success.txt')
|
@@ -101,4 +115,11 @@ describe CandyCheck::PlayStore::Client do
|
|
101
115
|
'applications/the_package/purchases/products/the_id/tokens/the_token')
|
102
116
|
.to_return(fixture_content('play_store', file))
|
103
117
|
end
|
118
|
+
|
119
|
+
def mock_subscriptions_request!(file)
|
120
|
+
stub_request(:get, 'https://www.googleapis.com/androidpublisher/v2/' \
|
121
|
+
'applications/the_package/purchases/subscriptions/' \
|
122
|
+
'the_id/tokens/the_token')
|
123
|
+
.to_return(fixture_content('play_store', file))
|
124
|
+
end
|
104
125
|
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CandyCheck::PlayStore::Subscription do
|
4
|
+
subject { CandyCheck::PlayStore::Subscription.new(attributes) }
|
5
|
+
|
6
|
+
describe 'expired and canceled subscription' do
|
7
|
+
let(:attributes) do
|
8
|
+
{
|
9
|
+
'kind' => 'androidpublisher#subscriptionPurchase',
|
10
|
+
'startTimeMillis' => '1459540113244',
|
11
|
+
'expiryTimeMillis' => '1462132088610',
|
12
|
+
'autoRenewing' => false,
|
13
|
+
'developerPayload' => 'payload that gets stored and returned',
|
14
|
+
'cancelReason' => 0,
|
15
|
+
'paymentState' => '1'
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'is expired?' do
|
20
|
+
subject.expired?.must_be_true
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'is canceled by user' do
|
24
|
+
subject.canceled_by_user?.must_be_true
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'returns the payment_state' do
|
28
|
+
subject.payment_state.must_equal 1
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'considers a payment as valid' do
|
32
|
+
subject.payment_received?.must_be_true
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'checks that auto renewal status is false' do
|
36
|
+
subject.auto_renewing?.must_be_false
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'returns the developer_payload' do
|
40
|
+
subject.developer_payload.must_equal \
|
41
|
+
'payload that gets stored and returned'
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'returns the kind' do
|
45
|
+
subject.kind.must_equal 'androidpublisher#subscriptionPurchase'
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'returns the start_time_millis' do
|
49
|
+
subject.start_time_millis.must_equal 145_954_011_324_4
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'returns the expiry_time_millis' do
|
53
|
+
subject.expiry_time_millis.must_equal 146_213_208_861_0
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'returns the starts_at' do
|
57
|
+
expected = DateTime.new(2016, 4, 1, 19, 48, 33)
|
58
|
+
subject.starts_at.must_equal expected
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'returns the expires_at' do
|
62
|
+
expected = DateTime.new(2016, 5, 1, 19, 48, 8)
|
63
|
+
subject.expires_at.must_equal expected
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe 'unexpired and renewing subscription' do
|
68
|
+
two_days_from_now = DateTime.now + 2
|
69
|
+
let(:attributes) do
|
70
|
+
{
|
71
|
+
'expiryTimeMillis' => (two_days_from_now.to_time.to_i * 1000).to_s,
|
72
|
+
'autoRenewing' => true
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'is expired?' do
|
77
|
+
subject.expired?.must_be_false
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'is two days left until it is overdue' do
|
81
|
+
subject.overdue_days.must_equal(-2)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe 'expired due to payment failure' do
|
86
|
+
let(:attributes) do
|
87
|
+
{
|
88
|
+
'expiryTimeMillis' => '1462132088610',
|
89
|
+
'autoRenewing' => true,
|
90
|
+
'cancelReason' => 1
|
91
|
+
}
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'is expired?' do
|
95
|
+
subject.expired?.must_be_true
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'is payment_failed?' do
|
99
|
+
subject.payment_failed?.must_be_true
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe 'expired with pending payment' do
|
104
|
+
let(:attributes) do
|
105
|
+
{
|
106
|
+
'expiryTimeMillis' => '1462132088610',
|
107
|
+
'autoRenewing' => true,
|
108
|
+
'paymentState' => 0
|
109
|
+
}
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'is expired?' do
|
113
|
+
subject.expired?.must_be_true
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'is payment_pending?' do
|
117
|
+
subject.payment_pending?.must_be_true
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe 'trial' do
|
122
|
+
let(:attributes) do
|
123
|
+
{
|
124
|
+
'paymentState' => 1,
|
125
|
+
'priceCurrencyCode' => 'SOMECODE',
|
126
|
+
'priceAmountMicros' => '0'
|
127
|
+
}
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'is trual?' do
|
131
|
+
subject.trial?.must_be_true
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'returns the price_currency_code' do
|
135
|
+
subject.price_currency_code.must_equal 'SOMECODE'
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CandyCheck::PlayStore::SubscriptionVerification do
|
4
|
+
subject do
|
5
|
+
CandyCheck::PlayStore::SubscriptionVerification.new(
|
6
|
+
client, package, product_id, token
|
7
|
+
)
|
8
|
+
end
|
9
|
+
let(:client) { DummyGoogleSubsClient.new(response) }
|
10
|
+
let(:package) { 'the_package' }
|
11
|
+
let(:product_id) { 'the_product' }
|
12
|
+
let(:token) { 'the_token' }
|
13
|
+
|
14
|
+
describe 'valid' do
|
15
|
+
let(:response) do
|
16
|
+
{
|
17
|
+
'kind' => 'androidpublisher#subscriptionPurchase',
|
18
|
+
'startTimeMillis' => '1459540113244',
|
19
|
+
'expiryTimeMillis' => '1462132088610',
|
20
|
+
'autoRenewing' => false,
|
21
|
+
'developerPayload' => 'payload that gets stored and returned',
|
22
|
+
'cancelReason' => 0,
|
23
|
+
'paymentState' => '1'
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'calls the client with the correct paramters' do
|
28
|
+
subject.call!
|
29
|
+
client.package.must_equal package
|
30
|
+
client.product_id.must_equal product_id
|
31
|
+
client.token.must_equal token
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'returns a subscription' do
|
35
|
+
result = subject.call!
|
36
|
+
result.must_be_instance_of CandyCheck::PlayStore::Subscription
|
37
|
+
result.expired?.must_be_true
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe 'failure' do
|
42
|
+
let(:response) do
|
43
|
+
{
|
44
|
+
'error' => {
|
45
|
+
'code' => 401,
|
46
|
+
'message' => 'The current user has insufficient permissions'
|
47
|
+
}
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'returns a verification failure' do
|
52
|
+
result = subject.call!
|
53
|
+
result.must_be_instance_of CandyCheck::PlayStore::VerificationFailure
|
54
|
+
result.code.must_equal 401
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe 'empty' do
|
59
|
+
let(:response) do
|
60
|
+
{}
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'returns a verification failure' do
|
64
|
+
result = subject.call!
|
65
|
+
result.must_be_instance_of CandyCheck::PlayStore::VerificationFailure
|
66
|
+
result.code.must_equal(-1)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe 'invalid response kind' do
|
71
|
+
let(:response) do
|
72
|
+
{
|
73
|
+
'kind' => 'something weird'
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'returns a verification failure' do
|
78
|
+
result = subject.call!
|
79
|
+
result.must_be_instance_of CandyCheck::PlayStore::VerificationFailure
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
DummyGoogleSubsClient = Struct.new(:response) do
|
86
|
+
attr_reader :package, :product_id, :token
|
87
|
+
|
88
|
+
def boot!
|
89
|
+
end
|
90
|
+
|
91
|
+
def verify_subscription(package, product_id, token)
|
92
|
+
@package = package
|
93
|
+
@product_id = product_id
|
94
|
+
@token = token
|
95
|
+
response
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -52,6 +52,22 @@ describe CandyCheck::PlayStore::Verifier do
|
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
55
|
+
it 'uses a subscription verifier when booted' do
|
56
|
+
result = :stubbed
|
57
|
+
with_mocked_client do
|
58
|
+
subject.boot!
|
59
|
+
end
|
60
|
+
with_mocked_verifier(result) do
|
61
|
+
subject.verify_subscription(
|
62
|
+
package, product_id, token
|
63
|
+
).must_be_same_as result
|
64
|
+
|
65
|
+
assert_recorded(
|
66
|
+
[@client, package, product_id, token]
|
67
|
+
)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
55
71
|
private
|
56
72
|
|
57
73
|
def with_mocked_verifier(*results)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: candy_check
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.1.0.pre
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jonas Thiel
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-10-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: multi_json
|
@@ -58,14 +58,14 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '0.
|
61
|
+
version: '0.39'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '0.
|
68
|
+
version: '0.39'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: inch
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -100,42 +100,42 @@ dependencies:
|
|
100
100
|
requirements:
|
101
101
|
- - "~>"
|
102
102
|
- !ruby/object:Gem::Version
|
103
|
-
version: '
|
103
|
+
version: '11.1'
|
104
104
|
type: :development
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
|
-
version: '
|
110
|
+
version: '11.1'
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
112
|
name: coveralls
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
114
114
|
requirements:
|
115
115
|
- - "~>"
|
116
116
|
- !ruby/object:Gem::Version
|
117
|
-
version: '0.
|
117
|
+
version: '0.8'
|
118
118
|
type: :development
|
119
119
|
prerelease: false
|
120
120
|
version_requirements: !ruby/object:Gem::Requirement
|
121
121
|
requirements:
|
122
122
|
- - "~>"
|
123
123
|
- !ruby/object:Gem::Version
|
124
|
-
version: '0.
|
124
|
+
version: '0.8'
|
125
125
|
- !ruby/object:Gem::Dependency
|
126
126
|
name: minitest
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
128
128
|
requirements:
|
129
129
|
- - "~>"
|
130
130
|
- !ruby/object:Gem::Version
|
131
|
-
version: '5.
|
131
|
+
version: '5.9'
|
132
132
|
type: :development
|
133
133
|
prerelease: false
|
134
134
|
version_requirements: !ruby/object:Gem::Requirement
|
135
135
|
requirements:
|
136
136
|
- - "~>"
|
137
137
|
- !ruby/object:Gem::Version
|
138
|
-
version: '5.
|
138
|
+
version: '5.9'
|
139
139
|
- !ruby/object:Gem::Dependency
|
140
140
|
name: minitest-around
|
141
141
|
requirement: !ruby/object:Gem::Requirement
|
@@ -156,14 +156,14 @@ dependencies:
|
|
156
156
|
requirements:
|
157
157
|
- - "~>"
|
158
158
|
- !ruby/object:Gem::Version
|
159
|
-
version: '1
|
159
|
+
version: '2.1'
|
160
160
|
type: :development
|
161
161
|
prerelease: false
|
162
162
|
version_requirements: !ruby/object:Gem::Requirement
|
163
163
|
requirements:
|
164
164
|
- - "~>"
|
165
165
|
- !ruby/object:Gem::Version
|
166
|
-
version: '1
|
166
|
+
version: '2.1'
|
167
167
|
description:
|
168
168
|
email:
|
169
169
|
- jonas@thiel.io
|
@@ -187,6 +187,8 @@ files:
|
|
187
187
|
- lib/candy_check/app_store/client.rb
|
188
188
|
- lib/candy_check/app_store/config.rb
|
189
189
|
- lib/candy_check/app_store/receipt.rb
|
190
|
+
- lib/candy_check/app_store/receipt_collection.rb
|
191
|
+
- lib/candy_check/app_store/subscription_verification.rb
|
190
192
|
- lib/candy_check/app_store/verification.rb
|
191
193
|
- lib/candy_check/app_store/verification_failure.rb
|
192
194
|
- lib/candy_check/app_store/verifier.rb
|
@@ -203,6 +205,8 @@ files:
|
|
203
205
|
- lib/candy_check/play_store/config.rb
|
204
206
|
- lib/candy_check/play_store/discovery_repository.rb
|
205
207
|
- lib/candy_check/play_store/receipt.rb
|
208
|
+
- lib/candy_check/play_store/subscription.rb
|
209
|
+
- lib/candy_check/play_store/subscription_verification.rb
|
206
210
|
- lib/candy_check/play_store/verification.rb
|
207
211
|
- lib/candy_check/play_store/verification_failure.rb
|
208
212
|
- lib/candy_check/play_store/verifier.rb
|
@@ -212,7 +216,9 @@ files:
|
|
212
216
|
- lib/candy_check/version.rb
|
213
217
|
- spec/app_store/client_spec.rb
|
214
218
|
- spec/app_store/config_spec.rb
|
219
|
+
- spec/app_store/receipt_collection_spec.rb
|
215
220
|
- spec/app_store/receipt_spec.rb
|
221
|
+
- spec/app_store/subscription_verification_spec.rb
|
216
222
|
- spec/app_store/verifcation_failure_spec.rb
|
217
223
|
- spec/app_store/verification_spec.rb
|
218
224
|
- spec/app_store/verifier_spec.rb
|
@@ -235,6 +241,8 @@ files:
|
|
235
241
|
- spec/play_store/config_spec.rb
|
236
242
|
- spec/play_store/discovery_respository_spec.rb
|
237
243
|
- spec/play_store/receipt_spec.rb
|
244
|
+
- spec/play_store/subscription_spec.rb
|
245
|
+
- spec/play_store/subscription_verification_spec.rb
|
238
246
|
- spec/play_store/verification_failure_spec.rb
|
239
247
|
- spec/play_store/verification_spec.rb
|
240
248
|
- spec/play_store/verifier_spec.rb
|
@@ -257,19 +265,21 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
257
265
|
version: '2.0'
|
258
266
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
259
267
|
requirements:
|
260
|
-
- - "
|
268
|
+
- - ">"
|
261
269
|
- !ruby/object:Gem::Version
|
262
|
-
version:
|
270
|
+
version: 1.3.1
|
263
271
|
requirements: []
|
264
272
|
rubyforge_project:
|
265
|
-
rubygems_version: 2.
|
273
|
+
rubygems_version: 2.5.1
|
266
274
|
signing_key:
|
267
275
|
specification_version: 4
|
268
276
|
summary: Check and verify in-app receipts
|
269
277
|
test_files:
|
270
278
|
- spec/app_store/client_spec.rb
|
271
279
|
- spec/app_store/config_spec.rb
|
280
|
+
- spec/app_store/receipt_collection_spec.rb
|
272
281
|
- spec/app_store/receipt_spec.rb
|
282
|
+
- spec/app_store/subscription_verification_spec.rb
|
273
283
|
- spec/app_store/verifcation_failure_spec.rb
|
274
284
|
- spec/app_store/verification_spec.rb
|
275
285
|
- spec/app_store/verifier_spec.rb
|
@@ -292,6 +302,8 @@ test_files:
|
|
292
302
|
- spec/play_store/config_spec.rb
|
293
303
|
- spec/play_store/discovery_respository_spec.rb
|
294
304
|
- spec/play_store/receipt_spec.rb
|
305
|
+
- spec/play_store/subscription_spec.rb
|
306
|
+
- spec/play_store/subscription_verification_spec.rb
|
295
307
|
- spec/play_store/verification_failure_spec.rb
|
296
308
|
- spec/play_store/verification_spec.rb
|
297
309
|
- spec/play_store/verifier_spec.rb
|