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.
- checksums.yaml +7 -0
- data/README.md +111 -0
- data/Rakefile +33 -0
- data/app/controllers/pay_me/api/v1/invoices_controller.rb +30 -0
- data/app/controllers/pay_me/api/v1/stripe_plans_controller.rb +35 -0
- data/app/controllers/pay_me/application_controller.rb +10 -0
- data/app/helpers/pay_me/application_helper.rb +4 -0
- data/app/subscribers/pay_me/customer_subscriber.rb +42 -0
- data/app/subscribers/pay_me/plan_subscriber.rb +38 -0
- data/app/subscribers/pay_me/subscription_subscriber.rb +50 -0
- data/app/subscribers/pay_me/webhook_event_subscriber.rb +11 -0
- data/config/routes.rb +11 -0
- data/db/migrate/20160725204454_create_pay_me_subscriptions.rb +16 -0
- data/db/migrate/20161011170659_create_pay_me_plans.rb +8 -0
- data/db/migrate/20171030174731_add_quantity_to_pay_me_subscription.rb +6 -0
- data/db/migrate/20171130192525_create_pay_me_customers.rb +11 -0
- data/db/migrate/20180116193943_remove_customerable_from_subscriptions.rb +11 -0
- data/db/migrate/20180215235017_add_past_due_to_subscriptions.rb +5 -0
- data/lib/generators/pay_me/controllers/controllers_generator.rb +11 -0
- data/lib/generators/pay_me/controllers/templates/subscriptions_controller.rb +12 -0
- data/lib/generators/pay_me/install_generator.rb +39 -0
- data/lib/generators/pay_me/migrate_to_customer_model/USAGE +8 -0
- data/lib/generators/pay_me/migrate_to_customer_model/migrate_to_customer_model_generator.rb +9 -0
- data/lib/generators/pay_me/migrate_to_customer_model/templates/migration.rb.erb +64 -0
- data/lib/generators/pay_me/models/models_generator.rb +13 -0
- data/lib/generators/pay_me/models/templates/customer.rb +14 -0
- data/lib/generators/pay_me/models/templates/plan.rb +14 -0
- data/lib/generators/pay_me/models/templates/subscription.rb +14 -0
- data/lib/generators/pay_me/policies/policies_generator.rb +11 -0
- data/lib/generators/pay_me/policies/templates/subscription_policy.rb +34 -0
- data/lib/generators/pay_me/templates/pay_me_initializer.rb +17 -0
- data/lib/pay_me.rb +28 -0
- data/lib/pay_me/active_record.rb +6 -0
- data/lib/pay_me/concerns/controllers/customerable.rb +116 -0
- data/lib/pay_me/concerns/models/customerable.rb +54 -0
- data/lib/pay_me/concerns/models/stripe_customerable.rb +24 -0
- data/lib/pay_me/concerns/models/stripe_plannable.rb +31 -0
- data/lib/pay_me/concerns/models/stripe_subscribable.rb +43 -0
- data/lib/pay_me/configuration.rb +27 -0
- data/lib/pay_me/engine.rb +8 -0
- data/lib/pay_me/models.rb +4 -0
- data/lib/pay_me/railtie.rb +2 -0
- data/lib/pay_me/route_helpers.rb +21 -0
- data/lib/pay_me/services/customer.rb +141 -0
- data/lib/pay_me/version.rb +3 -0
- data/lib/pay_me/view_models/charge.rb +48 -0
- data/lib/tasks/pay_me_tasks.rake +4 -0
- data/lib/tasks/stripe.rake +22 -0
- data/test/controllers/pay_me/api/v1/invoices_controller_test.rb +82 -0
- data/test/controllers/pay_me/api/v1/plans_controller_test.rb +62 -0
- data/test/dummy/README.rdoc +28 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +15 -0
- data/test/dummy/app/controllers/api/v1/users_controller.rb +10 -0
- data/test/dummy/app/controllers/application_controller.rb +5 -0
- data/test/dummy/app/controllers/pay_me/api/v1/subscriptions_controller.rb +12 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/models/pay_me/customer.rb +5 -0
- data/test/dummy/app/models/pay_me/plan.rb +5 -0
- data/test/dummy/app/models/pay_me/subscription.rb +5 -0
- data/test/dummy/app/models/user.rb +8 -0
- data/test/dummy/app/policies/pay_me/subscription_policy.rb +34 -0
- data/test/dummy/app/serializers/pay_me/subscription_serializer.rb +3 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/bin/setup +29 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +25 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +41 -0
- data/test/dummy/config/environments/production.rb +79 -0
- data/test/dummy/config/environments/test.rb +42 -0
- data/test/dummy/config/initializers/assets.rb +11 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/pay_me.rb +17 -0
- data/test/dummy/config/initializers/session_store.rb +3 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/routes.rb +10 -0
- data/test/dummy/config/secrets.yml +22 -0
- data/test/dummy/db/migrate/20160630181300_create_pay_me_users.rb +12 -0
- data/test/dummy/db/migrate/20180118001940_api_me_migrate_to_customer_model.rb +9 -0
- data/test/dummy/db/schema.rb +53 -0
- data/test/dummy/public/404.html +67 -0
- data/test/dummy/public/422.html +67 -0
- data/test/dummy/public/500.html +66 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/integration/customer_test.rb +395 -0
- data/test/lib/generators/pay_me/pay_me/migrate_to_customer_model_generator_test.rb +16 -0
- data/test/models/pay_me/customer_test.rb +9 -0
- data/test/models/pay_me/subscription_test.rb +9 -0
- data/test/models/pay_me/user_test.rb +6 -0
- data/test/pay_me_test.rb +7 -0
- data/test/support/concerns/api_test_helper.rb +15 -0
- data/test/support/concerns/stripe_helpers.rb +25 -0
- data/test/test_helper.rb +52 -0
- data/test/unit/concerns/customerable.rb +21 -0
- data/test/unit/models/customerable_test.rb +18 -0
- data/test/unit/services/customer_test.rb +75 -0
- data/test/unit/stubs/customerable.rb +28 -0
- data/test/unit/stubs/customerable_test.rb +16 -0
- data/test/webhooks/customer_webhook_test.rb +74 -0
- data/test/webhooks/plan_webhook_test.rb +74 -0
- 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
|