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.
Files changed (76) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +23 -0
  3. data/.ruby-version +1 -1
  4. data/.travis.yml +7 -8
  5. data/Guardfile +42 -0
  6. data/MIGRATION_GUIDE_0_2_0.md +141 -0
  7. data/README.md +86 -26
  8. data/Rakefile +1 -1
  9. data/candy_check.gemspec +33 -25
  10. data/lib/candy_check/app_store/receipt_collection.rb +5 -3
  11. data/lib/candy_check/app_store/subscription_verification.rb +25 -1
  12. data/lib/candy_check/app_store/verification.rb +1 -1
  13. data/lib/candy_check/app_store/verifier.rb +11 -11
  14. data/lib/candy_check/cli/app.rb +16 -33
  15. data/lib/candy_check/cli/commands/play_store.rb +12 -13
  16. data/lib/candy_check/play_store.rb +20 -10
  17. data/lib/candy_check/play_store/acknowledger.rb +19 -0
  18. data/lib/candy_check/play_store/android_publisher_service.rb +6 -0
  19. data/lib/candy_check/play_store/product_acknowledgements/acknowledgement.rb +45 -0
  20. data/lib/candy_check/play_store/product_acknowledgements/response.rb +24 -0
  21. data/lib/candy_check/play_store/product_purchases/product_purchase.rb +90 -0
  22. data/lib/candy_check/play_store/product_purchases/product_verification.rb +53 -0
  23. data/lib/candy_check/play_store/subscription_purchases/subscription_purchase.rb +154 -0
  24. data/lib/candy_check/play_store/subscription_purchases/subscription_verification.rb +55 -0
  25. data/lib/candy_check/play_store/verification_failure.rb +8 -6
  26. data/lib/candy_check/play_store/verifier.rb +24 -49
  27. data/lib/candy_check/utils/config.rb +5 -3
  28. data/lib/candy_check/version.rb +1 -1
  29. data/spec/app_store/receipt_collection_spec.rb +33 -0
  30. data/spec/app_store/subscription_verification_spec.rb +35 -2
  31. data/spec/app_store/verifier_spec.rb +24 -5
  32. data/spec/candy_check_spec.rb +2 -2
  33. data/spec/cli/commands/play_store_spec.rb +10 -43
  34. data/spec/fixtures/play_store/random_dummy_key.json +12 -0
  35. data/spec/fixtures/vcr_cassettes/play_store/product_acknowledgements/acknowledged.yml +105 -0
  36. data/spec/fixtures/vcr_cassettes/play_store/product_acknowledgements/already_acknowledged.yml +124 -0
  37. data/spec/fixtures/vcr_cassettes/play_store/product_acknowledgements/refunded.yml +122 -0
  38. data/spec/fixtures/vcr_cassettes/play_store/product_purchases/permission_denied.yml +196 -0
  39. data/spec/fixtures/vcr_cassettes/play_store/product_purchases/response_with_empty_body.yml +183 -0
  40. data/spec/fixtures/vcr_cassettes/play_store/product_purchases/valid_but_not_consumed.yml +122 -0
  41. data/spec/fixtures/vcr_cassettes/play_store/subscription_purchases/permission_denied.yml +196 -0
  42. data/spec/fixtures/vcr_cassettes/play_store/subscription_purchases/valid_but_expired.yml +127 -0
  43. data/spec/play_store/acknowledger_spec.rb +48 -0
  44. data/spec/play_store/product_acknowledgements/acknowledgement_spec.rb +54 -0
  45. data/spec/play_store/product_acknowledgements/response_spec.rb +66 -0
  46. data/spec/play_store/product_purchases/product_purchase_spec.rb +110 -0
  47. data/spec/play_store/product_purchases/product_verification_spec.rb +49 -0
  48. data/spec/play_store/subscription_purchases/subscription_purchase_spec.rb +237 -0
  49. data/spec/play_store/subscription_purchases/subscription_verification_spec.rb +65 -0
  50. data/spec/play_store/verification_failure_spec.rb +18 -18
  51. data/spec/play_store/verifier_spec.rb +32 -96
  52. data/spec/spec_helper.rb +32 -10
  53. metadata +175 -75
  54. data/lib/candy_check/play_store/client.rb +0 -126
  55. data/lib/candy_check/play_store/config.rb +0 -51
  56. data/lib/candy_check/play_store/discovery_repository.rb +0 -33
  57. data/lib/candy_check/play_store/receipt.rb +0 -81
  58. data/lib/candy_check/play_store/subscription.rb +0 -138
  59. data/lib/candy_check/play_store/subscription_verification.rb +0 -30
  60. data/lib/candy_check/play_store/verification.rb +0 -48
  61. data/spec/fixtures/api_cache.dump +0 -1
  62. data/spec/fixtures/play_store/api_cache.dump +0 -1
  63. data/spec/fixtures/play_store/auth_failure.txt +0 -18
  64. data/spec/fixtures/play_store/auth_success.txt +0 -20
  65. data/spec/fixtures/play_store/discovery.txt +0 -2841
  66. data/spec/fixtures/play_store/dummy.p12 +0 -0
  67. data/spec/fixtures/play_store/empty.txt +0 -17
  68. data/spec/fixtures/play_store/products_failure.txt +0 -29
  69. data/spec/fixtures/play_store/products_success.txt +0 -22
  70. data/spec/play_store/client_spec.rb +0 -125
  71. data/spec/play_store/config_spec.rb +0 -96
  72. data/spec/play_store/discovery_respository_spec.rb +0 -31
  73. data/spec/play_store/receipt_spec.rb +0 -88
  74. data/spec/play_store/subscription_spec.rb +0 -138
  75. data/spec/play_store/subscription_verification_spec.rb +0 -98
  76. data/spec/play_store/verification_spec.rb +0 -82
