candy_check 0.1.2 → 0.2.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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +5 -12
  3. data/Guardfile +42 -0
  4. data/MIGRATION_GUIDE_0_2_0.md +141 -0
  5. data/README.md +49 -26
  6. data/candy_check.gemspec +32 -26
  7. data/lib/candy_check/cli/app.rb +16 -33
  8. data/lib/candy_check/cli/commands/play_store.rb +12 -13
  9. data/lib/candy_check/play_store.rb +17 -10
  10. data/lib/candy_check/play_store/android_publisher_service.rb +6 -0
  11. data/lib/candy_check/play_store/product_purchases/product_purchase.rb +90 -0
  12. data/lib/candy_check/play_store/product_purchases/product_verification.rb +53 -0
  13. data/lib/candy_check/play_store/subscription_purchases/subscription_purchase.rb +141 -0
  14. data/lib/candy_check/play_store/subscription_purchases/subscription_verification.rb +55 -0
  15. data/lib/candy_check/play_store/verification_failure.rb +8 -6
  16. data/lib/candy_check/play_store/verifier.rb +24 -49
  17. data/lib/candy_check/version.rb +1 -1
  18. data/spec/candy_check_spec.rb +2 -2
  19. data/spec/cli/commands/play_store_spec.rb +10 -43
  20. data/spec/fixtures/play_store/random_dummy_key.json +12 -0
  21. data/spec/fixtures/vcr_cassettes/play_store/product_purchases/permission_denied.yml +196 -0
  22. data/spec/fixtures/vcr_cassettes/play_store/product_purchases/response_with_empty_body.yml +183 -0
  23. data/spec/fixtures/vcr_cassettes/play_store/product_purchases/valid_but_not_consumed.yml +122 -0
  24. data/spec/fixtures/vcr_cassettes/play_store/subscription_purchases/permission_denied.yml +196 -0
  25. data/spec/fixtures/vcr_cassettes/play_store/subscription_purchases/valid_but_expired.yml +127 -0
  26. data/spec/play_store/product_purchases/product_purchase_spec.rb +110 -0
  27. data/spec/play_store/product_purchases/product_verification_spec.rb +49 -0
  28. data/spec/play_store/subscription_purchases/subscription_purchase_spec.rb +181 -0
  29. data/spec/play_store/subscription_purchases/subscription_verification_spec.rb +65 -0
  30. data/spec/play_store/verification_failure_spec.rb +18 -18
  31. data/spec/play_store/verifier_spec.rb +32 -96
  32. data/spec/spec_helper.rb +24 -11
  33. metadata +120 -47
  34. data/lib/candy_check/play_store/client.rb +0 -139
  35. data/lib/candy_check/play_store/config.rb +0 -51
  36. data/lib/candy_check/play_store/discovery_repository.rb +0 -33
  37. data/lib/candy_check/play_store/receipt.rb +0 -81
  38. data/lib/candy_check/play_store/subscription.rb +0 -139
  39. data/lib/candy_check/play_store/subscription_verification.rb +0 -30
  40. data/lib/candy_check/play_store/verification.rb +0 -48
  41. data/spec/fixtures/api_cache.dump +0 -1
  42. data/spec/fixtures/play_store/api_cache.dump +0 -1
  43. data/spec/fixtures/play_store/auth_failure.txt +0 -18
  44. data/spec/fixtures/play_store/auth_success.txt +0 -20
  45. data/spec/fixtures/play_store/discovery.txt +0 -2841
  46. data/spec/fixtures/play_store/dummy.p12 +0 -0
  47. data/spec/fixtures/play_store/empty.txt +0 -17
  48. data/spec/fixtures/play_store/products_failure.txt +0 -29
  49. data/spec/fixtures/play_store/products_success.txt +0 -22
  50. data/spec/play_store/client_spec.rb +0 -125
  51. data/spec/play_store/config_spec.rb +0 -96
  52. data/spec/play_store/discovery_respository_spec.rb +0 -31
  53. data/spec/play_store/receipt_spec.rb +0 -88
  54. data/spec/play_store/subscription_spec.rb +0 -138
  55. data/spec/play_store/subscription_verification_spec.rb +0 -97
  56. data/spec/play_store/verification_spec.rb +0 -81
