pay_me 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +111 -0
  3. data/Rakefile +33 -0
  4. data/app/controllers/pay_me/api/v1/invoices_controller.rb +30 -0
  5. data/app/controllers/pay_me/api/v1/stripe_plans_controller.rb +35 -0
  6. data/app/controllers/pay_me/application_controller.rb +10 -0
  7. data/app/helpers/pay_me/application_helper.rb +4 -0
  8. data/app/subscribers/pay_me/customer_subscriber.rb +42 -0
  9. data/app/subscribers/pay_me/plan_subscriber.rb +38 -0
  10. data/app/subscribers/pay_me/subscription_subscriber.rb +50 -0
  11. data/app/subscribers/pay_me/webhook_event_subscriber.rb +11 -0
  12. data/config/routes.rb +11 -0
  13. data/db/migrate/20160725204454_create_pay_me_subscriptions.rb +16 -0
  14. data/db/migrate/20161011170659_create_pay_me_plans.rb +8 -0
  15. data/db/migrate/20171030174731_add_quantity_to_pay_me_subscription.rb +6 -0
  16. data/db/migrate/20171130192525_create_pay_me_customers.rb +11 -0
  17. data/db/migrate/20180116193943_remove_customerable_from_subscriptions.rb +11 -0
  18. data/db/migrate/20180215235017_add_past_due_to_subscriptions.rb +5 -0
  19. data/lib/generators/pay_me/controllers/controllers_generator.rb +11 -0
  20. data/lib/generators/pay_me/controllers/templates/subscriptions_controller.rb +12 -0
  21. data/lib/generators/pay_me/install_generator.rb +39 -0
  22. data/lib/generators/pay_me/migrate_to_customer_model/USAGE +8 -0
  23. data/lib/generators/pay_me/migrate_to_customer_model/migrate_to_customer_model_generator.rb +9 -0
  24. data/lib/generators/pay_me/migrate_to_customer_model/templates/migration.rb.erb +64 -0
  25. data/lib/generators/pay_me/models/models_generator.rb +13 -0
  26. data/lib/generators/pay_me/models/templates/customer.rb +14 -0
  27. data/lib/generators/pay_me/models/templates/plan.rb +14 -0
  28. data/lib/generators/pay_me/models/templates/subscription.rb +14 -0
  29. data/lib/generators/pay_me/policies/policies_generator.rb +11 -0
  30. data/lib/generators/pay_me/policies/templates/subscription_policy.rb +34 -0
  31. data/lib/generators/pay_me/templates/pay_me_initializer.rb +17 -0
  32. data/lib/pay_me.rb +28 -0
  33. data/lib/pay_me/active_record.rb +6 -0
  34. data/lib/pay_me/concerns/controllers/customerable.rb +116 -0
  35. data/lib/pay_me/concerns/models/customerable.rb +54 -0
  36. data/lib/pay_me/concerns/models/stripe_customerable.rb +24 -0
  37. data/lib/pay_me/concerns/models/stripe_plannable.rb +31 -0
  38. data/lib/pay_me/concerns/models/stripe_subscribable.rb +43 -0
  39. data/lib/pay_me/configuration.rb +27 -0
  40. data/lib/pay_me/engine.rb +8 -0
  41. data/lib/pay_me/models.rb +4 -0
  42. data/lib/pay_me/railtie.rb +2 -0
  43. data/lib/pay_me/route_helpers.rb +21 -0
  44. data/lib/pay_me/services/customer.rb +141 -0
  45. data/lib/pay_me/version.rb +3 -0
  46. data/lib/pay_me/view_models/charge.rb +48 -0
  47. data/lib/tasks/pay_me_tasks.rake +4 -0
  48. data/lib/tasks/stripe.rake +22 -0
  49. data/test/controllers/pay_me/api/v1/invoices_controller_test.rb +82 -0
  50. data/test/controllers/pay_me/api/v1/plans_controller_test.rb +62 -0
  51. data/test/dummy/README.rdoc +28 -0
  52. data/test/dummy/Rakefile +6 -0
  53. data/test/dummy/app/assets/javascripts/application.js +13 -0
  54. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  55. data/test/dummy/app/controllers/api/v1/users_controller.rb +10 -0
  56. data/test/dummy/app/controllers/application_controller.rb +5 -0
  57. data/test/dummy/app/controllers/pay_me/api/v1/subscriptions_controller.rb +12 -0
  58. data/test/dummy/app/helpers/application_helper.rb +2 -0
  59. data/test/dummy/app/models/pay_me/customer.rb +5 -0
  60. data/test/dummy/app/models/pay_me/plan.rb +5 -0
  61. data/test/dummy/app/models/pay_me/subscription.rb +5 -0
  62. data/test/dummy/app/models/user.rb +8 -0
  63. data/test/dummy/app/policies/pay_me/subscription_policy.rb +34 -0
  64. data/test/dummy/app/serializers/pay_me/subscription_serializer.rb +3 -0
  65. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  66. data/test/dummy/bin/bundle +3 -0
  67. data/test/dummy/bin/rails +4 -0
  68. data/test/dummy/bin/rake +4 -0
  69. data/test/dummy/bin/setup +29 -0
  70. data/test/dummy/config.ru +4 -0
  71. data/test/dummy/config/application.rb +25 -0
  72. data/test/dummy/config/boot.rb +5 -0
  73. data/test/dummy/config/database.yml +25 -0
  74. data/test/dummy/config/environment.rb +5 -0
  75. data/test/dummy/config/environments/development.rb +41 -0
  76. data/test/dummy/config/environments/production.rb +79 -0
  77. data/test/dummy/config/environments/test.rb +42 -0
  78. data/test/dummy/config/initializers/assets.rb +11 -0
  79. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  80. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  81. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  82. data/test/dummy/config/initializers/inflections.rb +16 -0
  83. data/test/dummy/config/initializers/mime_types.rb +4 -0
  84. data/test/dummy/config/initializers/pay_me.rb +17 -0
  85. data/test/dummy/config/initializers/session_store.rb +3 -0
  86. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  87. data/test/dummy/config/locales/en.yml +23 -0
  88. data/test/dummy/config/routes.rb +10 -0
  89. data/test/dummy/config/secrets.yml +22 -0
  90. data/test/dummy/db/migrate/20160630181300_create_pay_me_users.rb +12 -0
  91. data/test/dummy/db/migrate/20180118001940_api_me_migrate_to_customer_model.rb +9 -0
  92. data/test/dummy/db/schema.rb +53 -0
  93. data/test/dummy/public/404.html +67 -0
  94. data/test/dummy/public/422.html +67 -0
  95. data/test/dummy/public/500.html +66 -0
  96. data/test/dummy/public/favicon.ico +0 -0
  97. data/test/integration/customer_test.rb +395 -0
  98. data/test/lib/generators/pay_me/pay_me/migrate_to_customer_model_generator_test.rb +16 -0
  99. data/test/models/pay_me/customer_test.rb +9 -0
  100. data/test/models/pay_me/subscription_test.rb +9 -0
  101. data/test/models/pay_me/user_test.rb +6 -0
  102. data/test/pay_me_test.rb +7 -0
  103. data/test/support/concerns/api_test_helper.rb +15 -0
  104. data/test/support/concerns/stripe_helpers.rb +25 -0
  105. data/test/test_helper.rb +52 -0
  106. data/test/unit/concerns/customerable.rb +21 -0
  107. data/test/unit/models/customerable_test.rb +18 -0
  108. data/test/unit/services/customer_test.rb +75 -0
  109. data/test/unit/stubs/customerable.rb +28 -0
  110. data/test/unit/stubs/customerable_test.rb +16 -0
  111. data/test/webhooks/customer_webhook_test.rb +74 -0
  112. data/test/webhooks/plan_webhook_test.rb +74 -0
  113. metadata +288 -0
