candy_check 0.1.0.pre → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -0,0 +1,53 @@
|
|
1
|
+
module CandyCheck
|
2
|
+
module PlayStore
|
3
|
+
module ProductPurchases
|
4
|
+
# Verifies a purchase token against the PlayStore API
|
5
|
+
# The call return either a {ProductPurchase} or a {VerificationFailure}
|
6
|
+
class ProductVerification
|
7
|
+
# @return [String] the package_name which will be queried
|
8
|
+
attr_reader :package_name
|
9
|
+
# @return [String] the item id which will be queried
|
10
|
+
attr_reader :product_id
|
11
|
+
# @return [String] the token for authentication
|
12
|
+
attr_reader :token
|
13
|
+
|
14
|
+
# Initializes a new call to the API
|
15
|
+
# @param package_name [String]
|
16
|
+
# @param product_id [String]
|
17
|
+
# @param token [String]
|
18
|
+
def initialize(package_name:, product_id:, token:, authorization:)
|
19
|
+
@package_name = package_name
|
20
|
+
@product_id = product_id
|
21
|
+
@token = token
|
22
|
+
@authorization = authorization
|
23
|
+
end
|
24
|
+
|
25
|
+
# Performs the verification against the remote server
|
26
|
+
# @return [ProductPurchase] if successful
|
27
|
+
# @return [VerificationFailure] otherwise
|
28
|
+
def call!
|
29
|
+
verify!
|
30
|
+
if valid?
|
31
|
+
CandyCheck::PlayStore::ProductPurchases::ProductPurchase.new(@response[:result])
|
32
|
+
else
|
33
|
+
CandyCheck::PlayStore::VerificationFailure.new(@response[:error])
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def valid?
|
40
|
+
@response[:result] && @response[:result].purchase_state && @response[:result].consumption_state
|
41
|
+
end
|
42
|
+
|
43
|
+
def verify!
|
44
|
+
service = CandyCheck::PlayStore::AndroidPublisherService.new
|
45
|
+
service.authorization = @authorization
|
46
|
+
service.get_purchase_product(package_name, product_id, token) do |result, error|
|
47
|
+
@response = { result: result, error: error }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
module CandyCheck
|
2
|
+
module PlayStore
|
3
|
+
module SubscriptionPurchases
|
4
|
+
# Describes a successfully validated subscription
|
5
|
+
class SubscriptionPurchase
|
6
|
+
include Utils::AttributeReader
|
7
|
+
|
8
|
+
# @return [Google::Apis::AndroidpublisherV3::SubscriptionPurchase] the raw subscription purchase from google-api-client
|
9
|
+
attr_reader :subscription_purchase
|
10
|
+
|
11
|
+
# The payment of the subscription is pending (paymentState)
|
12
|
+
PAYMENT_PENDING = 0
|
13
|
+
# The payment of the subscript is received (paymentState)
|
14
|
+
PAYMENT_RECEIVED = 1
|
15
|
+
# The subscription was canceled by the user (cancelReason)
|
16
|
+
PAYMENT_CANCELED = 0
|
17
|
+
# The payment failed during processing (cancelReason)
|
18
|
+
PAYMENT_FAILED = 1
|
19
|
+
|
20
|
+
# Initializes a new instance which bases on a JSON result
|
21
|
+
# from Google's servers
|
22
|
+
# @param subscription_purchase [Google::Apis::AndroidpublisherV3::SubscriptionPurchase]
|
23
|
+
def initialize(subscription_purchase)
|
24
|
+
@subscription_purchase = subscription_purchase
|
25
|
+
end
|
26
|
+
|
27
|
+
# Check if the expiration date is passed
|
28
|
+
# @return [bool]
|
29
|
+
def expired?
|
30
|
+
overdue_days > 0
|
31
|
+
end
|
32
|
+
|
33
|
+
# Check if in trial. This is actually not given by Google, but we assume
|
34
|
+
# that it is a trial going on if the paid amount is 0 and
|
35
|
+
# renewal is activated.
|
36
|
+
# @return [bool]
|
37
|
+
def trial?
|
38
|
+
price_is_zero = price_amount_micros == 0
|
39
|
+
price_is_zero && payment_received?
|
40
|
+
end
|
41
|
+
|
42
|
+
# see if payment is ok
|
43
|
+
# @return [bool]
|
44
|
+
def payment_received?
|
45
|
+
payment_state == PAYMENT_RECEIVED
|
46
|
+
end
|
47
|
+
|
48
|
+
# see if payment is pending
|
49
|
+
# @return [bool]
|
50
|
+
def payment_pending?
|
51
|
+
payment_state == PAYMENT_PENDING
|
52
|
+
end
|
53
|
+
|
54
|
+
# see if payment has failed according to Google
|
55
|
+
# @return [bool]
|
56
|
+
def payment_failed?
|
57
|
+
cancel_reason == PAYMENT_FAILED
|
58
|
+
end
|
59
|
+
|
60
|
+
# see if this the user has canceled its subscription
|
61
|
+
# @return [bool]
|
62
|
+
def canceled_by_user?
|
63
|
+
cancel_reason == PAYMENT_CANCELED
|
64
|
+
end
|
65
|
+
|
66
|
+
# Get number of overdue days. If this is negative, it is not overdue.
|
67
|
+
# @return [Integer]
|
68
|
+
def overdue_days
|
69
|
+
(Time.now.utc.to_date - expires_at.to_date).to_i
|
70
|
+
end
|
71
|
+
|
72
|
+
# Get the auto renewal status as given by Google
|
73
|
+
# @return [bool] true if renewing automatically, false otherwise
|
74
|
+
def auto_renewing?
|
75
|
+
@subscription_purchase.auto_renewing
|
76
|
+
end
|
77
|
+
|
78
|
+
# Get the payment state as given by Google
|
79
|
+
# @return [Integer]
|
80
|
+
def payment_state
|
81
|
+
@subscription_purchase.payment_state
|
82
|
+
end
|
83
|
+
|
84
|
+
# Get the price amount for the subscription in micros in the payed
|
85
|
+
# currency
|
86
|
+
# @return [Integer]
|
87
|
+
def price_amount_micros
|
88
|
+
@subscription_purchase.price_amount_micros
|
89
|
+
end
|
90
|
+
|
91
|
+
# Get the cancel reason, as given by Google
|
92
|
+
# @return [Integer]
|
93
|
+
def cancel_reason
|
94
|
+
@subscription_purchase.cancel_reason
|
95
|
+
end
|
96
|
+
|
97
|
+
# Get the kind of subscription as stored in the android publisher service
|
98
|
+
# @return [String]
|
99
|
+
def kind
|
100
|
+
@subscription_purchase.kind
|
101
|
+
end
|
102
|
+
|
103
|
+
# Get developer-specified supplemental information about the order
|
104
|
+
# @return [String]
|
105
|
+
def developer_payload
|
106
|
+
@subscription_purchase.developer_payload
|
107
|
+
end
|
108
|
+
|
109
|
+
# Get the currency code in ISO 4217 format, e.g. "GBP" for British pounds
|
110
|
+
# @return [String]
|
111
|
+
def price_currency_code
|
112
|
+
@subscription_purchase.price_currency_code
|
113
|
+
end
|
114
|
+
|
115
|
+
# Get start time for subscription in milliseconds since Epoch
|
116
|
+
# @return [Integer]
|
117
|
+
def start_time_millis
|
118
|
+
@subscription_purchase.start_time_millis
|
119
|
+
end
|
120
|
+
|
121
|
+
# Get expiry time for subscription in milliseconds since Epoch
|
122
|
+
# @return [Integer]
|
123
|
+
def expiry_time_millis
|
124
|
+
@subscription_purchase.expiry_time_millis
|
125
|
+
end
|
126
|
+
|
127
|
+
# Get cancellation time for subscription in milliseconds since Epoch.
|
128
|
+
# Only present if cancelReason is 0.
|
129
|
+
# @return [Integer]
|
130
|
+
def user_cancellation_time_millis
|
131
|
+
@subscription_purchase.user_cancellation_time_millis if canceled_by_user?
|
132
|
+
end
|
133
|
+
|
134
|
+
# Get start time in UTC
|
135
|
+
# @return [DateTime]
|
136
|
+
def starts_at
|
137
|
+
Time.at(start_time_millis / 1000).utc.to_datetime
|
138
|
+
end
|
139
|
+
|
140
|
+
# Get expiration time in UTC
|
141
|
+
# @return [DateTime]
|
142
|
+
def expires_at
|
143
|
+
Time.at(expiry_time_millis / 1000).utc.to_datetime
|
144
|
+
end
|
145
|
+
|
146
|
+
# Get cancellation time in UTC
|
147
|
+
# @return [DateTime]
|
148
|
+
def canceled_at
|
149
|
+
Time.at(user_cancellation_time_millis / 1000).utc.to_datetime if user_cancellation_time_millis
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module CandyCheck
|
2
|
+
module PlayStore
|
3
|
+
module SubscriptionPurchases
|
4
|
+
# Verifies a purchase token against the Google API
|
5
|
+
# The call return either an {SubscriptionPurchase} or an {VerificationFailure}
|
6
|
+
class SubscriptionVerification
|
7
|
+
# @return [String] the package which will be queried
|
8
|
+
attr_reader :package_name
|
9
|
+
# @return [String] the item id which will be queried
|
10
|
+
attr_reader :subscription_id
|
11
|
+
# @return [String] the token for authentication
|
12
|
+
attr_reader :token
|
13
|
+
|
14
|
+
# Initializes a new call to the API
|
15
|
+
# @param package_name [String]
|
16
|
+
# @param subscription_id [String]
|
17
|
+
# @param token [String]
|
18
|
+
def initialize(package_name:, subscription_id:, token:, authorization:)
|
19
|
+
@package_name = package_name
|
20
|
+
@subscription_id = subscription_id
|
21
|
+
@token = token
|
22
|
+
@authorization = authorization
|
23
|
+
end
|
24
|
+
|
25
|
+
# Performs the verification against the remote server
|
26
|
+
# @return [SubscriptionPurchase] if successful
|
27
|
+
# @return [VerificationFailure] otherwise
|
28
|
+
def call!
|
29
|
+
verify!
|
30
|
+
if valid?
|
31
|
+
CandyCheck::PlayStore::SubscriptionPurchases::SubscriptionPurchase.new(@response[:result])
|
32
|
+
else
|
33
|
+
CandyCheck::PlayStore::VerificationFailure.new(@response[:error])
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def valid?
|
40
|
+
return false unless @response[:result]
|
41
|
+
ok_kind = @response[:result].kind == "androidpublisher#subscriptionPurchase"
|
42
|
+
@response && @response[:result].expiry_time_millis && ok_kind
|
43
|
+
end
|
44
|
+
|
45
|
+
def verify!
|
46
|
+
service = CandyCheck::PlayStore::AndroidPublisherService.new
|
47
|
+
service.authorization = @authorization
|
48
|
+
service.get_purchase_subscription(package_name, subscription_id, token) do |result, error|
|
49
|
+
@response = { result: result, error: error }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -5,25 +5,27 @@ module CandyCheck
|
|
5
5
|
include Utils::AttributeReader
|
6
6
|
|
7
7
|
# @return [Hash] the raw attributes returned from the server
|
8
|
-
attr_reader :
|
8
|
+
attr_reader :error
|
9
9
|
|
10
10
|
# Initializes a new instance which bases on a JSON result
|
11
11
|
# from Google API servers
|
12
|
-
# @param
|
13
|
-
def initialize(
|
14
|
-
@
|
12
|
+
# @param error [Hash]
|
13
|
+
def initialize(error)
|
14
|
+
@error = error
|
15
15
|
end
|
16
16
|
|
17
17
|
# The code of the failure
|
18
18
|
# @return [Fixnum]
|
19
19
|
def code
|
20
|
-
|
20
|
+
Integer(error.status_code)
|
21
|
+
rescue
|
22
|
+
-1
|
21
23
|
end
|
22
24
|
|
23
25
|
# The message of the failure
|
24
26
|
# @return [String]
|
25
27
|
def message
|
26
|
-
|
28
|
+
error.message || "Unknown error"
|
27
29
|
end
|
28
30
|
end
|
29
31
|
end
|
@@ -1,69 +1,44 @@
|
|
1
1
|
module CandyCheck
|
2
2
|
module PlayStore
|
3
3
|
# Verifies purchase tokens against the Google API.
|
4
|
-
# The call return either
|
4
|
+
# The call return either a {SubscriptionPurchases::SubscriptionPurchase} or a {VerificationFailure}
|
5
5
|
class Verifier
|
6
|
-
#
|
7
|
-
#
|
8
|
-
|
9
|
-
|
10
|
-
# @return [Config] the current configuration
|
11
|
-
attr_reader :config
|
12
|
-
|
13
|
-
# Initializes a new verifier for the application which is bound
|
14
|
-
# to a configuration
|
15
|
-
# @param config [Config]
|
16
|
-
def initialize(config)
|
17
|
-
@config = config
|
18
|
-
end
|
19
|
-
|
20
|
-
# Boot the module
|
21
|
-
def boot!
|
22
|
-
boot_error('You\'re only allowed to boot the verifier once') if booted?
|
23
|
-
@client = Client.new(config)
|
24
|
-
@client.boot!
|
6
|
+
# Initializes a new verifier which is bound to an authorization
|
7
|
+
# @param authorization [Google::Auth::ServiceAccountCredentials] to use against the PlayStore API
|
8
|
+
def initialize(authorization:)
|
9
|
+
@authorization = authorization
|
25
10
|
end
|
26
11
|
|
27
12
|
# Contacts the Google API and requests the product state
|
28
|
-
# @param
|
13
|
+
# @param package_name [String] to query
|
29
14
|
# @param product_id [String] to query
|
30
15
|
# @param token [String] to use for authentication
|
31
|
-
# @return [
|
16
|
+
# @return [ProductPurchases::ProductPurchase] if successful
|
32
17
|
# @return [VerificationFailure] otherwise
|
33
|
-
def
|
34
|
-
|
35
|
-
|
36
|
-
|
18
|
+
def verify_product_purchase(package_name:, product_id:, token:)
|
19
|
+
verifier = CandyCheck::PlayStore::ProductPurchases::ProductVerification.new(
|
20
|
+
package_name: package_name,
|
21
|
+
product_id: product_id,
|
22
|
+
token: token,
|
23
|
+
authorization: @authorization,
|
24
|
+
)
|
25
|
+
verifier.call!
|
37
26
|
end
|
38
27
|
|
39
28
|
# Contacts the Google API and requests the product state
|
40
|
-
# @param
|
29
|
+
# @param package_name [String] to query
|
41
30
|
# @param subscription_id [String] to query
|
42
31
|
# @param token [String] to use for authentication
|
43
|
-
# @return [
|
32
|
+
# @return [SubscriptionPurchases::SubscriptionPurchase] if successful
|
44
33
|
# @return [VerificationFailure] otherwise
|
45
|
-
def
|
46
|
-
|
47
|
-
|
48
|
-
|
34
|
+
def verify_subscription_purchase(package_name:, subscription_id:, token:)
|
35
|
+
verifier = CandyCheck::PlayStore::SubscriptionPurchases::SubscriptionVerification.new(
|
36
|
+
package_name: package_name,
|
37
|
+
subscription_id: subscription_id,
|
38
|
+
token: token,
|
39
|
+
authorization: @authorization,
|
49
40
|
)
|
50
|
-
|
51
|
-
end
|
52
|
-
|
53
|
-
private
|
54
|
-
|
55
|
-
def booted?
|
56
|
-
instance_variable_defined?(:@client)
|
57
|
-
end
|
58
|
-
|
59
|
-
def check_boot!
|
60
|
-
return if booted?
|
61
|
-
boot_error 'You need to boot the verifier service first: '\
|
62
|
-
'CandyCheck::PlayStore::Verifier#boot!'
|
63
|
-
end
|
64
|
-
|
65
|
-
def boot_error(message)
|
66
|
-
raise BootRequiredError, message
|
41
|
+
verifier.call!
|
67
42
|
end
|
68
43
|
end
|
69
44
|
end
|
@@ -5,9 +5,11 @@ module CandyCheck
|
|
5
5
|
# Initializes a new configuration from a hash
|
6
6
|
# @param attributes [Hash]
|
7
7
|
def initialize(attributes)
|
8
|
-
attributes.
|
9
|
-
|
10
|
-
|
8
|
+
if attributes.is_a?(Hash)
|
9
|
+
attributes.each do |k, v|
|
10
|
+
instance_variable_set "@#{k}", v
|
11
|
+
end
|
12
|
+
end
|
11
13
|
validate!
|
12
14
|
end
|
13
15
|
|
data/lib/candy_check/version.rb
CHANGED
@@ -8,10 +8,12 @@ describe CandyCheck::AppStore::ReceiptCollection do
|
|
8
8
|
[{
|
9
9
|
'expires_date' => '2014-04-15 12:52:40 Etc/GMT',
|
10
10
|
'expires_date_pst' => '2014-04-15 05:52:40 America/Los_Angeles',
|
11
|
+
'purchase_date' => '2014-04-14 12:52:40 Etc/GMT',
|
11
12
|
'is_trial_period' => 'false'
|
12
13
|
}, {
|
13
14
|
'expires_date' => '2015-04-15 12:52:40 Etc/GMT',
|
14
15
|
'expires_date_pst' => '2015-04-15 05:52:40 America/Los_Angeles',
|
16
|
+
'purchase_date' => '2015-04-14 12:52:40 Etc/GMT',
|
15
17
|
'is_trial_period' => 'false'
|
16
18
|
}]
|
17
19
|
end
|
@@ -34,6 +36,34 @@ describe CandyCheck::AppStore::ReceiptCollection do
|
|
34
36
|
expected = DateTime.new(2015, 4, 15, 12, 52, 40)
|
35
37
|
subject.expires_at.must_equal expected
|
36
38
|
end
|
39
|
+
|
40
|
+
it 'is expired? at same pointin time' do
|
41
|
+
Timecop.freeze(Time.utc(2015, 4, 15, 12, 52, 40)) do
|
42
|
+
subject.expired?.must_be_true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe 'unordered receipts' do
|
48
|
+
let(:attributes) do
|
49
|
+
[{
|
50
|
+
'expires_date' => '2015-04-15 12:52:40 Etc/GMT',
|
51
|
+
'expires_date_pst' => '2015-04-15 05:52:40 America/Los_Angeles',
|
52
|
+
'purchase_date' => '2015-04-14 12:52:40 Etc/GMT',
|
53
|
+
'is_trial_period' => 'false'
|
54
|
+
}, {
|
55
|
+
'expires_date' => '2014-04-15 12:52:40 Etc/GMT',
|
56
|
+
'expires_date_pst' => '2014-04-15 05:52:40 America/Los_Angeles',
|
57
|
+
'purchase_date' => '2014-04-14 12:52:40 Etc/GMT',
|
58
|
+
'is_trial_period' => 'false'
|
59
|
+
}]
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'the expires date is the latest one in time' do
|
63
|
+
expected = DateTime.new(2015, 4, 15, 12, 52, 40)
|
64
|
+
subject.expires_at.must_equal expected
|
65
|
+
end
|
66
|
+
|
37
67
|
end
|
38
68
|
|
39
69
|
describe 'unexpired trial subscription' do
|
@@ -42,10 +72,12 @@ describe CandyCheck::AppStore::ReceiptCollection do
|
|
42
72
|
let(:attributes) do
|
43
73
|
[{
|
44
74
|
'expires_date' => '2016-04-15 12:52:40 Etc/GMT',
|
75
|
+
'purchase_date' => '2016-04-15 12:52:40 Etc/GMT',
|
45
76
|
'is_trial_period' => 'true'
|
46
77
|
}, {
|
47
78
|
'expires_date' =>
|
48
79
|
two_days_from_now.strftime('%Y-%m-%d %H:%M:%S Etc/GMT'),
|
80
|
+
'purchase_date' => '2016-04-15 12:52:40 Etc/GMT',
|
49
81
|
'is_trial_period' => 'true'
|
50
82
|
}]
|
51
83
|
end
|
@@ -62,4 +94,5 @@ describe CandyCheck::AppStore::ReceiptCollection do
|
|
62
94
|
subject.overdue_days.must_equal(-2)
|
63
95
|
end
|
64
96
|
end
|
97
|
+
|
65
98
|
end
|