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