pay_me 0.5.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 (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