@@ -0,0 +1,122 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: https://www.googleapis.com/oauth2/v4/token
6
+ body:
7
+ encoding: ASCII-8BIT
8
+ string: params
9
+ headers:
10
+ User-Agent:
11
+ - Faraday v0.15.4
12
+ Content-Type:
13
+ - application/x-www-form-urlencoded
14
+ Accept-Encoding:
15
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
16
+ Accept:
17
+ - "*/*"
18
+ response:
19
+ status:
20
+ code: 200
21
+ message: OK
22
+ headers:
23
+ Content-Type:
24
+ - application/json; charset=utf-8
25
+ Vary:
26
+ - Origin
27
+ - Referer
28
+ - X-Origin
29
+ Date:
30
+ - Fri, 04 Oct 2019 09:25:53 GMT
31
+ Server:
32
+ - scaffolding on HTTPServer2
33
+ Cache-Control:
34
+ - private
35
+ X-Xss-Protection:
36
+ - '0'
37
+ X-Frame-Options:
38
+ - SAMEORIGIN
39
+ X-Content-Type-Options:
40
+ - nosniff
41
+ Alt-Svc:
42
+ - quic=":443"; ma=2592000; v="46,43",h3-Q048=":443"; ma=2592000,h3-Q046=":443";
43
+ ma=2592000,h3-Q043=":443"; ma=2592000
44
+ Transfer-Encoding:
45
+ - chunked
46
+ body:
47
+ encoding: ASCII-8BIT
48
+ string: |-
49
+ {
50
+ "access_token": "some token",
51
+ "expires_in": 3600,
52
+ "token_type": "Bearer"
53
+ }
54
+ http_version:
55
+ recorded_at: Fri, 04 Oct 2019 09:25:53 GMT
56
+ - request:
57
+ method: get
58
+ uri: https://www.googleapis.com/androidpublisher/v3/applications/my_package_name/purchases/products/my_product_id/tokens/my_token
59
+ body:
60
+ encoding: UTF-8
61
+ string: ''
62
+ headers:
63
+ User-Agent:
64
+ - unknown/0.0.0 google-api-ruby-client/0.28.7 Linux/5.0.0-29-generic (gzip)
65
+ Accept:
66
+ - "*/*"
67
+ Accept-Encoding:
68
+ - gzip,deflate
69
+ Date:
70
+ - Fri, 04 Oct 2019 09:25:53 GMT
71
+ Authorization:
72
+ - Bearer some_bearer_token
73
+ Content-Type:
74
+ - application/x-www-form-urlencoded
75
+ response:
76
+ status:
77
+ code: 200
78
+ message: OK
79
+ headers:
80
+ Expires:
81
+ - Fri, 04 Oct 2019 09:25:54 GMT
82
+ Date:
83
+ - Fri, 04 Oct 2019 09:25:54 GMT
84
+ Cache-Control:
85
+ - private, max-age=0, must-revalidate, no-transform
86
+ Etag:
87
+ - '""'
88
+ Vary:
89
+ - Origin
90
+ - X-Origin
91
+ Content-Type:
92
+ - application/json; charset=UTF-8
93
+ Content-Encoding:
94
+ - gzip
95
+ X-Content-Type-Options:
96
+ - nosniff
97
+ X-Frame-Options:
98
+ - SAMEORIGIN
99
+ X-Xss-Protection:
100
+ - 1; mode=block
101
+ Server:
102
+ - GSE
103
+ Alt-Svc:
104
+ - quic=":443"; ma=2592000; v="46,43",h3-Q048=":443"; ma=2592000,h3-Q046=":443";
105
+ ma=2592000,h3-Q043=":443"; ma=2592000
106
+ Transfer-Encoding:
107
+ - chunked
108
+ body:
109
+ encoding: UTF-8
110
+ string: |
111
+ {
112
+ "kind": "androidpublisher#productPurchase",
113
+ "purchaseTimeMillis": "123456789",
114
+ "purchaseState": 0,
115
+ "consumptionState": 0,
116
+ "developerPayload": "",
117
+ "orderId": "123",
118
+ "acknowledgementState": 1
119
+ }
120
+ http_version:
121
+ recorded_at: Fri, 04 Oct 2019 09:25:54 GMT
122
+ recorded_with: VCR 5.0.0
@@ -0,0 +1,196 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: https://www.googleapis.com/oauth2/v4/token
6
+ body:
7
+ encoding: ASCII-8BIT
8
+ string: params
9
+ headers:
10
+ User-Agent:
11
+ - Faraday v0.15.4
12
+ Content-Type:
13
+ - application/x-www-form-urlencoded
14
+ Accept-Encoding:
15
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
16
+ Accept:
17
+ - "*/*"
18
+ response:
19
+ status:
20
+ code: 200
21
+ message: OK
22
+ headers:
23
+ Content-Type:
24
+ - application/json; charset=utf-8
25
+ Vary:
26
+ - Origin
27
+ - Referer
28
+ - X-Origin
29
+ Date:
30
+ - Thu, 10 Oct 2019 14:07:52 GMT
31
+ Server:
32
+ - scaffolding on HTTPServer2
33
+ Cache-Control:
34
+ - private
35
+ X-Xss-Protection:
36
+ - '0'
37
+ X-Frame-Options:
38
+ - SAMEORIGIN
39
+ X-Content-Type-Options:
40
+ - nosniff
41
+ Alt-Svc:
42
+ - quic=":443"; ma=2592000; v="46,43",h3-Q048=":443"; ma=2592000,h3-Q046=":443";
43
+ ma=2592000,h3-Q043=":443"; ma=2592000
44
+ Transfer-Encoding:
45
+ - chunked
46
+ body:
47
+ encoding: ASCII-8BIT
48
+ string: |-
49
+ {
50
+ "access_token": "my_token",
51
+ "expires_in": 3600,
52
+ "token_type": "Bearer"
53
+ }
54
+ http_version:
55
+ recorded_at: Thu, 10 Oct 2019 14:07:52 GMT
56
+ - request:
57
+ method: get
58
+ uri: https://www.googleapis.com/androidpublisher/v3/applications/my_package_name/purchases/subscriptions/my_subscription_id/tokens/my_token
59
+ body:
60
+ encoding: UTF-8
61
+ string: ''
62
+ headers:
63
+ User-Agent:
64
+ - unknown/0.0.0 google-api-ruby-client/0.28.7 Linux/5.0.0-31-generic (gzip)
65
+ Accept:
66
+ - "*/*"
67
+ Accept-Encoding:
68
+ - gzip,deflate
69
+ Date:
70
+ - Thu, 10 Oct 2019 14:07:52 GMT
71
+ Authorization:
72
+ - Bearer my_bearer
73
+ Content-Type:
74
+ - application/x-www-form-urlencoded
75
+ response:
76
+ status:
77
+ code: 401
78
+ message: Unauthorized
79
+ headers:
80
+ Vary:
81
+ - Origin
82
+ - X-Origin
83
+ Www-Authenticate:
84
+ - Bearer realm="https://accounts.google.com/", error=invalid_token
85
+ Content-Type:
86
+ - application/json; charset=UTF-8
87
+ Content-Encoding:
88
+ - gzip
89
+ Date:
90
+ - Thu, 10 Oct 2019 14:07:52 GMT
91
+ Expires:
92
+ - Thu, 10 Oct 2019 14:07:52 GMT
93
+ Cache-Control:
94
+ - private, max-age=0
95
+ X-Content-Type-Options:
96
+ - nosniff
97
+ X-Frame-Options:
98
+ - SAMEORIGIN
99
+ X-Xss-Protection:
100
+ - 1; mode=block
101
+ Server:
102
+ - GSE
103
+ Alt-Svc:
104
+ - quic=":443"; ma=2592000; v="46,43",h3-Q048=":443"; ma=2592000,h3-Q046=":443";
105
+ ma=2592000,h3-Q043=":443"; ma=2592000
106
+ Transfer-Encoding:
107
+ - chunked
108
+ body:
109
+ encoding: UTF-8
110
+ string: |
111
+ {
112
+ "error": {
113
+ "errors": [
114
+ {
115
+ "domain": "androidpublisher",
116
+ "reason": "permissionDenied",
117
+ "message": "The current user has insufficient permissions to perform the requested operation."
118
+ }
119
+ ],
120
+ "code": 401,
121
+ "message": "The current user has insufficient permissions to perform the requested operation."
122
+ }
123
+ }
124
+ http_version:
125
+ recorded_at: Thu, 10 Oct 2019 14:07:52 GMT
126
+ - request:
127
+ method: get
128
+ uri: https://www.googleapis.com/androidpublisher/v3/applications/my_package_name/purchases/subscriptions/my_subscription_id/tokens/my_token
129
+ body:
130
+ encoding: UTF-8
131
+ string: ''
132
+ headers:
133
+ User-Agent:
134
+ - unknown/0.0.0 google-api-ruby-client/0.28.7 Linux/5.0.0-31-generic (gzip)
135
+ Accept:
136
+ - "*/*"
137
+ Accept-Encoding:
138
+ - gzip,deflate
139
+ Date:
140
+ - Thu, 10 Oct 2019 14:07:53 GMT
141
+ Authorization:
142
+ - Bearer my_bearer
143
+ Content-Type:
144
+ - application/x-www-form-urlencoded
145
+ response:
146
+ status:
147
+ code: 401
148
+ message: Unauthorized
149
+ headers:
150
+ Vary:
151
+ - Origin
152
+ - X-Origin
153
+ Www-Authenticate:
154
+ - Bearer realm="https://accounts.google.com/", error=invalid_token
155
+ Content-Type:
156
+ - application/json; charset=UTF-8
157
+ Content-Encoding:
158
+ - gzip
159
+ Date:
160
+ - Thu, 10 Oct 2019 14:07:53 GMT
161
+ Expires:
162
+ - Thu, 10 Oct 2019 14:07:53 GMT
163
+ Cache-Control:
164
+ - private, max-age=0
165
+ X-Content-Type-Options:
166
+ - nosniff
167
+ X-Frame-Options:
168
+ - SAMEORIGIN
169
+ X-Xss-Protection:
170
+ - 1; mode=block
171
+ Server:
172
+ - GSE
173
+ Alt-Svc:
174
+ - quic=":443"; ma=2592000; v="46,43",h3-Q048=":443"; ma=2592000,h3-Q046=":443";
175
+ ma=2592000,h3-Q043=":443"; ma=2592000
176
+ Transfer-Encoding:
177
+ - chunked
178
+ body:
179
+ encoding: UTF-8
180
+ string: |
181
+ {
182
+ "error": {
183
+ "errors": [
184
+ {
185
+ "domain": "androidpublisher",
186
+ "reason": "permissionDenied",
187
+ "message": "The current user has insufficient permissions to perform the requested operation."
188
+ }
189
+ ],
190
+ "code": 401,
191
+ "message": "The current user has insufficient permissions to perform the requested operation."
192
+ }
193
+ }
194
+ http_version:
195
+ recorded_at: Thu, 10 Oct 2019 14:07:53 GMT
196
+ recorded_with: VCR 5.0.0
@@ -0,0 +1,127 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: https://www.googleapis.com/oauth2/v4/token
6
+ body:
7
+ encoding: ASCII-8BIT
8
+ string: some_grant
9
+ headers:
10
+ User-Agent:
11
+ - Faraday v0.15.4
12
+ Content-Type:
13
+ - application/x-www-form-urlencoded
14
+ Accept-Encoding:
15
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
16
+ Accept:
17
+ - "*/*"
18
+ response:
19
+ status:
20
+ code: 200
21
+ message: OK
22
+ headers:
23
+ Content-Type:
24
+ - application/json; charset=utf-8
25
+ Vary:
26
+ - Origin
27
+ - Referer
28
+ - X-Origin
29
+ Date:
30
+ - Thu, 24 Oct 2019 10:43:13 GMT
31
+ Server:
32
+ - scaffolding on HTTPServer2
33
+ Cache-Control:
34
+ - private
35
+ X-Xss-Protection:
36
+ - '0'
37
+ X-Frame-Options:
38
+ - SAMEORIGIN
39
+ X-Content-Type-Options:
40
+ - nosniff
41
+ Alt-Svc:
42
+ - quic=":443"; ma=2592000; v="46,43",h3-Q048=":443"; ma=2592000,h3-Q046=":443";
43
+ ma=2592000,h3-Q043=":443"; ma=2592000
44
+ Transfer-Encoding:
45
+ - chunked
46
+ body:
47
+ encoding: ASCII-8BIT
48
+ string: |-
49
+ {
50
+ "access_token": "some token",
51
+ "expires_in": 3600,
52
+ "token_type": "Bearer"
53
+ }
54
+ http_version:
55
+ recorded_at: Thu, 24 Oct 2019 10:43:13 GMT
56
+ - request:
57
+ method: get
58
+ uri: https://www.googleapis.com/androidpublisher/v3/applications/my_package_name/purchases/subscriptions/my_subscription_id/tokens/my_token
59
+ body:
60
+ encoding: UTF-8
61
+ string: ''
62
+ headers:
63
+ User-Agent:
64
+ - unknown/0.0.0 google-api-ruby-client/0.28.7 Linux/5.0.0-32-generic (gzip)
65
+ Accept:
66
+ - "*/*"
67
+ Accept-Encoding:
68
+ - gzip,deflate
69
+ Date:
70
+ - Thu, 24 Oct 2019 10:43:13 GMT
71
+ Authorization:
72
+ - Bearer some_token
73
+ Content-Type:
74
+ - application/x-www-form-urlencoded
75
+ response:
76
+ status:
77
+ code: 200
78
+ message: OK
79
+ headers:
80
+ Expires:
81
+ - Thu, 24 Oct 2019 10:43:13 GMT
82
+ Date:
83
+ - Thu, 24 Oct 2019 10:43:13 GMT
84
+ Cache-Control:
85
+ - private, max-age=0, must-revalidate, no-transform
86
+ Etag:
87
+ - ''
88
+ Vary:
89
+ - Origin
90
+ - X-Origin
91
+ Content-Type:
92
+ - application/json; charset=UTF-8
93
+ Content-Encoding:
94
+ - gzip
95
+ X-Content-Type-Options:
96
+ - nosniff
97
+ X-Frame-Options:
98
+ - SAMEORIGIN
99
+ X-Xss-Protection:
100
+ - 1; mode=block
101
+ Server:
102
+ - GSE
103
+ Alt-Svc:
104
+ - quic=":443"; ma=2592000; v="46,43",h3-Q048=":443"; ma=2592000,h3-Q046=":443";
105
+ ma=2592000,h3-Q043=":443"; ma=2592000
106
+ Transfer-Encoding:
107
+ - chunked
108
+ body:
109
+ encoding: UTF-8
110
+ string: |
111
+ {
112
+ "kind": "androidpublisher#subscriptionPurchase",
113
+ "startTimeMillis": "123456789",
114
+ "expiryTimeMillis": "123456789",
115
+ "autoRenewing": false,
116
+ "priceCurrencyCode": "EUR",
117
+ "priceAmountMicros": "123",
118
+ "countryCode": "DE",
119
+ "developerPayload": "",
120
+ "cancelReason": 1,
121
+ "orderId": "123",
122
+ "purchaseType": 0,
123
+ "acknowledgementState": 1
124
+ }
125
+ http_version:
126
+ recorded_at: Thu, 24 Oct 2019 10:43:13 GMT
127
+ recorded_with: VCR 5.0.0
@@ -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