@@ -0,0 +1,67 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The change you wanted was rejected (422)</title>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <style>
7
+ body {
8
+ background-color: #EFEFEF;
9
+ color: #2E2F30;
10
+ text-align: center;
11
+ font-family: arial, sans-serif;
12
+ margin: 0;
13
+ }
14
+
15
+ div.dialog {
16
+ width: 95%;
17
+ max-width: 33em;
18
+ margin: 4em auto 0;
19
+ }
20
+
21
+ div.dialog > div {
22
+ border: 1px solid #CCC;
23
+ border-right-color: #999;
24
+ border-left-color: #999;
25
+ border-bottom-color: #BBB;
26
+ border-top: #B00100 solid 4px;
27
+ border-top-left-radius: 9px;
28
+ border-top-right-radius: 9px;
29
+ background-color: white;
30
+ padding: 7px 12% 0;
31
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
32
+ }
33
+
34
+ h1 {
35
+ font-size: 100%;
36
+ color: #730E15;
37
+ line-height: 1.5em;
38
+ }
39
+
40
+ div.dialog > p {
41
+ margin: 0 0 1em;
42
+ padding: 1em;
43
+ background-color: #F7F7F7;
44
+ border: 1px solid #CCC;
45
+ border-right-color: #999;
46
+ border-left-color: #999;
47
+ border-bottom-color: #999;
48
+ border-bottom-left-radius: 4px;
49
+ border-bottom-right-radius: 4px;
50
+ border-top-color: #DADADA;
51
+ color: #666;
52
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
53
+ }
54
+ </style>
55
+ </head>
56
+
57
+ <body>
58
+ <!-- This file lives in public/422.html -->
59
+ <div class="dialog">
60
+ <div>
61
+ <h1>The change you wanted was rejected.</h1>
62
+ <p>Maybe you tried to change something you didn't have access to.</p>
63
+ </div>
64
+ <p>If you are the application owner check the logs for more information.</p>
65
+ </div>
66
+ </body>
67
+ </html>
@@ -0,0 +1,66 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>We're sorry, but something went wrong (500)</title>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <style>
7
+ body {
8
+ background-color: #EFEFEF;
9
+ color: #2E2F30;
10
+ text-align: center;
11
+ font-family: arial, sans-serif;
12
+ margin: 0;
13
+ }
14
+
15
+ div.dialog {
16
+ width: 95%;
17
+ max-width: 33em;
18
+ margin: 4em auto 0;
19
+ }
20
+
21
+ div.dialog > div {
22
+ border: 1px solid #CCC;
23
+ border-right-color: #999;
24
+ border-left-color: #999;
25
+ border-bottom-color: #BBB;
26
+ border-top: #B00100 solid 4px;
27
+ border-top-left-radius: 9px;
28
+ border-top-right-radius: 9px;
29
+ background-color: white;
30
+ padding: 7px 12% 0;
31
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
32
+ }
33
+
34
+ h1 {
35
+ font-size: 100%;
36
+ color: #730E15;
37
+ line-height: 1.5em;
38
+ }
39
+
40
+ div.dialog > p {
41
+ margin: 0 0 1em;
42
+ padding: 1em;
43
+ background-color: #F7F7F7;
44
+ border: 1px solid #CCC;
45
+ border-right-color: #999;
46
+ border-left-color: #999;
47
+ border-bottom-color: #999;
48
+ border-bottom-left-radius: 4px;
49
+ border-bottom-right-radius: 4px;
50
+ border-top-color: #DADADA;
51
+ color: #666;
52
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
53
+ }
54
+ </style>
55
+ </head>
56
+
57
+ <body>
58
+ <!-- This file lives in public/500.html -->
59
+ <div class="dialog">
60
+ <div>
61
+ <h1>We're sorry, but something went wrong.</h1>
62
+ </div>
63
+ <p>If you are the application owner check the logs for more information.</p>
64
+ </div>
65
+ </body>
66
+ </html>
File without changes
@@ -0,0 +1,395 @@
1
+ require 'minitest/autorun'
2
+ require 'securerandom'
3
+ require 'test_helper'
4
+
5
+ require 'support/concerns/api_test_helper'
6
+ require 'support/concerns/stripe_helpers'
7
+
8
+ VALID_VISA_CARD_NUMBER = '4242424242424242'.freeze
9
+ VALID_MASTERCARD_NUMBER = '5555555555554444'.freeze
10
+ VALID_AMERICAN_EXPRESS_CARD_NUMBER = '378282246310005'.freeze
11
+ VALID_DISCOVER_CARD_NUMBER = '6011111111111117'.freeze
12
+ VALID_CARD_NUMBERS = {
13
+ 'visa' => VALID_VISA_CARD_NUMBER
14
+ # 'mastercard' => VALID_MASTERCARD_NUMBER,
15
+ # 'american express' => VALID_AMERICAN_EXPRESS_CARD_NUMBER,
16
+ # 'discover' => VALID_DISCOVER_CARD_NUMBER
17
+ }.freeze
18
+
19
+ DECLINED_CARD_NUMBER = '4000000000000002'.freeze
20
+ FRADULENT_CARD_NUMBER = '4100000000000019'.freeze
21
+ INVALID_CVC_CARD_NUMBER = '4000000000000127'.freeze
22
+ EXPIRED_CARD_NUMBER = '4000000000000069'.freeze
23
+ UNPROCESSABLE_CARD_NUMBER = '4000000000000119'.freeze
24
+ INVALID_CARD_NUMBERS = [
25
+ { name: 'invalid card', card: INVALID_CVC_CARD_NUMBER, error: /invalid/, mock: :invalid_number },
26
+ { name: 'declined card', card: DECLINED_CARD_NUMBER, error: /declined/, mock: :card_declined }
27
+ ].freeze
28
+
29
+ describe 'As a customer' do
30
+ include PayMe::Support::ApiTestHelper # Mixin get/post/etc... methods, see /test/test_helper.rb for details
31
+ let(:stripe_helper) { StripeMock.create_test_helper }
32
+ before { StripeMock.start }
33
+ after { StripeMock.stop }
34
+ let(:user) do
35
+ User.create!(email: 'person@wild.land')
36
+ end
37
+
38
+ let(:next_year) do
39
+ (DateTime.now + 1.year).year
40
+ end
41
+
42
+ let(:plan) do
43
+ stripe_helper.create_plan(
44
+ amount: 2000,
45
+ interval: 'month',
46
+ name: 'Amazing Gold Plan',
47
+ currency: 'usd',
48
+ id: 'spec_gold'
49
+ ).tap do |p|
50
+ PayMe::Plan.create!(plan_id: p.id)
51
+ end
52
+ end
53
+
54
+ after do
55
+ # stripe_helper.delete_plan(plan.id)
56
+ end
57
+
58
+ VALID_CARD_NUMBERS.each do |name, card_number|
59
+ describe "with a funded #{name} payment source" do
60
+ before do
61
+ user.add_default_payment_source(
62
+ stripe_helper.generate_card_token({
63
+ number: card_number,
64
+ exp_month: 1,
65
+ exp_year: next_year,
66
+ cvc: '123'
67
+ })
68
+ )
69
+ end
70
+ describe 'looking to subscribe to a valid plan' do
71
+ it 'should be able to subscribe to a plan' do
72
+ assert user.stripe_customer.default_source != nil
73
+ post(
74
+ "/api/v1/users/#{user.id}/subscribe",
75
+ plan_id: plan.id
76
+ )
77
+ assert_equal 201, last_response.status
78
+ json = JSON.parse(last_response.body)
79
+
80
+ refute_nil json['subscription']
81
+ end
82
+
83
+ it 'should not be able to subscribe to a plan and card' do
84
+ assert user.stripe_customer.default_source != nil
85
+ post(
86
+ "/api/v1/users/#{user.id}/subscribe",
87
+ plan_id: plan.id,
88
+ card: {
89
+ number: card_number,
90
+ exp_month: 1,
91
+ exp_year: next_year,
92
+ cvc: '123'
93
+ }
94
+ )
95
+ assert_equal 201, last_response.status
96
+ assert_equal 1, user.stripe_customer.sources.total_count
97
+ json = JSON.parse(last_response.body)
98
+ end
99
+
100
+ describe 'wanting to use a discount coupon that provides 15 percent off' do
101
+ let(:coupon) do
102
+ Stripe::Coupon.create(
103
+ percent_off: 15,
104
+ duration: 'repeating',
105
+ duration_in_months: 3,
106
+ id: '15OFF'
107
+ )
108
+ end
109
+
110
+ after do
111
+ coupon.delete
112
+ end
113
+
114
+ it 'should be able to subscribe to a plan' do
115
+ assert user.stripe_customer.default_source != nil
116
+ post(
117
+ "/api/v1/users/#{user.id}/subscribe",
118
+ plan_id: plan.id,
119
+ coupon_id: coupon.id
120
+ )
121
+
122
+ assert_equal 201, last_response.status
123
+ json = JSON.parse(last_response.body)
124
+
125
+ refute_nil json['subscription']
126
+ end
127
+ end
128
+
129
+ describe 'wanting to use a discount coupon that provides 15 dollars off' do
130
+ let(:coupon) do
131
+ Stripe::Coupon.create(
132
+ amount_off: 15,
133
+ currency: 'USD',
134
+ duration: 'once',
135
+ id: '15DOLLARSOFF'
136
+ )
137
+ end
138
+
139
+ after do
140
+ coupon.delete
141
+ end
142
+
143
+ it 'should be able to subscribe to a plan' do
144
+ assert user.stripe_customer.default_source != nil
145
+ post(
146
+ "/api/v1/users/#{user.id}/subscribe",
147
+ plan_id: plan.id,
148
+ coupon_id: coupon.id
149
+ )
150
+ assert_equal 201, last_response.status
151
+ json = JSON.parse(last_response.body)
152
+ refute_nil json['subscription']
153
+ end
154
+ end
155
+ end
156
+
157
+ describe 'looking to make a one-time payment for non-subscriptions or products' do
158
+ it 'should be able to create a charge with default source' do
159
+ user.add_default_payment_source(
160
+ stripe_helper.generate_card_token({
161
+ number: card_number,
162
+ exp_month: 1,
163
+ exp_year: next_year,
164
+ cvc: '123'
165
+ })
166
+ )
167
+ post(
168
+ "/api/v1/users/#{user.id}/charge",
169
+ currency: 'usd',
170
+ amount: 100,
171
+ description: 'One Time Payment'
172
+ )
173
+
174
+ assert_equal 201, last_response.status
175
+ json = JSON.parse(last_response.body)
176
+
177
+ refute_nil json['charge_id']
178
+ end
179
+
180
+ it 'should be able to create a charge with specified source' do
181
+ source = user.add_payment_source(Stripe::Token.create(
182
+ card: {
183
+ number: card_number,
184
+ exp_month: 1,
185
+ exp_year: next_year,
186
+ cvc: '123'
187
+ }
188
+ ).id)
189
+ post(
190
+ "/api/v1/users/#{user.id}/charge",
191
+ currency: 'usd',
192
+ source: source.id,
193
+ amount: 100,
194
+ description: 'One Time Payment'
195
+ )
196
+
197
+ assert_equal 201, last_response.status
198
+ json = JSON.parse(last_response.body)
199
+
200
+ refute_nil json['charge_id']
201
+ end
202
+ end
203
+ end
204
+ end
205
+
206
+ # Ignoring this for now - JW
207
+ # INVALID_CARD_NUMBERS.each do |hash|
208
+ # describe "with an #{hash[:name]} source" do
209
+ # describe 'looking to make a one-time payment for non-subscriptions or products' do
210
+ # it 'should not be able to create a charge with default source' do
211
+ # StripeMock.start
212
+ # # StripeMock.prepare_card_error(hash[:mock], :create_card)
213
+ # user.add_default_payment_source(Stripe::Token.create(
214
+ # card: {
215
+ # number: hash[:card],
216
+ # exp_month: 1,
217
+ # exp_year: next_year,
218
+ # cvc: '123'
219
+ # }
220
+ # ).id)
221
+ # post(
222
+ # "/api/v1/users/#{user.id}/charge",
223
+ # currency: 'usd',
224
+ # amount: 100,
225
+ # description: 'One Time Payment'
226
+ # )
227
+
228
+ # assert_equal 422, last_response.status
229
+ # json = JSON.parse(last_response.body)
230
+
231
+ # assert_match hash[:error], json['reason']
232
+ # end
233
+
234
+ # it 'should not be able to create a charge with specified source' do
235
+ # StripeMock.start
236
+ # # StripeMock.prepare_card_error(hash[:mock], :create_card)
237
+ # source = user.add_payment_source(
238
+ # Stripe::Token.create(card: {
239
+ # number: hash[:card],
240
+ # exp_month: 1,
241
+ # exp_year: next_year,
242
+ # cvc: '123'
243
+ # }).id
244
+ # )
245
+
246
+ # post(
247
+ # "/api/v1/users/#{user.id}/charge",
248
+ # currency: 'usd',
249
+ # source: source.id,
250
+ # amount: 100,
251
+ # description: 'One Time Payment'
252
+ # )
253
+
254
+ # assert_equal 422, last_response.status
255
+ # json = JSON.parse(last_response.body)
256
+
257
+ # assert_match hash[:error], json['reason']
258
+ # end
259
+ # end
260
+ # end
261
+ # end
262
+
263
+ describe "with a subscription" do
264
+ before { user.add_default_payment_source(stripe_helper.generate_card_token) }
265
+ let(:subscription) do
266
+ Stripe::Subscription.create(
267
+ customer: user.stripe_customer_id,
268
+ plan: plan.id,
269
+ quantity: 1
270
+ ).tap do |s|
271
+ PayMe::Subscription.create!(
272
+ subscription_id: s.id,
273
+ quantity: s.quantity,
274
+ customer: user.customer,
275
+ is_active: true,
276
+ plan: PayMe::Plan.find_by!(plan_id: plan.id)
277
+ )
278
+ end
279
+ end
280
+
281
+ describe "when canceling a subscription" do
282
+ describe "that is currently active" do
283
+ it 'should update the local subscription' do
284
+ assert_equal false, PayMe::Subscription.where(subscription_id: subscription.id, is_active: false).exists?
285
+ delete("/api/v1/users/#{user.id}/cancel_subscription", cancel_now: true)
286
+ assert_equal 200, last_response.status
287
+ assert_equal true, PayMe::Subscription.where(subscription_id: subscription.id, is_active: false).exists?
288
+ json = JSON.parse(last_response.body)
289
+ assert_match subscription.id, json['subscription']['subscription_id']
290
+ end
291
+
292
+ describe 'should cancel the stripe subscription' do
293
+ it 'at the end of the billing cycle by default' do
294
+ assert_nil Stripe::Subscription.retrieve(subscription.id).canceled_at
295
+ delete("/api/v1/users/#{user.id}/cancel_subscription")
296
+ assert_equal 200, last_response.status
297
+ refute_nil Stripe::Subscription.retrieve(subscription.id).canceled_at
298
+ assert_equal 'active', Stripe::Subscription.retrieve(subscription.id).status
299
+ assert_equal true, Stripe::Subscription.retrieve(subscription.id).cancel_at_period_end
300
+ json = JSON.parse(last_response.body)
301
+ assert_match subscription.id, json['subscription']['subscription_id']
302
+
303
+ end
304
+
305
+ it 'now' do
306
+ assert_nil Stripe::Subscription.retrieve(subscription.id).canceled_at
307
+ delete("/api/v1/users/#{user.id}/cancel_subscription", cancel_now: true)
308
+ assert_equal 200, last_response.status
309
+ refute_nil Stripe::Subscription.retrieve(subscription.id).canceled_at
310
+ assert_equal 'canceled', Stripe::Subscription.retrieve(subscription.id).status
311
+ assert_equal false, Stripe::Subscription.retrieve(subscription.id).cancel_at_period_end
312
+ json = JSON.parse(last_response.body)
313
+ assert_match subscription.id, json['subscription']['subscription_id']
314
+ end
315
+ end
316
+
317
+ it 'should handle no active subscription' do
318
+ delete("/api/v1/users/#{user.id}/cancel_subscription")
319
+ assert_equal 404, last_response.status
320
+ end
321
+ end
322
+ end
323
+
324
+ describe "when updating a subscription" do
325
+ describe "increasing the quantity" do
326
+ it 'should update the local subscription' do
327
+ new_quantity = subscription.quantity + 1
328
+ assert_equal false, PayMe::Subscription.where(subscription_id: subscription.id, quantity: new_quantity).exists?
329
+ put("/api/v1/users/#{user.id}/update_subscription", quantity: new_quantity)
330
+ assert_equal 200, last_response.status
331
+ assert_equal true, PayMe::Subscription.where(subscription_id: subscription.id, quantity: new_quantity).exists?
332
+ json = JSON.parse(last_response.body)
333
+ assert_equal new_quantity, json['subscription']['quantity']
334
+ end
335
+
336
+ it 'should update the stripe subscription' do
337
+ new_quantity = subscription.quantity + 1
338
+ put("/api/v1/users/#{user.id}/update_subscription", quantity: new_quantity)
339
+ assert_equal 200, last_response.status
340
+ assert_equal new_quantity.to_s, Stripe::Subscription.retrieve(subscription.id).quantity
341
+ json = JSON.parse(last_response.body)
342
+ assert_equal new_quantity, json['subscription']['quantity']
343
+ end
344
+
345
+ it 'should handle no active subscription' do
346
+ put("/api/v1/users/#{user.id}/update_subscription", quantity: 2)
347
+ assert_equal 404, last_response.status
348
+ end
349
+ end
350
+
351
+ describe "decreasing the quantity" do
352
+ it 'should update the local subscription' do
353
+ new_quantity = subscription.quantity - 1
354
+ assert_equal false, PayMe::Subscription.where(subscription_id: subscription.id, quantity: new_quantity).exists?
355
+ put("/api/v1/users/#{user.id}/update_subscription", quantity: new_quantity)
356
+ assert_equal 200, last_response.status
357
+ assert_equal true, PayMe::Subscription.where(subscription_id: subscription.id, quantity: new_quantity).exists?
358
+ json = JSON.parse(last_response.body)
359
+ assert_equal new_quantity, json['subscription']['quantity']
360
+ end
361
+
362
+ it 'should update the stripe subscription' do
363
+ new_quantity = subscription.quantity + 1
364
+ put("/api/v1/users/#{user.id}/update_subscription", quantity: new_quantity)
365
+ assert_equal 200, last_response.status
366
+ assert_equal new_quantity.to_s, Stripe::Subscription.retrieve(subscription.id).quantity
367
+ json = JSON.parse(last_response.body)
368
+ assert_equal new_quantity, json['subscription']['quantity']
369
+ end
370
+
371
+ it 'should handle no active subscription' do
372
+ put("/api/v1/users/#{user.id}/update_subscription", quantity: 2)
373
+ assert_equal 404, last_response.status
374
+ end
375
+ end
376
+
377
+ describe "invalid quantity" do
378
+ it 'should reject invalid quantity' do
379
+ skip('No stripe validation with webmock')
380
+ subscription
381
+ put("/api/v1/users/#{user.id}/update_subscription", quantity: "Bananas")
382
+ assert_equal 422, last_response.status
383
+ json = JSON.parse(last_response.body)
384
+
385
+ assert_match /Invalid integer/i, json['reason']
386
+ end
387
+
388
+ it 'should handle no active subscription' do
389
+ put("/api/v1/users/#{user.id}/update_subscription", quantity: "Bananas")
390
+ assert_equal 404, last_response.status
391
+ end
392
+ end
393
+ end
394
+ end
395
+ end