candy_check 0.2.0 → 0.2.1
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 +4 -4
- data/README.md +8 -1
- data/lib/candy_check/app_store/receipt_collection.rb +4 -2
- 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/play_store/subscription_purchases/subscription_purchase.rb +1 -1
- data/lib/candy_check/version.rb +1 -1
- data/spec/app_store/receipt_collection_spec.rb +27 -0
- data/spec/app_store/subscription_verification_spec.rb +35 -2
- data/spec/app_store/verifier_spec.rb +24 -5
- metadata +2 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0ce2db5208c39a8d9cda9eb6308fae0ae95b34460a053036b16cf2496c5fdffb
|
4
|
+
data.tar.gz: 1eba58dfa69e6c8732ae1f2e33553d2386e85653c5ba867603253d531c2e0891
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0f58665b4df59f402b0e43f6f9a09a74b40caf8ab7fd6511732b7ff742b35f8a1a32f53821a777a423effdc386757aaec88965aa1a63686ca463b9e98f7fc54d
|
7
|
+
data.tar.gz: 245dad3d70c0eccc33c992a5420f2ac2ddb8521a524a06b0361418516d35ee9471f71c12bff4771ce6a00390ba16858d12b836060e463524cd2c3b0310ab59d8
|
data/README.md
CHANGED
@@ -4,7 +4,6 @@
|
|
4
4
|
[](https://travis-ci.org/jnbt/candy_check)
|
5
5
|
[](https://coveralls.io/r/jnbt/candy_check?branch=master)
|
6
6
|
[](https://codeclimate.com/github/jnbt/candy_check)
|
7
|
-
[](https://gemnasium.com/jnbt/candy_check)
|
8
7
|
[](http://inch-ci.org/github/jnbt/candy_check)
|
9
8
|
[](http://www.rubydoc.info/github/jnbt/candy_check/master)
|
10
9
|
|
@@ -76,6 +75,14 @@ For **subscription verification**, Apple also returns a list of the user's purch
|
|
76
75
|
verifier.verify_subscription(your_receipt_data, your_secret) # => ReceiptCollection or VerificationFailure
|
77
76
|
```
|
78
77
|
|
78
|
+
If you want to restrict the subscription verification to some specific products, pass their ids as an array:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
# ... create your verifier as above
|
82
|
+
product_ids = ['sk_product_id_1', 'sk_product_id_2'...]
|
83
|
+
verifier.verify_subscription(your_receipt_data, your_secret, product_ids) # => ReceiptCollection or VerificationFailure
|
84
|
+
```
|
85
|
+
|
79
86
|
Please see the class documentation for [`CandyCheck::AppStore::ReceiptCollection`](http://www.rubydoc.info/github/jnbt/candy_check/master/CandyCheck/AppStore/ReceiptCollection) for further details.
|
80
87
|
|
81
88
|
### PlayStore
|
@@ -8,9 +8,11 @@ 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
|
@@ -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)
|
@@ -66,7 +66,7 @@ module CandyCheck
|
|
66
66
|
# Get number of overdue days. If this is negative, it is not overdue.
|
67
67
|
# @return [Integer]
|
68
68
|
def overdue_days
|
69
|
-
(
|
69
|
+
(Time.now.utc.to_date - expires_at.to_date).to_i
|
70
70
|
end
|
71
71
|
|
72
72
|
# Get the auto renewal status as given by Google
|
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
|
@@ -42,16 +44,40 @@ describe CandyCheck::AppStore::ReceiptCollection do
|
|
42
44
|
end
|
43
45
|
end
|
44
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
|
+
|
67
|
+
end
|
68
|
+
|
45
69
|
describe 'unexpired trial subscription' do
|
46
70
|
two_days_from_now = DateTime.now + 2
|
47
71
|
|
48
72
|
let(:attributes) do
|
49
73
|
[{
|
50
74
|
'expires_date' => '2016-04-15 12:52:40 Etc/GMT',
|
75
|
+
'purchase_date' => '2016-04-15 12:52:40 Etc/GMT',
|
51
76
|
'is_trial_period' => 'true'
|
52
77
|
}, {
|
53
78
|
'expires_date' =>
|
54
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',
|
55
81
|
'is_trial_period' => 'true'
|
56
82
|
}]
|
57
83
|
end
|
@@ -68,4 +94,5 @@ describe CandyCheck::AppStore::ReceiptCollection do
|
|
68
94
|
subject.overdue_days.must_equal(-2)
|
69
95
|
end
|
70
96
|
end
|
97
|
+
|
71
98
|
end
|
@@ -38,20 +38,53 @@ describe CandyCheck::AppStore::SubscriptionVerification do
|
|
38
38
|
response = {
|
39
39
|
'status' => 0,
|
40
40
|
'latest_receipt_info' => [
|
41
|
-
{ 'item_id' => 'some_id' },
|
42
|
-
{ 'item_id' => 'some_other_id' }
|
41
|
+
{ 'item_id' => 'some_id', 'purchase_date' => '2016-04-15 12:52:40 Etc/GMT' },
|
42
|
+
{ 'item_id' => 'some_other_id', 'purchase_date' => '2016-04-15 12:52:40 Etc/GMT' }
|
43
43
|
]
|
44
44
|
}
|
45
45
|
with_mocked_response(response) do
|
46
46
|
result = subject.call!
|
47
47
|
result.must_be_instance_of CandyCheck::AppStore::ReceiptCollection
|
48
48
|
result.receipts.must_be_instance_of Array
|
49
|
+
result.receipts.size.must_equal(2)
|
49
50
|
last = result.receipts.last
|
50
51
|
last.must_be_instance_of CandyCheck::AppStore::Receipt
|
51
52
|
last.item_id.must_equal('some_other_id')
|
52
53
|
end
|
53
54
|
end
|
54
55
|
|
56
|
+
describe 'filtered product_ids' do
|
57
|
+
subject do
|
58
|
+
CandyCheck::AppStore::SubscriptionVerification.new(
|
59
|
+
endpoint,
|
60
|
+
data,
|
61
|
+
secret,
|
62
|
+
product_ids
|
63
|
+
)
|
64
|
+
end
|
65
|
+
let(:product_ids) { ['product_1'] }
|
66
|
+
|
67
|
+
it 'returns only filtered reciepts when specifc product_ids are reqested' do
|
68
|
+
response = {
|
69
|
+
'status' => 0,
|
70
|
+
'latest_receipt_info' => [
|
71
|
+
{ 'item_id' => 'some_id', 'product_id' => 'product_1', 'purchase_date' => '2016-04-15 12:52:40 Etc/GMT' },
|
72
|
+
{ 'item_id' => 'some_other_id', 'product_id' => 'product_1', 'purchase_date' => '2016-04-15 12:52:40 Etc/GMT' },
|
73
|
+
{ 'item_id' => 'some_id', 'product_id' => 'product_2', 'purchase_date' => '2016-04-15 12:52:40 Etc/GMT' }
|
74
|
+
]
|
75
|
+
}
|
76
|
+
with_mocked_response(response) do
|
77
|
+
result = subject.call!
|
78
|
+
result.must_be_instance_of CandyCheck::AppStore::ReceiptCollection
|
79
|
+
result.receipts.must_be_instance_of Array
|
80
|
+
result.receipts.size.must_equal(2)
|
81
|
+
last = result.receipts.last
|
82
|
+
last.must_be_instance_of CandyCheck::AppStore::Receipt
|
83
|
+
last.item_id.must_equal('some_other_id')
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
55
88
|
private
|
56
89
|
|
57
90
|
DummyClient = Struct.new(:response) do
|
@@ -89,7 +89,7 @@ describe CandyCheck::AppStore::Verifier do
|
|
89
89
|
subject.verify_subscription(
|
90
90
|
data, secret
|
91
91
|
).must_be_same_as receipt_collection
|
92
|
-
assert_recorded([production_endpoint, data, secret])
|
92
|
+
assert_recorded([production_endpoint, data, secret, nil])
|
93
93
|
end
|
94
94
|
end
|
95
95
|
|
@@ -97,7 +97,7 @@ describe CandyCheck::AppStore::Verifier do
|
|
97
97
|
failure = get_failure(21_000)
|
98
98
|
with_mocked_verifier(failure) do
|
99
99
|
subject.verify_subscription(data, secret).must_be_same_as failure
|
100
|
-
assert_recorded([production_endpoint, data, secret])
|
100
|
+
assert_recorded([production_endpoint, data, secret, nil])
|
101
101
|
end
|
102
102
|
end
|
103
103
|
|
@@ -106,11 +106,21 @@ describe CandyCheck::AppStore::Verifier do
|
|
106
106
|
with_mocked_verifier(failure, receipt) do
|
107
107
|
subject.verify_subscription(data, secret).must_be_same_as receipt
|
108
108
|
assert_recorded(
|
109
|
-
[production_endpoint, data, secret],
|
110
|
-
[sandbox_endpoint, data, secret]
|
109
|
+
[production_endpoint, data, secret, nil],
|
110
|
+
[sandbox_endpoint, data, secret, nil]
|
111
111
|
)
|
112
112
|
end
|
113
113
|
end
|
114
|
+
|
115
|
+
it 'passed the product_ids' do
|
116
|
+
product_ids = ['product_1']
|
117
|
+
with_mocked_verifier(receipt_collection) do
|
118
|
+
subject.verify_subscription(
|
119
|
+
data, secret, product_ids
|
120
|
+
).must_be_same_as receipt_collection
|
121
|
+
assert_recorded([production_endpoint, data, secret, product_ids])
|
122
|
+
end
|
123
|
+
end
|
114
124
|
end
|
115
125
|
|
116
126
|
private
|
@@ -134,8 +144,17 @@ describe CandyCheck::AppStore::Verifier do
|
|
134
144
|
CandyCheck::AppStore::VerificationFailure.fetch(code)
|
135
145
|
end
|
136
146
|
|
137
|
-
DummyAppStoreVerification
|
147
|
+
class DummyAppStoreVerification
|
148
|
+
attr_reader :endpoint, :data, :secret, :product_ids
|
138
149
|
attr_accessor :results
|
150
|
+
|
151
|
+
def initialize(endpoint, data, secret, product_ids = nil)
|
152
|
+
@endpoint = endpoint
|
153
|
+
@data = data
|
154
|
+
@secret = secret
|
155
|
+
@product_ids = product_ids
|
156
|
+
end
|
157
|
+
|
139
158
|
def call!
|
140
159
|
results.shift
|
141
160
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: candy_check
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jonas Thiel
|
@@ -362,8 +362,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
362
362
|
- !ruby/object:Gem::Version
|
363
363
|
version: '0'
|
364
364
|
requirements: []
|
365
|
-
|
366
|
-
rubygems_version: 2.7.9
|
365
|
+
rubygems_version: 3.0.3
|
367
366
|
signing_key:
|
368
367
|
specification_version: 4
|
369
368
|
summary: Check and verify in-app receipts
|