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
@@ -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
|