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
@@ -8,15 +8,17 @@ module CandyCheck
|
|
8
8
|
|
9
9
|
# Initializes a new instance which bases on a JSON result
|
10
10
|
# from Apple's verification server
|
11
|
-
# @param attributes [Array<Hash>]
|
11
|
+
# @param attributes [Array<Hash>] raw data from Apple's server
|
12
12
|
def initialize(attributes)
|
13
|
-
@receipts = attributes.map {
|
13
|
+
@receipts = attributes.map {|r| Receipt.new(r) }.sort{ |a, b|
|
14
|
+
a.purchase_date - b.purchase_date
|
15
|
+
}
|
14
16
|
end
|
15
17
|
|
16
18
|
# Check if the latest expiration date is passed
|
17
19
|
# @return [bool]
|
18
20
|
def expired?
|
19
|
-
|
21
|
+
expires_at.to_time <= Time.now.utc
|
20
22
|
end
|
21
23
|
|
22
24
|
# Check if in trial
|
@@ -3,13 +3,28 @@ module CandyCheck
|
|
3
3
|
# Verifies a latest_receipt_info block against a verification server.
|
4
4
|
# The call return either an {ReceiptCollection} or a {VerificationFailure}
|
5
5
|
class SubscriptionVerification < CandyCheck::AppStore::Verification
|
6
|
+
# Builds a fresh verification run
|
7
|
+
# @param endpoint_url [String] the verification URL to use
|
8
|
+
# @param receipt_data [String] the raw data to be verified
|
9
|
+
# @param secret [String] optional: shared secret
|
10
|
+
# @param product_ids [Array<String>] optional: select specific products
|
11
|
+
def initialize(
|
12
|
+
endpoint_url,
|
13
|
+
receipt_data,
|
14
|
+
secret = nil,
|
15
|
+
product_ids = nil
|
16
|
+
)
|
17
|
+
super(endpoint_url, receipt_data, secret)
|
18
|
+
@product_ids = product_ids
|
19
|
+
end
|
20
|
+
|
6
21
|
# Performs the verification against the remote server
|
7
22
|
# @return [ReceiptCollection] if successful
|
8
23
|
# @return [VerificationFailure] otherwise
|
9
24
|
def call!
|
10
25
|
verify!
|
11
26
|
if valid?
|
12
|
-
|
27
|
+
build_collection(@response['latest_receipt_info'])
|
13
28
|
else
|
14
29
|
VerificationFailure.fetch(@response['status'])
|
15
30
|
end
|
@@ -17,6 +32,15 @@ module CandyCheck
|
|
17
32
|
|
18
33
|
private
|
19
34
|
|
35
|
+
def build_collection(latest_receipt_info)
|
36
|
+
unless @product_ids.nil?
|
37
|
+
latest_receipt_info = latest_receipt_info.select do |info|
|
38
|
+
@product_ids.include?(info['product_id'])
|
39
|
+
end
|
40
|
+
end
|
41
|
+
ReceiptCollection.new(latest_receipt_info)
|
42
|
+
end
|
43
|
+
|
20
44
|
def valid?
|
21
45
|
status_is_ok = @response['status'] == STATUS_OK
|
22
46
|
@response && status_is_ok && @response['latest_receipt_info']
|
@@ -16,7 +16,7 @@ module CandyCheck
|
|
16
16
|
# Builds a fresh verification run
|
17
17
|
# @param endpoint_url [String] the verification URL to use
|
18
18
|
# @param receipt_data [String] the raw data to be verified
|
19
|
-
# @param secret [String]
|
19
|
+
# @param secret [String] optional: shared secret
|
20
20
|
def initialize(endpoint_url, receipt_data, secret = nil)
|
21
21
|
@endpoint_url = endpoint_url
|
22
22
|
@receipt_data = receipt_data
|
@@ -30,33 +30,33 @@ module CandyCheck
|
|
30
30
|
# @return [Receipt] if successful
|
31
31
|
# @return [VerificationFailure] otherwise
|
32
32
|
def verify(receipt_data, secret = nil)
|
33
|
-
|
34
|
-
fetch_receipt_information(receipt_data, secret)
|
33
|
+
fetch_receipt_information(Verification, [receipt_data, secret])
|
35
34
|
end
|
36
35
|
|
37
36
|
# Calls a subscription verification for the given input
|
38
37
|
# @param receipt_data [String] the raw data to be verified
|
39
|
-
# @param secret [
|
38
|
+
# @param secret [String] optional: shared secret
|
39
|
+
# @param product_ids [Array<String>] optional: products to filter
|
40
40
|
# @return [ReceiptCollection] if successful
|
41
41
|
# @return [Verification] otherwise
|
42
|
-
def verify_subscription(receipt_data, secret = nil)
|
43
|
-
|
44
|
-
fetch_receipt_information(
|
42
|
+
def verify_subscription(receipt_data, secret = nil, product_ids = nil)
|
43
|
+
args = [receipt_data, secret, product_ids]
|
44
|
+
fetch_receipt_information(SubscriptionVerification, args)
|
45
45
|
end
|
46
46
|
|
47
47
|
private
|
48
48
|
|
49
|
-
def fetch_receipt_information(
|
49
|
+
def fetch_receipt_information(verifier_class, args)
|
50
50
|
default_endpoint, opposite_endpoint = endpoints
|
51
|
-
result = call_for(
|
51
|
+
result = call_for(verifier_class, args.dup.unshift(default_endpoint))
|
52
52
|
if should_retry?(result)
|
53
|
-
return call_for(
|
53
|
+
return call_for(verifier_class, args.dup.unshift(opposite_endpoint))
|
54
54
|
end
|
55
55
|
result
|
56
56
|
end
|
57
57
|
|
58
|
-
def call_for(
|
59
|
-
|
58
|
+
def call_for(verifier_class, args)
|
59
|
+
verifier_class.new(*args).call!
|
60
60
|
end
|
61
61
|
|
62
62
|
def should_retry?(result)
|
data/lib/candy_check/cli/app.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "thor"
|
2
2
|
|
3
3
|
module CandyCheck
|
4
4
|
module CLI
|
@@ -6,54 +6,37 @@ module CandyCheck
|
|
6
6
|
# @example
|
7
7
|
# $> candy_check help
|
8
8
|
class App < Thor
|
9
|
-
package_name
|
9
|
+
package_name "CandyCheck"
|
10
10
|
|
11
|
-
desc
|
11
|
+
desc "app_store RECEIPT_DATA", "Verify a base64 encoded AppStore receipt"
|
12
12
|
method_option :environment,
|
13
|
-
default:
|
13
|
+
default: "production",
|
14
14
|
type: :string,
|
15
15
|
enum: %w(production sandbox),
|
16
|
-
aliases:
|
17
|
-
desc:
|
16
|
+
aliases: "-e",
|
17
|
+
desc: "The environment to use for verfication"
|
18
18
|
method_option :secret,
|
19
|
-
aliases:
|
19
|
+
aliases: "-s",
|
20
20
|
type: :string,
|
21
|
-
desc:
|
21
|
+
desc: "The shared secret for auto-renewable subscriptions"
|
22
|
+
|
22
23
|
def app_store(receipt)
|
23
24
|
Commands::AppStore.run(receipt, options)
|
24
25
|
end
|
25
26
|
|
26
|
-
desc
|
27
|
-
method_option :
|
28
|
-
required: true,
|
29
|
-
type: :string,
|
30
|
-
aliases: '-i',
|
31
|
-
desc: 'The issuer\'s email address for the API call'
|
32
|
-
method_option :key_file,
|
27
|
+
desc "play_store PACKAGE PRODUCT_ID TOKEN", "Verify PlayStore purchase"
|
28
|
+
method_option :json_key_file,
|
33
29
|
required: true,
|
34
30
|
type: :string,
|
35
|
-
aliases:
|
36
|
-
desc:
|
37
|
-
|
38
|
-
default: 'notasecret',
|
39
|
-
type: :string,
|
40
|
-
aliases: '-s',
|
41
|
-
desc: 'The secret to decrypt the key_file'
|
42
|
-
method_option :application_name,
|
43
|
-
default: 'CandyCheck',
|
44
|
-
type: :string,
|
45
|
-
aliases: '-a',
|
46
|
-
desc: 'Your application\'s name'
|
47
|
-
method_option :application_version,
|
48
|
-
default: CandyCheck::VERSION,
|
49
|
-
type: :string,
|
50
|
-
aliases: '-v',
|
51
|
-
desc: 'Your application\'s version'
|
31
|
+
aliases: "-k",
|
32
|
+
desc: "The json key file to use for API authentication"
|
33
|
+
|
52
34
|
def play_store(package, product_id, token)
|
53
35
|
Commands::PlayStore.run(package, product_id, token, options)
|
54
36
|
end
|
55
37
|
|
56
|
-
desc
|
38
|
+
desc "version", 'Print the gem\'s version'
|
39
|
+
|
57
40
|
def version
|
58
41
|
Commands::Version.run
|
59
42
|
end
|
@@ -4,17 +4,13 @@ module CandyCheck
|
|
4
4
|
# Command to verify an PlayStore purchase
|
5
5
|
class PlayStore < Base
|
6
6
|
# Prepare a verification run from the terminal
|
7
|
-
# @param
|
7
|
+
# @param package_name [String]
|
8
8
|
# @param product_id [String]
|
9
9
|
# @param token [String]
|
10
10
|
# @param options [Hash]
|
11
|
-
# @option options [String] :
|
12
|
-
|
13
|
-
|
14
|
-
# @option options [String] :application_name for the API call
|
15
|
-
# @option options [String] :application_version for the API call
|
16
|
-
def initialize(package, product_id, token, options)
|
17
|
-
@package = package
|
11
|
+
# @option options [String] :json_key_file to use for API access
|
12
|
+
def initialize(package_name, product_id, token, options)
|
13
|
+
@package = package_name
|
18
14
|
@product_id = product_id
|
19
15
|
@token = token
|
20
16
|
super(options)
|
@@ -22,17 +18,20 @@ module CandyCheck
|
|
22
18
|
|
23
19
|
# Print the result of the verification to the terminal
|
24
20
|
def run
|
25
|
-
verifier = CandyCheck::PlayStore::Verifier.new(
|
26
|
-
verifier.
|
27
|
-
|
21
|
+
verifier = CandyCheck::PlayStore::Verifier.new(authorization: authorization)
|
22
|
+
result = verifier.verify_product_purchase(
|
23
|
+
package_name: @package,
|
24
|
+
product_id: @product_id,
|
25
|
+
token: @token,
|
26
|
+
)
|
28
27
|
out.print "#{result.class}:"
|
29
28
|
out.pretty result
|
30
29
|
end
|
31
30
|
|
32
31
|
private
|
33
32
|
|
34
|
-
def
|
35
|
-
CandyCheck::PlayStore
|
33
|
+
def authorization
|
34
|
+
CandyCheck::PlayStore.authorization(options["json_key_file"])
|
36
35
|
end
|
37
36
|
end
|
38
37
|
end
|
@@ -1,17 +1,27 @@
|
|
1
|
-
require
|
1
|
+
require "google/apis/androidpublisher_v3"
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
11
|
-
require
|
3
|
+
require "candy_check/play_store/android_publisher_service"
|
4
|
+
require "candy_check/play_store/product_purchases/product_purchase"
|
5
|
+
require "candy_check/play_store/subscription_purchases/subscription_purchase"
|
6
|
+
require "candy_check/play_store/product_purchases/product_verification"
|
7
|
+
require "candy_check/play_store/product_acknowledgements/acknowledgement"
|
8
|
+
require "candy_check/play_store/product_acknowledgements/response"
|
9
|
+
require "candy_check/play_store/subscription_purchases/subscription_verification"
|
10
|
+
require "candy_check/play_store/verification_failure"
|
11
|
+
require "candy_check/play_store/verifier"
|
12
|
+
require "candy_check/play_store/acknowledger"
|
12
13
|
|
13
14
|
module CandyCheck
|
14
15
|
# Module to request and verify a AppStore receipt
|
15
16
|
module PlayStore
|
17
|
+
# Build an authorization object
|
18
|
+
# @param json_key_file [String]
|
19
|
+
# @return [Google::Auth::ServiceAccountCredentials]
|
20
|
+
def self.authorization(json_key_file)
|
21
|
+
Google::Auth::ServiceAccountCredentials.make_creds(
|
22
|
+
json_key_io: File.open(json_key_file),
|
23
|
+
scope: "https://www.googleapis.com/auth/androidpublisher",
|
24
|
+
)
|
25
|
+
end
|
16
26
|
end
|
17
27
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module CandyCheck
|
2
|
+
module PlayStore
|
3
|
+
class Acknowledger
|
4
|
+
def initialize(authorization:)
|
5
|
+
@authorization = authorization
|
6
|
+
end
|
7
|
+
|
8
|
+
def acknowledge_product_purchase(package_name:, product_id:, token:)
|
9
|
+
acknowledger = CandyCheck::PlayStore::ProductAcknowledgements::Acknowledgement.new(
|
10
|
+
package_name: package_name,
|
11
|
+
product_id: product_id,
|
12
|
+
token: token,
|
13
|
+
authorization: @authorization,
|
14
|
+
)
|
15
|
+
acknowledger.call!
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module CandyCheck
|
2
|
+
module PlayStore
|
3
|
+
module ProductAcknowledgements
|
4
|
+
# Verifies a purchase token against the PlayStore API
|
5
|
+
|
6
|
+
class Acknowledgement
|
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
|
+
def call!
|
26
|
+
acknowledge!
|
27
|
+
|
28
|
+
CandyCheck::PlayStore::ProductAcknowledgements::Response.new(
|
29
|
+
result: @response[:result], error_data: @response[:error_data])
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def acknowledge!
|
35
|
+
service = CandyCheck::PlayStore::AndroidPublisherService.new
|
36
|
+
|
37
|
+
service.authorization = @authorization
|
38
|
+
service.acknowledge_purchase_product(package_name, product_id, token) do |result, error_data|
|
39
|
+
@response = { result: result, error_data: error_data }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module CandyCheck
|
2
|
+
module PlayStore
|
3
|
+
module ProductAcknowledgements
|
4
|
+
class Response
|
5
|
+
def initialize(result:, error_data:)
|
6
|
+
@result = result
|
7
|
+
@error_data = error_data
|
8
|
+
end
|
9
|
+
|
10
|
+
def acknowledged?
|
11
|
+
!!result
|
12
|
+
end
|
13
|
+
|
14
|
+
def error
|
15
|
+
return unless error_data
|
16
|
+
|
17
|
+
{ status_code: error_data.status_code, body: error_data.body }
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :result, :error_data
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module CandyCheck
|
2
|
+
module PlayStore
|
3
|
+
module ProductPurchases
|
4
|
+
# Describes a successful response from the PlayStore verification server
|
5
|
+
class ProductPurchase
|
6
|
+
include Utils::AttributeReader
|
7
|
+
|
8
|
+
# Returns the raw ProductPurchase from google-api-client gem
|
9
|
+
# @return [Google::Apis::AndroidpublisherV3::ProductPurchase]
|
10
|
+
attr_reader :product_purchase
|
11
|
+
|
12
|
+
# Purchased product (0 is purchased, don't ask me why)
|
13
|
+
# @see https://developers.google.com/android-publisher/api-ref/purchases/products
|
14
|
+
PURCHASE_STATE_PURCHASED = 0
|
15
|
+
|
16
|
+
# A consumed product
|
17
|
+
CONSUMPTION_STATE_CONSUMED = 1
|
18
|
+
|
19
|
+
# Initializes a new instance which bases on a JSON result
|
20
|
+
# from PlayStore API servers
|
21
|
+
# @param product_purchase [Google::Apis::AndroidpublisherV3::ProductPurchase]
|
22
|
+
def initialize(product_purchase)
|
23
|
+
@product_purchase = product_purchase
|
24
|
+
end
|
25
|
+
|
26
|
+
# The purchase state of the order. Possible values are:
|
27
|
+
# * 0: Purchased
|
28
|
+
# * 1: Cancelled
|
29
|
+
# @return [Fixnum]
|
30
|
+
def purchase_state
|
31
|
+
@product_purchase.purchase_state
|
32
|
+
end
|
33
|
+
|
34
|
+
# The consumption state of the inapp product. Possible values are:
|
35
|
+
# * 0: Yet to be consumed
|
36
|
+
# * 1: Consumed
|
37
|
+
# @return [Fixnum]
|
38
|
+
def consumption_state
|
39
|
+
@product_purchase.consumption_state
|
40
|
+
end
|
41
|
+
|
42
|
+
# The developer payload which was used when buying the product
|
43
|
+
# @return [String]
|
44
|
+
def developer_payload
|
45
|
+
@product_purchase.developer_payload
|
46
|
+
end
|
47
|
+
|
48
|
+
# This kind represents an inappPurchase object in the androidpublisher
|
49
|
+
# service.
|
50
|
+
# @return [String]
|
51
|
+
def kind
|
52
|
+
@product_purchase.kind
|
53
|
+
end
|
54
|
+
|
55
|
+
# The order id
|
56
|
+
# @return [String]
|
57
|
+
def order_id
|
58
|
+
@product_purchase.order_id
|
59
|
+
end
|
60
|
+
|
61
|
+
# The time the product was purchased, in milliseconds since the
|
62
|
+
# epoch (Jan 1, 1970)
|
63
|
+
# @return [Fixnum]
|
64
|
+
def purchase_time_millis
|
65
|
+
@product_purchase.purchase_time_millis
|
66
|
+
end
|
67
|
+
|
68
|
+
# A product may be purchased or canceled. Ensure a receipt
|
69
|
+
# is valid before granting some candy
|
70
|
+
# @return [Boolean]
|
71
|
+
def valid?
|
72
|
+
purchase_state == PURCHASE_STATE_PURCHASED
|
73
|
+
end
|
74
|
+
|
75
|
+
# A purchased product may already be consumed. In this case you
|
76
|
+
# should grant candy even if it's valid.
|
77
|
+
# @return [Boolean]
|
78
|
+
def consumed?
|
79
|
+
consumption_state == CONSUMPTION_STATE_CONSUMED
|
80
|
+
end
|
81
|
+
|
82
|
+
# The date and time the product was purchased
|
83
|
+
# @return [DateTime]
|
84
|
+
def purchased_at
|
85
|
+
Time.at(purchase_time_millis / 1000).utc.to_datetime
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|