@@ -0,0 +1,110 @@
1
+ require "spec_helper"
2
+
3
+ describe CandyCheck::PlayStore::ProductPurchases::ProductPurchase do
4
+ subject { CandyCheck::PlayStore::ProductPurchases::ProductPurchase.new(fake_product_purchase) }
5
+
6
+ describe "valid and non-consumed product" do
7
+ let(:fake_product_purchase) do
8
+ FakeProductPurchase.new(
9
+ consumption_state: 0,
10
+ developer_payload: "payload that gets stored and returned",
11
+ kind: "androidpublisher#productPurchase",
12
+ order_id: "ABC123",
13
+ purchase_state: 0,
14
+ purchase_time_millis: 1421676237413,
15
+ )
16
+ end
17
+
18
+ it "is valid?" do
19
+ subject.valid?.must_be_true
20
+ end
21
+
22
+ it "is not consumed" do
23
+ subject.consumed?.must_be_false
24
+ end
25
+
26
+ it "returns the purchase_state" do
27
+ subject.purchase_state.must_equal 0
28
+ end
29
+
30
+ it "returns the consumption_state" do
31
+ subject.consumption_state.must_equal 0
32
+ end
33
+
34
+ it "returns the developer_payload" do
35
+ subject.developer_payload.must_equal "payload that gets stored and returned"
36
+ end
37
+
38
+ it "returns the kind" do
39
+ subject.kind.must_equal "androidpublisher#productPurchase"
40
+ end
41
+
42
+ it "returns the purchase_time_millis" do
43
+ subject.purchase_time_millis.must_equal 1_421_676_237_413
44
+ end
45
+
46
+ it "returns the purchased_at" do
47
+ expected = DateTime.new(2015, 1, 19, 14, 3, 57)
48
+ subject.purchased_at.must_equal expected
49
+ end
50
+ end
51
+
52
+ describe "valid and consumed product" do
53
+ let(:fake_product_purchase) do
54
+ FakeProductPurchase.new(
55
+ consumption_state: 1,
56
+ developer_payload: "payload that gets stored and returned",
57
+ kind: "androidpublisher#productPurchase",
58
+ order_id: "ABC123",
59
+ purchase_state: 0,
60
+ purchase_time_millis: 1421676237413,
61
+ )
62
+ end
63
+
64
+ it "is valid?" do
65
+ subject.valid?.must_be_true
66
+ end
67
+
68
+ it "is consumed?" do
69
+ subject.consumed?.must_be_true
70
+ end
71
+ end
72
+
73
+ describe "non-valid product" do
74
+ let(:fake_product_purchase) do
75
+ FakeProductPurchase.new(
76
+ consumption_state: 0,
77
+ developer_payload: "payload that gets stored and returned",
78
+ kind: "androidpublisher#productPurchase",
79
+ order_id: "ABC123",
80
+ purchase_state: 1,
81
+ purchase_time_millis: 1421676237413,
82
+ )
83
+ end
84
+
85
+ it "is valid?" do
86
+ subject.valid?.must_be_false
87
+ end
88
+ end
89
+
90
+ private
91
+
92
+ class FakeProductPurchase
93
+ FIELDS = [
94
+ :consumption_state,
95
+ :developer_payload,
96
+ :kind,
97
+ :order_id,
98
+ :purchase_state,
99
+ :purchase_time_millis,
100
+ ].freeze
101
+
102
+ attr_accessor *FIELDS
103
+
104
+ def initialize(hash)
105
+ FIELDS.each do |key|
106
+ self.public_send("#{key}=", hash[key])
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,49 @@
1
+ require "spec_helper"
2
+
3
+ describe CandyCheck::PlayStore::ProductPurchases::ProductVerification do
4
+ subject do
5
+ CandyCheck::PlayStore::ProductPurchases::ProductVerification.new(
6
+ package_name: package_name,
7
+ product_id: product_id,
8
+ token: token,
9
+ authorization: authorization,
10
+ )
11
+ end
12
+ let(:package_name) { "my_package_name" }
13
+ let(:product_id) { "my_product_id" }
14
+ let(:token) { "my_token" }
15
+ let(:json_key_file) { File.expand_path("../../fixtures/play_store/random_dummy_key.json", __dir__) }
16
+
17
+ let(:authorization) { CandyCheck::PlayStore.authorization(json_key_file) }
18
+
19
+ describe "valid" do
20
+ it "returns a product purchase" do
21
+ VCR.use_cassette("play_store/product_purchases/valid_but_not_consumed") do
22
+ result = subject.call!
23
+ result.must_be_instance_of CandyCheck::PlayStore::ProductPurchases::ProductPurchase
24
+ result.valid?.must_be_true
25
+ result.consumed?.must_be_false
26
+ end
27
+ end
28
+ end
29
+
30
+ describe "failure" do
31
+ it "returns a verification failure" do
32
+ VCR.use_cassette("play_store/product_purchases/permission_denied") do
33
+ result = subject.call!
34
+ result.must_be_instance_of CandyCheck::PlayStore::VerificationFailure
35
+ result.code.must_equal 401
36
+ end
37
+ end
38
+ end
39
+
40
+ describe "empty" do
41
+ it "returns a verification failure" do
42
+ VCR.use_cassette("play_store/product_purchases/response_with_empty_body") do
43
+ result = subject.call!
44
+ result.must_be_instance_of CandyCheck::PlayStore::VerificationFailure
45
+ result.code.must_equal(-1)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,237 @@
1
+ require "spec_helper"
2
+
3
+ describe CandyCheck::PlayStore::SubscriptionPurchases::SubscriptionPurchase do
4
+ subject { CandyCheck::PlayStore::SubscriptionPurchases::SubscriptionPurchase.new(fake_subscription_purchase) }
5
+
6
+ describe "expired and canceled subscription" do
7
+ let(:fake_subscription_purchase) do
8
+ FakeSubscriptionPurchase.new(
9
+ kind: "androidpublisher#subscriptionPurchase",
10
+ start_time_millis: 1459540113244,
11
+ expiry_time_millis: 1462132088610,
12
+ auto_renewing: false,
13
+ developer_payload: "payload that gets stored and returned",
14
+ cancel_reason: 0,
15
+ payment_state: 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(:fake_subscription_purchase) do
70
+ FakeSubscriptionPurchase.new(
71
+ kind: "androidpublisher#subscriptionPurchase",
72
+ start_time_millis: 1459540113244,
73
+ expiry_time_millis: (two_days_from_now.to_time.to_i * 1000),
74
+ auto_renewing: true,
75
+ developer_payload: "payload that gets stored and returned",
76
+ cancel_reason: 0,
77
+ payment_state: 1,
78
+ )
79
+ end
80
+
81
+ it "is expired?" do
82
+ subject.expired?.must_be_false
83
+ end
84
+
85
+ it "is two days left until it is overdue" do
86
+ subject.overdue_days.must_equal(-2)
87
+ end
88
+ end
89
+
90
+ describe "expired due to payment failure" do
91
+ let(:fake_subscription_purchase) do
92
+ FakeSubscriptionPurchase.new(
93
+ kind: "androidpublisher#subscriptionPurchase",
94
+ start_time_millis: 1459540113244,
95
+ expiry_time_millis: 1462132088610,
96
+ auto_renewing: true,
97
+ developer_payload: "payload that gets stored and returned",
98
+ cancel_reason: 1,
99
+ payment_state: 1,
100
+ )
101
+ end
102
+
103
+ it "is expired?" do
104
+ subject.expired?.must_be_true
105
+ end
106
+
107
+ it "is payment_failed?" do
108
+ subject.payment_failed?.must_be_true
109
+ end
110
+ end
111
+
112
+ describe "subscription cancelation by user" do
113
+ describe "when subscription is not canceled" do
114
+ let(:fake_subscription_purchase) do
115
+ FakeSubscriptionPurchase.new(
116
+ kind: "androidpublisher#subscriptionPurchase",
117
+ start_time_millis: 1459540113244,
118
+ expiry_time_millis: 1462132088610,
119
+ auto_renewing: true,
120
+ developer_payload: "payload that gets stored and returned",
121
+ payment_state: 1,
122
+ )
123
+ end
124
+
125
+ it "is not canceled?" do
126
+ subject.canceled_by_user?.must_be_false
127
+ end
128
+
129
+ it "returns nil user_cancellation_time_millis" do
130
+ subject.user_cancellation_time_millis.must_be_nil
131
+ end
132
+
133
+ it "returns nil canceled_at" do
134
+ subject.canceled_at.must_be_nil
135
+ end
136
+ end
137
+
138
+ describe "when subscription is canceled" do
139
+ let(:fake_subscription_purchase) do
140
+ FakeSubscriptionPurchase.new(
141
+ kind: "androidpublisher#subscriptionPurchase",
142
+ start_time_millis: 1459540113244,
143
+ expiry_time_millis: 1462132088610,
144
+ user_cancellation_time_millis: 1461872888000,
145
+ auto_renewing: true,
146
+ developer_payload: "payload that gets stored and returned",
147
+ cancel_reason: 0,
148
+ payment_state: 1,
149
+ )
150
+ end
151
+
152
+ it "is canceled?" do
153
+ subject.canceled_by_user?.must_be_true
154
+ end
155
+
156
+ it "returns the user_cancellation_time_millis" do
157
+ subject.user_cancellation_time_millis.must_equal 1_461_872_888_000
158
+ end
159
+
160
+ it "returns the starts_at" do
161
+ expected = DateTime.new(2016, 4, 28, 19, 48, 8)
162
+ subject.canceled_at.must_equal expected
163
+ end
164
+ end
165
+ end
166
+
167
+ describe "expired with pending payment" do
168
+ let(:fake_subscription_purchase) do
169
+ FakeSubscriptionPurchase.new(
170
+ kind: "androidpublisher#subscriptionPurchase",
171
+ start_time_millis: 1459540113244,
172
+ expiry_time_millis: 1462132088610,
173
+ auto_renewing: true,
174
+ developer_payload: "payload that gets stored and returned",
175
+ cancel_reason: 0,
176
+ payment_state: 0,
177
+ )
178
+ end
179
+
180
+ it "is expired?" do
181
+ subject.expired?.must_be_true
182
+ end
183
+
184
+ it "is payment_pending?" do
185
+ subject.payment_pending?.must_be_true
186
+ end
187
+ end
188
+
189
+ describe "trial" do
190
+ let(:fake_subscription_purchase) do
191
+ FakeSubscriptionPurchase.new(
192
+ kind: "androidpublisher#subscriptionPurchase",
193
+ start_time_millis: 1459540113244,
194
+ expiry_time_millis: 1462132088610,
195
+ auto_renewing: false,
196
+ developer_payload: "payload that gets stored and returned",
197
+ cancel_reason: 0,
198
+ payment_state: 1,
199
+ price_currency_code: "SOMECODE",
200
+ price_amount_micros: 0,
201
+ )
202
+ end
203
+
204
+ it "is trial?" do
205
+ subject.trial?.must_be_true
206
+ end
207
+
208
+ it "returns the price_currency_code" do
209
+ subject.price_currency_code.must_equal "SOMECODE"
210
+ end
211
+ end
212
+
213
+ private
214
+
215
+ class FakeSubscriptionPurchase
216
+ FIELDS = [
217
+ :kind,
218
+ :start_time_millis,
219
+ :expiry_time_millis,
220
+ :user_cancellation_time_millis,
221
+ :auto_renewing,
222
+ :developer_payload,
223
+ :cancel_reason,
224
+ :payment_state,
225
+ :price_amount_micros,
226
+ :price_currency_code,
227
+ ].freeze
228
+
229
+ attr_accessor *FIELDS
230
+
231
+ def initialize(hash)
232
+ FIELDS.each do |key|
233
+ self.public_send("#{key}=", hash[key])
234
+ end
235
+ end
236
+ end
237
+ end
@@ -0,0 +1,65 @@
1
+ require "spec_helper"
2
+
3
+ describe CandyCheck::PlayStore::SubscriptionPurchases::SubscriptionVerification do
4
+ subject do
5
+ CandyCheck::PlayStore::SubscriptionPurchases::SubscriptionVerification.new(
6
+ package_name: package_name,
7
+ subscription_id: subscription_id,
8
+ token: token,
9
+ authorization: authorization,
10
+ )
11
+ end
12
+
13
+ let(:json_key_file) { File.expand_path("../../fixtures/play_store/random_dummy_key.json", __dir__) }
14
+ let(:authorization) { CandyCheck::PlayStore.authorization(json_key_file) }
15
+
16
+ let(:package_name) { "my_package_name" }
17
+ let(:subscription_id) { "my_subscription_id" }
18
+ let(:token) { "my_token" }
19
+
20
+ describe "valid" do
21
+ it "returns a subscription" do
22
+ VCR.use_cassette("play_store/subscription_purchases/valid_but_expired") do
23
+ result = subject.call!
24
+
25
+ result.must_be_instance_of CandyCheck::PlayStore::SubscriptionPurchases::SubscriptionPurchase
26
+ result.expired?.must_be_true
27
+ end
28
+ end
29
+ end
30
+
31
+ describe "failure" do
32
+ it "returns a verification failure" do
33
+ VCR.use_cassette("play_store/subscription_purchases/permission_denied") do
34
+ result = subject.call!
35
+ result.must_be_instance_of CandyCheck::PlayStore::VerificationFailure
36
+ result.code.must_equal 401
37
+ end
38
+ end
39
+ end
40
+
41
+ describe "empty" do
42
+ let(:response) do
43
+ {}
44
+ end
45
+
46
+ it "returns a verification failure" do
47
+ result = subject.call!
48
+ result.must_be_instance_of CandyCheck::PlayStore::VerificationFailure
49
+ result.code.must_equal(-1)
50
+ end
51
+ end
52
+
53
+ describe "invalid response kind" do
54
+ let(:response) do
55
+ {
56
+ "kind" => "something weird",
57
+ }
58
+ end
59
+
60
+ it "returns a verification failure" do
61
+ result = subject.call!
62
+ result.must_be_instance_of CandyCheck::PlayStore::VerificationFailure
63
+ end
64
+ end
65
+ end