saas_payments 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +2 -7
- data/app/assets/javascripts/saas_payments/elements.js +2 -2
- data/app/controllers/concerns/saas_payments/payable.rb +189 -0
- data/app/controllers/concerns/saas_payments/webhook.rb +28 -0
- data/app/controllers/saas_payments/application_controller.rb +0 -1
- data/app/controllers/saas_payments/webhook_controller.rb +0 -2
- data/app/lib/saas_payments/products_service.rb +24 -9
- data/app/lib/saas_payments/webhook/customer_event.rb +5 -9
- data/app/lib/saas_payments/webhook/order_event.rb +31 -0
- data/app/lib/saas_payments/webhook/plan_event.rb +4 -4
- data/app/lib/saas_payments/webhook/product_event.rb +4 -4
- data/app/lib/saas_payments/webhook/session_event.rb +1 -1
- data/app/lib/saas_payments/webhook/sku_event.rb +25 -0
- data/app/lib/saas_payments/webhook/subscription_event.rb +4 -4
- data/app/lib/saas_payments/webhook_service.rb +14 -4
- data/app/models/concerns/saas_payments/stripe_model.rb +9 -11
- data/app/models/saas_payments/customer.rb +21 -13
- data/app/models/saas_payments/order.rb +28 -0
- data/app/models/saas_payments/plan.rb +2 -4
- data/app/models/saas_payments/product.rb +8 -9
- data/app/models/saas_payments/sku.rb +30 -0
- data/app/models/saas_payments/subscription.rb +2 -3
- data/app/views/saas_payments/_stripe_elements.html.erb +2 -0
- data/db/migrate/20191006144720_create_saas_payments_skus.rb +22 -0
- data/db/migrate/20191008134837_remove_user_id_from_customer.rb +5 -0
- data/db/migrate/20191010013513_create_saas_payments_orders.rb +24 -0
- data/lib/saas_payments/config.rb +0 -1
- data/lib/saas_payments/engine.rb +0 -1
- data/lib/saas_payments/version.rb +1 -1
- data/lib/tasks/products.rake +15 -1
- metadata +11 -4
- data/app/controllers/concerns/saas_payments/subscription_concerns.rb +0 -173
- data/app/controllers/concerns/saas_payments/webhook_concerns.rb +0 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ce17de72e16596e973ee323fe642f744afcbad16589fcd1ef0ac53c363dff461
|
4
|
+
data.tar.gz: 0e3fc7823f91df6b934fee91e3a8a82005075119a127400ecf70716977c66096
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '09f0fea75408fee1c60f4977b8ecd16ec9fd45167debe7705d8af9f406b0c0ac1310acd6a149b03ffc24f1d3499d40304e713d5e7ff58bf69ee4f035cebf2663'
|
7
|
+
data.tar.gz: cc114c1c7a728f9208d4109d66681c5fdc3996df3d92f09a4dd3ef7e0b52851bc62fb998b9c669b29094575b59c95bd16567388649801ba66957209096e6d31e
|
data/README.md
CHANGED
@@ -1,15 +1,10 @@
|
|
1
1
|
# SaasPayments
|
2
2
|
|
3
3
|
SaasPayments is a thin wrapper around the Stripe API to manage the most common
|
4
|
-
SAAS subscription actions.
|
4
|
+
SAAS subscription actions.
|
5
5
|
|
6
|
-
- [ ] Subscription model to hold the state of a user's subscription
|
7
|
-
- [ ] Payment form template
|
8
|
-
- [ ] Webhook to handle subscription updates from Stripe
|
9
|
-
- [ ] Sign up, cancel, and change plan routes
|
10
6
|
|
11
|
-
|
12
|
-
This Gem also assumes you already have:
|
7
|
+
This Gem assumes you already have:
|
13
8
|
|
14
9
|
- A `User` model (If you don't, try [Devise](https://github.com/plataformatec/devise))
|
15
10
|
|
@@ -104,7 +104,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
104
104
|
|
105
105
|
var setHiddenValue = function(form, className, value) {
|
106
106
|
var field = form.getElementsByClassName(className)[0]
|
107
|
-
field.value = value
|
107
|
+
if(!!field) field.value = value
|
108
108
|
}
|
109
109
|
|
110
110
|
this.show = function(id) {
|
@@ -127,7 +127,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
127
127
|
return
|
128
128
|
}
|
129
129
|
|
130
|
-
var defaults = ['plan_id', 'user_id']
|
130
|
+
var defaults = ['sku_id', 'plan_id', 'user_id']
|
131
131
|
for (var i = 0; i < defaults.length; i++) {
|
132
132
|
var key = defaults[i]
|
133
133
|
if(options[key]) setHiddenValue(this.form, 'form_'+key, options[key])
|
@@ -0,0 +1,189 @@
|
|
1
|
+
module SaasPayments
|
2
|
+
module Payable
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
Stripe.api_key = SaasPayments.config.stripe_secret_key
|
6
|
+
|
7
|
+
class FailedPayment < StandardError; end
|
8
|
+
class ActionRequired < StandardError; end
|
9
|
+
|
10
|
+
def purchase(customer, sku)
|
11
|
+
order = create_order customer, sku
|
12
|
+
pay_order customer, order
|
13
|
+
end
|
14
|
+
|
15
|
+
def sign_up_for_plan(customer, plan)
|
16
|
+
update_subscription customer, plan
|
17
|
+
render_success
|
18
|
+
rescue ActionRequired
|
19
|
+
render_action_required
|
20
|
+
rescue FailedPayment
|
21
|
+
render_card_failure
|
22
|
+
rescue Stripe::CardError
|
23
|
+
render_card_failure
|
24
|
+
end
|
25
|
+
|
26
|
+
def change_plan(customer, plan)
|
27
|
+
sub = customer.subscription remote: true
|
28
|
+
update_plan sub, customer, plan
|
29
|
+
render_success
|
30
|
+
rescue StandardError
|
31
|
+
render_error
|
32
|
+
end
|
33
|
+
|
34
|
+
def cancel_at_period_end(customer)
|
35
|
+
sub = customer.subscription remote: true
|
36
|
+
|
37
|
+
# Customer has an existing subscription, change plans
|
38
|
+
remote_sub = Stripe::Subscription.update(
|
39
|
+
sub[:id],
|
40
|
+
cancel_at_period_end: true
|
41
|
+
)
|
42
|
+
|
43
|
+
customer.subscription.update Subscription.from_stripe(remote_sub)
|
44
|
+
render_success
|
45
|
+
rescue StandardError
|
46
|
+
render_error
|
47
|
+
end
|
48
|
+
|
49
|
+
def resume_subscription(customer)
|
50
|
+
sub = customer.subscription
|
51
|
+
remote_sub = Stripe::Subscription.update(
|
52
|
+
sub.stripe_id,
|
53
|
+
cancel_at_period_end: false
|
54
|
+
)
|
55
|
+
|
56
|
+
customer.subscription.update Subscription.from_stripe(remote_sub)
|
57
|
+
render_success
|
58
|
+
rescue StandardError
|
59
|
+
render_error
|
60
|
+
end
|
61
|
+
|
62
|
+
def cancel_now(customer)
|
63
|
+
Stripe::Subscription.delete customer.subscription.stripe_id
|
64
|
+
end
|
65
|
+
|
66
|
+
def create_customer(email, card_token)
|
67
|
+
# Create a new customer in stripe
|
68
|
+
customer = Stripe::Customer.create(email: email, source: card_token)
|
69
|
+
|
70
|
+
# Create a new customer locally that maps to the stripe customer
|
71
|
+
Customer.create Customer.from_stripe(customer)
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def create_order(customer, sku)
|
77
|
+
order = Stripe::Order.create({
|
78
|
+
currency: 'usd',
|
79
|
+
customer: customer.stripe_id,
|
80
|
+
items: [
|
81
|
+
{ type: 'sku', parent: sku.stripe_id },
|
82
|
+
],
|
83
|
+
})
|
84
|
+
|
85
|
+
Order.create Order.from_stripe(order)
|
86
|
+
end
|
87
|
+
|
88
|
+
def pay_order(customer, order)
|
89
|
+
# Pay the order
|
90
|
+
stripe_order = Stripe::Order.pay(
|
91
|
+
order[:stripe_id],
|
92
|
+
{ customer: customer.stripe_id }
|
93
|
+
)
|
94
|
+
order.update Order.from_stripe(stripe_order)
|
95
|
+
order
|
96
|
+
end
|
97
|
+
|
98
|
+
def render_success
|
99
|
+
respond_to do |format|
|
100
|
+
format.html do
|
101
|
+
redirect_to SaasPayments.config.account_path
|
102
|
+
end
|
103
|
+
format.json do
|
104
|
+
render json: { message: "success" }
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def render_error
|
110
|
+
format.html do
|
111
|
+
flash[:sp_error] = 'Failed to update plan'
|
112
|
+
redirect_back fallback_location: root_path
|
113
|
+
end
|
114
|
+
format.json { render json: { message: "update_failed" }, status: :bad_request }
|
115
|
+
end
|
116
|
+
|
117
|
+
def render_action_required
|
118
|
+
respond_to do |format|
|
119
|
+
format.html do
|
120
|
+
flash[:sp_notice] = 'Payment requires customer action'
|
121
|
+
redirect_to SaasPayments.successPath
|
122
|
+
end
|
123
|
+
format.json { render json: { message: "action_required" } }
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def render_card_failure
|
128
|
+
respond_to do |format|
|
129
|
+
format.html do
|
130
|
+
flash[:sp_error] = 'Failed to process card'
|
131
|
+
redirect_back fallback_location: root_path
|
132
|
+
end
|
133
|
+
format.json { render json: { message: "card_error" }, status: :bad_request }
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def update_subscription(customer, plan)
|
138
|
+
sub = customer.subscription remote: true
|
139
|
+
if sub.present?
|
140
|
+
update_plan sub, customer, plan
|
141
|
+
else
|
142
|
+
sub = create_subscription customer, plan
|
143
|
+
end
|
144
|
+
|
145
|
+
sub
|
146
|
+
end
|
147
|
+
|
148
|
+
def update_plan(sub, customer, plan)
|
149
|
+
# Customer has an existing subscription, change plans
|
150
|
+
remote_sub = Stripe::Subscription.update(
|
151
|
+
sub[:id],
|
152
|
+
cancel_at_period_end: false,
|
153
|
+
items: [
|
154
|
+
{ id: sub[:items][:data][0][:id], plan: plan.stripe_id },
|
155
|
+
]
|
156
|
+
)
|
157
|
+
|
158
|
+
customer.subscription.update Subscription.from_stripe(remote_sub)
|
159
|
+
end
|
160
|
+
|
161
|
+
def create_subscription(customer, plan)
|
162
|
+
# Customer does not have a subscription, sign them up
|
163
|
+
sub = Stripe::Subscription.create(
|
164
|
+
customer: customer.stripe_id,
|
165
|
+
items: [{ plan: plan.stripe_id }]
|
166
|
+
)
|
167
|
+
complete_subscription sub
|
168
|
+
end
|
169
|
+
|
170
|
+
def complete_subscription(sub)
|
171
|
+
if incomplete? sub
|
172
|
+
raise FailedPayment if payment_intent sub, :requires_payment_method
|
173
|
+
raise ActionRequired if payment_intent sub, :requires_action
|
174
|
+
end
|
175
|
+
|
176
|
+
# Successful payment, or trialling: provision the subscription
|
177
|
+
# Create a local subscription
|
178
|
+
Subscription.create Subscription.from_stripe(sub)
|
179
|
+
end
|
180
|
+
|
181
|
+
def incomplete?(sub)
|
182
|
+
sub[:status] == "incomplete"
|
183
|
+
end
|
184
|
+
|
185
|
+
def payment_intent(sub, intent)
|
186
|
+
sub[:latest_invoice][:payment_intent][:status] == intent.to_s
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module SaasPayments
|
2
|
+
module Webhook
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
Stripe.api_key = SaasPayments.config.stripe_secret_key
|
5
|
+
SECRET = SaasPayments.config.webhook_secret
|
6
|
+
|
7
|
+
def webhook
|
8
|
+
event = parse_event request.body.read
|
9
|
+
WebhookService.new(event).process
|
10
|
+
rescue ActiveRecord::RecordNotFound => e
|
11
|
+
raise SaasPayments::WebhookError, e.message
|
12
|
+
rescue StandardError => e
|
13
|
+
raise SaasPayments::WebhookError, e.message
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def parse_event(payload)
|
19
|
+
if SaasPayments.config.webhook_secret.present?
|
20
|
+
header = request.headers['HTTP_STRIPE_SIGNATURE']
|
21
|
+
Stripe::Webhook.construct_event payload, header, SECRET
|
22
|
+
else
|
23
|
+
data = JSON.parse(request.body.read, symbolize_names: true)
|
24
|
+
Stripe::Event.construct_from(data)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -1,24 +1,39 @@
|
|
1
1
|
module SaasPayments
|
2
2
|
class ProductsService
|
3
|
-
def
|
3
|
+
def initialize
|
4
4
|
Stripe.api_key = SaasPayments.config.stripe_secret_key
|
5
|
-
|
6
|
-
|
5
|
+
end
|
6
|
+
|
7
|
+
def sync
|
8
|
+
Stripe::Product.all.each { |p| create_product p }
|
9
|
+
Stripe::Plan.list.each { |p| create_plan p }
|
10
|
+
Stripe::SKU.list.each { |s| create_sku s }
|
11
|
+
end
|
12
|
+
|
13
|
+
def shippable(product, on)
|
14
|
+
p = Stripe::Product.retrieve(product)
|
15
|
+
p.shippable = on
|
16
|
+
p.save
|
17
|
+
rescue StandardError => e
|
18
|
+
puts "ERROR: #{e.message}"
|
19
|
+
false
|
7
20
|
end
|
8
21
|
|
9
22
|
private
|
10
23
|
|
11
|
-
def
|
12
|
-
|
24
|
+
def create_sku(sku)
|
25
|
+
Sku.create! Sku.from_stripe(sku)
|
26
|
+
end
|
27
|
+
|
28
|
+
def create_product(product)
|
29
|
+
Product.create! Product.from_stripe(product)
|
13
30
|
rescue ActiveRecord::RecordInvalid
|
14
|
-
puts "Product (#{product[:id]}) already exists. Updating..."
|
15
31
|
Product.find_by_stripe_id(product[:id]).update!(Product.from_stripe(product))
|
16
32
|
end
|
17
33
|
|
18
|
-
def
|
19
|
-
Plan.create!
|
34
|
+
def create_plan(plan)
|
35
|
+
Plan.create! Plan.from_stripe(plan)
|
20
36
|
rescue ActiveRecord::RecordInvalid
|
21
|
-
puts "Plan (#{plan[:id]}) already exists. Updating..."
|
22
37
|
Plan.find_by_stripe_id(plan[:id]).update!(Plan.from_stripe(plan))
|
23
38
|
end
|
24
39
|
end
|
@@ -1,16 +1,16 @@
|
|
1
1
|
module SaasPayments
|
2
2
|
class Webhook::CustomerEvent
|
3
|
-
def created
|
4
|
-
Customer.create
|
3
|
+
def created(data)
|
4
|
+
Customer.create Customer.from_stripe(data)
|
5
5
|
end
|
6
6
|
|
7
|
-
def updated
|
7
|
+
def updated(data)
|
8
8
|
customer = Customer.find_by_stripe_id(data[:id])
|
9
9
|
raise_not_found(data[:id]) unless customer
|
10
10
|
customer.update(Customer.from_stripe(data))
|
11
11
|
end
|
12
12
|
|
13
|
-
def deleted
|
13
|
+
def deleted(data)
|
14
14
|
customer = Customer.find_by_stripe_id(data[:id])
|
15
15
|
raise_not_found(data[:id]) unless customer
|
16
16
|
customer.delete
|
@@ -18,11 +18,7 @@ module SaasPayments
|
|
18
18
|
|
19
19
|
private
|
20
20
|
|
21
|
-
def
|
22
|
-
data[:metadata][:user_id] rescue nil
|
23
|
-
end
|
24
|
-
|
25
|
-
def raise_not_found id
|
21
|
+
def raise_not_found(id)
|
26
22
|
raise ActiveRecord::RecordNotFound.new("Could not find customer with stripe id: #{id}")
|
27
23
|
end
|
28
24
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module SaasPayments
|
2
|
+
class Webhook::OrderEvent
|
3
|
+
def created(data)
|
4
|
+
Order.create!(Order.from_stripe(data))
|
5
|
+
end
|
6
|
+
|
7
|
+
def updated(data)
|
8
|
+
order = Order.find_by_stripe_id(data[:id])
|
9
|
+
raise_not_found(data[:id]) unless order
|
10
|
+
order.update(Order.from_stripe(data))
|
11
|
+
end
|
12
|
+
|
13
|
+
def payment_failed(data)
|
14
|
+
order = Order.find_by_stripe_id(data[:id])
|
15
|
+
raise_not_found(data[:id]) unless order
|
16
|
+
order.update(Order.from_stripe(data))
|
17
|
+
end
|
18
|
+
|
19
|
+
def payment_succeeded(data)
|
20
|
+
order = Order.find_by_stripe_id(data[:id])
|
21
|
+
raise_not_found(data[:id]) unless order
|
22
|
+
order.update(Order.from_stripe(data))
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def raise_not_found(id)
|
28
|
+
raise ActiveRecord::RecordNotFound.new("Could not find order with stripe_id: #{id}")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -1,16 +1,16 @@
|
|
1
1
|
module SaasPayments
|
2
2
|
class Webhook::PlanEvent
|
3
|
-
def created
|
3
|
+
def created(data)
|
4
4
|
Plan.create!(Plan.from_stripe(data))
|
5
5
|
end
|
6
6
|
|
7
|
-
def updated
|
7
|
+
def updated(data)
|
8
8
|
plan = Plan.find_by_stripe_id(data[:id])
|
9
9
|
raise_not_found(data[:id]) unless plan
|
10
10
|
plan.update(Plan.from_stripe(data))
|
11
11
|
end
|
12
12
|
|
13
|
-
def deleted
|
13
|
+
def deleted(data)
|
14
14
|
plan = Plan.find_by_stripe_id(data[:id])
|
15
15
|
raise_not_found(data[:id]) unless plan
|
16
16
|
plan.delete
|
@@ -18,7 +18,7 @@ module SaasPayments
|
|
18
18
|
|
19
19
|
private
|
20
20
|
|
21
|
-
def raise_not_found
|
21
|
+
def raise_not_found(id)
|
22
22
|
raise ActiveRecord::RecordNotFound.new("Could not find plan with stripe id: #{id}")
|
23
23
|
end
|
24
24
|
end
|
@@ -1,16 +1,16 @@
|
|
1
1
|
module SaasPayments
|
2
2
|
class Webhook::ProductEvent
|
3
|
-
def created
|
3
|
+
def created(data)
|
4
4
|
Product.create!(Product.from_stripe(data))
|
5
5
|
end
|
6
6
|
|
7
|
-
def updated
|
7
|
+
def updated(data)
|
8
8
|
product = Product.find_by_stripe_id(data[:id])
|
9
9
|
raise_not_found(data[:id]) unless product
|
10
10
|
product.update(Product.from_stripe(data))
|
11
11
|
end
|
12
12
|
|
13
|
-
def deleted
|
13
|
+
def deleted(data)
|
14
14
|
product = Product.find_by_stripe_id(data[:id])
|
15
15
|
raise_not_found(data[:id]) unless product
|
16
16
|
product.delete
|
@@ -18,7 +18,7 @@ module SaasPayments
|
|
18
18
|
|
19
19
|
private
|
20
20
|
|
21
|
-
def raise_not_found
|
21
|
+
def raise_not_found(id)
|
22
22
|
raise ActiveRecord::RecordNotFound.new("Could not find product with stripe_id: #{id}")
|
23
23
|
end
|
24
24
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module SaasPayments
|
2
|
+
class Webhook::SkuEvent
|
3
|
+
def created(data)
|
4
|
+
Sku.create!(Sku.from_stripe(data))
|
5
|
+
end
|
6
|
+
|
7
|
+
def updated(data)
|
8
|
+
sku = Sku.find_by_stripe_id(data[:id])
|
9
|
+
raise_not_found(data[:id]) unless sku
|
10
|
+
sku.update(Sku.from_stripe(data))
|
11
|
+
end
|
12
|
+
|
13
|
+
def deleted(data)
|
14
|
+
sku = Sku.find_by_stripe_id(data[:id])
|
15
|
+
raise_not_found(data[:id]) unless sku
|
16
|
+
sku.delete
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def raise_not_found(id)
|
22
|
+
raise ActiveRecord::RecordNotFound.new("Could not find sku with stripe id: #{id}")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -1,16 +1,16 @@
|
|
1
1
|
module SaasPayments
|
2
2
|
class Webhook::SubscriptionEvent
|
3
|
-
def created
|
3
|
+
def created(data)
|
4
4
|
Subscription.create!(Subscription.from_stripe(data))
|
5
5
|
end
|
6
6
|
|
7
|
-
def updated
|
7
|
+
def updated(data)
|
8
8
|
sub = Subscription.find_by_stripe_id(data[:id])
|
9
9
|
raise_not_found(data[:id]) unless sub
|
10
10
|
sub.update(Subscription.from_stripe(data))
|
11
11
|
end
|
12
12
|
|
13
|
-
def deleted
|
13
|
+
def deleted(data)
|
14
14
|
sub = Subscription.find_by_stripe_id(data[:id])
|
15
15
|
raise_not_found(data[:id]) unless sub
|
16
16
|
sub.delete
|
@@ -18,7 +18,7 @@ module SaasPayments
|
|
18
18
|
|
19
19
|
private
|
20
20
|
|
21
|
-
def raise_not_found
|
21
|
+
def raise_not_found(id)
|
22
22
|
raise ActiveRecord::RecordNotFound.new("Could not find subscription with stripe id: #{id}")
|
23
23
|
end
|
24
24
|
end
|
@@ -11,6 +11,11 @@ module SaasPayments
|
|
11
11
|
'plan.deleted' => [Webhook::PlanEvent, 'deleted'],
|
12
12
|
'plan.updated' => [Webhook::PlanEvent, 'updated'],
|
13
13
|
|
14
|
+
# Register Plan events
|
15
|
+
'sku.created' => [Webhook::SkuEvent, 'created'],
|
16
|
+
'sku.deleted' => [Webhook::SkuEvent, 'deleted'],
|
17
|
+
'sku.updated' => [Webhook::SkuEvent, 'updated'],
|
18
|
+
|
14
19
|
# Register Customer events
|
15
20
|
'customer.created' => [Webhook::CustomerEvent, 'created'],
|
16
21
|
'customer.deleted' => [Webhook::CustomerEvent, 'deleted'],
|
@@ -22,10 +27,16 @@ module SaasPayments
|
|
22
27
|
'customer.subscription.updated' => [Webhook::SubscriptionEvent, 'updated'],
|
23
28
|
|
24
29
|
# Register Session events
|
25
|
-
'checkout.session.completed' => [Webhook::SessionEvent, 'completed']
|
26
|
-
}
|
30
|
+
'checkout.session.completed' => [Webhook::SessionEvent, 'completed'],
|
27
31
|
|
28
|
-
|
32
|
+
# Order events
|
33
|
+
'order.created' => [Webhook::OrderEvent, 'created'],
|
34
|
+
'order.payment_failed' => [Webhook::OrderEvent, 'payment_failed'],
|
35
|
+
'order.payment_succeeded' => [Webhook::OrderEvent, 'payment_succeeded'],
|
36
|
+
'order.updated' => [Webhook::OrderEvent, 'updated'],
|
37
|
+
}.freeze
|
38
|
+
|
39
|
+
def initialize(event)
|
29
40
|
@event = event
|
30
41
|
end
|
31
42
|
|
@@ -34,6 +45,5 @@ module SaasPayments
|
|
34
45
|
raise WebhookError.new("Unrecognized event") unless event && action
|
35
46
|
event.new.send(action, @event[:data][:object])
|
36
47
|
end
|
37
|
-
|
38
48
|
end
|
39
49
|
end
|
@@ -3,30 +3,28 @@ module SaasPayments
|
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
|
5
5
|
module ClassMethods
|
6
|
-
def truthy?
|
6
|
+
def truthy?(val)
|
7
7
|
val.present? ? val : false
|
8
8
|
end
|
9
9
|
|
10
|
-
def stripe_hash
|
10
|
+
def stripe_hash(data)
|
11
11
|
(data || {}).to_hash
|
12
12
|
end
|
13
13
|
|
14
|
-
def
|
14
|
+
def stripe_array(data)
|
15
|
+
(data || []).map(&:to_hash)
|
16
|
+
end
|
17
|
+
|
18
|
+
def date(ts)
|
15
19
|
ts.present? ? Time.at(ts).to_datetime : nil
|
16
20
|
rescue
|
17
21
|
nil
|
18
22
|
end
|
19
23
|
|
20
|
-
def parse_id
|
24
|
+
def parse_id(val)
|
21
25
|
return nil unless val
|
22
|
-
|
23
|
-
if val.is_a?(String)
|
24
|
-
val
|
25
|
-
else
|
26
|
-
val[:id]
|
27
|
-
end
|
26
|
+
val.is_a?(String) ? val : val[:id]
|
28
27
|
end
|
29
28
|
end
|
30
|
-
|
31
29
|
end
|
32
30
|
end
|
@@ -4,25 +4,23 @@ module SaasPayments
|
|
4
4
|
|
5
5
|
validates_uniqueness_of :stripe_id
|
6
6
|
|
7
|
-
belongs_to :user, optional: true
|
8
|
-
|
9
7
|
serialize :discount
|
10
8
|
serialize :metadata
|
11
9
|
|
12
|
-
def self.from_stripe
|
10
|
+
def self.from_stripe(c)
|
13
11
|
{
|
14
|
-
stripe_id:
|
15
|
-
delinquent:
|
16
|
-
description:
|
17
|
-
discount:
|
18
|
-
email:
|
19
|
-
livemode:
|
20
|
-
metadata:
|
21
|
-
name:
|
12
|
+
stripe_id: c[:id],
|
13
|
+
delinquent: truthy?(c[:delinquent]),
|
14
|
+
description: c[:description],
|
15
|
+
discount: stripe_hash(c[:discount]),
|
16
|
+
email: c[:email],
|
17
|
+
livemode: truthy?(c[:livemode]),
|
18
|
+
metadata: stripe_hash(c[:metadata]),
|
19
|
+
name: c[:name],
|
22
20
|
}
|
23
21
|
end
|
24
22
|
|
25
|
-
def subscription
|
23
|
+
def subscription(options = {})
|
26
24
|
sub = Subscription.where(customer_id: stripe_id).first
|
27
25
|
if sub.present? && options[:remote]
|
28
26
|
sub = Stripe::Subscription.retrieve(sub.stripe_id)
|
@@ -34,9 +32,19 @@ module SaasPayments
|
|
34
32
|
end
|
35
33
|
|
36
34
|
def plan
|
37
|
-
return nil if
|
35
|
+
return nil if subscription.blank?
|
38
36
|
Plan.find_by_stripe_id(subscription.plan_id)
|
39
37
|
end
|
40
38
|
|
39
|
+
def products
|
40
|
+
items = []
|
41
|
+
Order.where(customer_id: stripe_id).map do |order|
|
42
|
+
order.items.each do |item|
|
43
|
+
sku = Sku.find_by_stripe_id(item[:parent].to_s)
|
44
|
+
items << sku if sku.present?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
items
|
48
|
+
end
|
41
49
|
end
|
42
50
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module SaasPayments
|
2
|
+
class Order < ApplicationRecord
|
3
|
+
include StripeModel
|
4
|
+
|
5
|
+
serialize :metadata
|
6
|
+
serialize :status_transitions
|
7
|
+
serialize :items
|
8
|
+
|
9
|
+
def self.from_stripe(o)
|
10
|
+
{
|
11
|
+
stripe_id: parse_id(o[:id]),
|
12
|
+
customer_id: parse_id(o[:customer]),
|
13
|
+
amount: o[:amount],
|
14
|
+
amount_returned: o[:amount_returned],
|
15
|
+
application: o[:application],
|
16
|
+
application_fee: o[:application_fee],
|
17
|
+
charge: o[:charge],
|
18
|
+
currency: o[:currency],
|
19
|
+
email: o[:email],
|
20
|
+
livemode: o[:livemode],
|
21
|
+
status: o[:status],
|
22
|
+
metadata: stripe_hash(o[:metadata]),
|
23
|
+
status_transitions: stripe_hash(o[:status_transitions]),
|
24
|
+
items: stripe_array(o[:items]),
|
25
|
+
}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -6,10 +6,9 @@ module SaasPayments
|
|
6
6
|
|
7
7
|
serialize :metadata
|
8
8
|
|
9
|
-
def self.from_stripe
|
9
|
+
def self.from_stripe(p)
|
10
10
|
{
|
11
11
|
product_id: parse_id(p[:product]),
|
12
|
-
|
13
12
|
stripe_id: p[:id],
|
14
13
|
active: p[:active],
|
15
14
|
amount: p[:amount].to_i,
|
@@ -20,9 +19,8 @@ module SaasPayments
|
|
20
19
|
livemode: truthy?(p[:livemode]),
|
21
20
|
metadata: stripe_hash(p[:metadata]),
|
22
21
|
nickname: p[:nickname],
|
23
|
-
trial_period_days: p[:trial_period_days].to_i
|
22
|
+
trial_period_days: p[:trial_period_days].to_i,
|
24
23
|
}
|
25
|
-
|
26
24
|
end
|
27
25
|
end
|
28
26
|
end
|
@@ -8,17 +8,16 @@ module SaasPayments
|
|
8
8
|
|
9
9
|
serialize :metadata
|
10
10
|
|
11
|
-
def self.from_stripe
|
11
|
+
def self.from_stripe(p)
|
12
12
|
{
|
13
|
-
stripe_id:
|
14
|
-
active:
|
15
|
-
caption:
|
16
|
-
livemode:
|
17
|
-
metadata:
|
18
|
-
name:
|
19
|
-
unit_label:
|
13
|
+
stripe_id: p[:id],
|
14
|
+
active: p[:active],
|
15
|
+
caption: p[:caption],
|
16
|
+
livemode: truthy?(p[:livemode]),
|
17
|
+
metadata: stripe_hash(p[:metadata]),
|
18
|
+
name: p[:name],
|
19
|
+
unit_label: p[:unit_label],
|
20
20
|
}
|
21
21
|
end
|
22
|
-
|
23
22
|
end
|
24
23
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module SaasPayments
|
2
|
+
class Sku < ApplicationRecord
|
3
|
+
include StripeModel
|
4
|
+
|
5
|
+
serialize :metadata
|
6
|
+
serialize :attrs
|
7
|
+
serialize :inventory
|
8
|
+
serialize :package_dimensions
|
9
|
+
|
10
|
+
def self.from_stripe(p)
|
11
|
+
{
|
12
|
+
product_id: parse_id(p[:product]),
|
13
|
+
stripe_id: p[:id],
|
14
|
+
active: p[:active],
|
15
|
+
currency: p[:currency],
|
16
|
+
image: p[:image],
|
17
|
+
livemode: truthy?(p[:livemode]),
|
18
|
+
price: p[:price].to_i,
|
19
|
+
package_dimensions: stripe_hash(p[:package_dimensions]),
|
20
|
+
metadata: stripe_hash(p[:metadata]),
|
21
|
+
inventory: stripe_hash(p[:inventory]),
|
22
|
+
attrs: stripe_hash(p[:attributes]),
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
def name
|
27
|
+
attrs[:name]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -6,7 +6,7 @@ module SaasPayments
|
|
6
6
|
|
7
7
|
serialize :metadata
|
8
8
|
|
9
|
-
def self.from_stripe
|
9
|
+
def self.from_stripe(s)
|
10
10
|
{
|
11
11
|
plan_id: parse_id(s[:plan]),
|
12
12
|
customer_id: parse_id(s[:customer]),
|
@@ -23,9 +23,8 @@ module SaasPayments
|
|
23
23
|
start_date: date(s[:start_date]),
|
24
24
|
status: s[:status], # TODO: Status might not come from stripe?
|
25
25
|
trial_end: date(s[:trial_end]),
|
26
|
-
trial_start: date(s[:trial_start])
|
26
|
+
trial_start: date(s[:trial_start]),
|
27
27
|
}
|
28
28
|
end
|
29
|
-
|
30
29
|
end
|
31
30
|
end
|
@@ -1,11 +1,13 @@
|
|
1
1
|
<% css_id = 'sp_form' if local_assigns[:css_id].nil? %>
|
2
2
|
<% user_id = nil if local_assigns[:user_id].nil? %>
|
3
3
|
<% plan_id = nil if local_assigns[:plan_id].nil? %>
|
4
|
+
<% sku_id = nil if local_assigns[:sku_id].nil? %>
|
4
5
|
|
5
6
|
<form action="<%= submit_path %>" method="post" class="payment-form" id="<%= css_id %>" style="display: none">
|
6
7
|
<%= hidden_field_tag :authenticity_token, form_authenticity_token -%>
|
7
8
|
<%= hidden_field_tag :user_id, user_id, class: 'form_user_id' -%>
|
8
9
|
<%= hidden_field_tag :plan_id, plan_id, class: 'form_plan_id' -%>
|
10
|
+
<%= hidden_field_tag :sku_id, sku_id, class: 'form_sku_id' -%>
|
9
11
|
|
10
12
|
<div id="publishable-key" data-publishable="<%= SaasPayments.config.stripe_publishable_key %>"></div>
|
11
13
|
<div class="form-row">
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class CreateSaasPaymentsSkus < ActiveRecord::Migration[5.2]
|
2
|
+
def change
|
3
|
+
create_table :saas_payments_skus do |t|
|
4
|
+
t.string :stripe_id
|
5
|
+
t.string :product_id
|
6
|
+
|
7
|
+
t.boolean :active
|
8
|
+
t.string :currency
|
9
|
+
t.string :image
|
10
|
+
t.boolean :livemode
|
11
|
+
t.integer :price
|
12
|
+
|
13
|
+
# Serialized objects
|
14
|
+
t.text :package_dimensions
|
15
|
+
t.text :metadata
|
16
|
+
t.text :inventory
|
17
|
+
t.text :attrs
|
18
|
+
|
19
|
+
t.timestamps
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class CreateSaasPaymentsOrders < ActiveRecord::Migration[5.2]
|
2
|
+
def change
|
3
|
+
create_table :saas_payments_orders do |t|
|
4
|
+
t.string :stripe_id
|
5
|
+
t.string :customer_id
|
6
|
+
t.integer :amount
|
7
|
+
t.integer :amount_returned
|
8
|
+
t.string :application
|
9
|
+
t.integer :application_fee
|
10
|
+
t.string :charge
|
11
|
+
t.string :currency
|
12
|
+
t.string :email
|
13
|
+
t.boolean :livemode
|
14
|
+
t.string :status
|
15
|
+
|
16
|
+
# Serialize
|
17
|
+
t.text :metadata
|
18
|
+
t.text :status_transitions
|
19
|
+
t.text :items
|
20
|
+
|
21
|
+
t.timestamps
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/saas_payments/config.rb
CHANGED
data/lib/saas_payments/engine.rb
CHANGED
data/lib/tasks/products.rake
CHANGED
@@ -1,6 +1,20 @@
|
|
1
1
|
namespace :products do
|
2
2
|
desc "Create products in the database that come from stripe"
|
3
3
|
task sync: :environment do
|
4
|
-
SaasPayments::ProductsService.sync
|
4
|
+
SaasPayments::ProductsService.new.sync
|
5
|
+
end
|
6
|
+
|
7
|
+
desc "Digitize a product by toggling the shippable flag"
|
8
|
+
task :digitize, [:product, :shippable] => :environment do |t, args|
|
9
|
+
product = args.product
|
10
|
+
shippable = args.shippable.to_s == 'true'
|
11
|
+
|
12
|
+
if !['true', 'false'].include? args.shippable.to_s
|
13
|
+
puts "Shippable must be a boolean"
|
14
|
+
next
|
15
|
+
end
|
16
|
+
|
17
|
+
success = SaasPayments::ProductsService.new.shippable product, shippable
|
18
|
+
puts "Could not digitize product: #{args.product}" unless success
|
5
19
|
end
|
6
20
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: saas_payments
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cody
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-10-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -123,25 +123,29 @@ files:
|
|
123
123
|
- app/assets/javascripts/saas_payments/elements.js
|
124
124
|
- app/assets/stylesheets/saas_payments/application.css
|
125
125
|
- app/assets/stylesheets/saas_payments/elements.css
|
126
|
-
- app/controllers/concerns/saas_payments/
|
127
|
-
- app/controllers/concerns/saas_payments/
|
126
|
+
- app/controllers/concerns/saas_payments/payable.rb
|
127
|
+
- app/controllers/concerns/saas_payments/webhook.rb
|
128
128
|
- app/controllers/saas_payments/application_controller.rb
|
129
129
|
- app/controllers/saas_payments/webhook_controller.rb
|
130
130
|
- app/helpers/saas_payments/application_helper.rb
|
131
131
|
- app/jobs/saas_payments/application_job.rb
|
132
132
|
- app/lib/saas_payments/products_service.rb
|
133
133
|
- app/lib/saas_payments/webhook/customer_event.rb
|
134
|
+
- app/lib/saas_payments/webhook/order_event.rb
|
134
135
|
- app/lib/saas_payments/webhook/plan_event.rb
|
135
136
|
- app/lib/saas_payments/webhook/product_event.rb
|
136
137
|
- app/lib/saas_payments/webhook/session_event.rb
|
138
|
+
- app/lib/saas_payments/webhook/sku_event.rb
|
137
139
|
- app/lib/saas_payments/webhook/subscription_event.rb
|
138
140
|
- app/lib/saas_payments/webhook_service.rb
|
139
141
|
- app/mailers/saas_payments/application_mailer.rb
|
140
142
|
- app/models/concerns/saas_payments/stripe_model.rb
|
141
143
|
- app/models/saas_payments/application_record.rb
|
142
144
|
- app/models/saas_payments/customer.rb
|
145
|
+
- app/models/saas_payments/order.rb
|
143
146
|
- app/models/saas_payments/plan.rb
|
144
147
|
- app/models/saas_payments/product.rb
|
148
|
+
- app/models/saas_payments/sku.rb
|
145
149
|
- app/models/saas_payments/subscription.rb
|
146
150
|
- app/views/saas_payments/_scripts.html.erb
|
147
151
|
- app/views/saas_payments/_stripe_elements.html.erb
|
@@ -151,6 +155,9 @@ files:
|
|
151
155
|
- db/migrate/20190820040835_create_saas_payments_products.rb
|
152
156
|
- db/migrate/20190820040959_create_saas_payments_plans.rb
|
153
157
|
- db/migrate/20190823023237_create_saas_payments_customers.rb
|
158
|
+
- db/migrate/20191006144720_create_saas_payments_skus.rb
|
159
|
+
- db/migrate/20191008134837_remove_user_id_from_customer.rb
|
160
|
+
- db/migrate/20191010013513_create_saas_payments_orders.rb
|
154
161
|
- lib/saas_payments.rb
|
155
162
|
- lib/saas_payments/config.rb
|
156
163
|
- lib/saas_payments/engine.rb
|
@@ -1,173 +0,0 @@
|
|
1
|
-
module SaasPayments
|
2
|
-
module SubscriptionConcerns
|
3
|
-
Stripe.api_key = SaasPayments.config.stripe_secret_key
|
4
|
-
|
5
|
-
class FailedPayment < StandardError; end
|
6
|
-
class ActionRequired < StandardError; end
|
7
|
-
|
8
|
-
extend ActiveSupport::Concern
|
9
|
-
|
10
|
-
def sign_up_for_plan user, plan
|
11
|
-
customer = get_customer user
|
12
|
-
sub = update_subscription customer, plan
|
13
|
-
render_success
|
14
|
-
rescue ActionRequired
|
15
|
-
render_action_required
|
16
|
-
rescue FailedPayment
|
17
|
-
render_card_failure
|
18
|
-
rescue Stripe::CardError
|
19
|
-
render_card_failure
|
20
|
-
end
|
21
|
-
|
22
|
-
def change_plan user, plan
|
23
|
-
customer = get_customer user
|
24
|
-
sub = customer.subscription(remote: true)
|
25
|
-
update_plan sub, customer, plan
|
26
|
-
render_success
|
27
|
-
rescue StandardError
|
28
|
-
render_error
|
29
|
-
end
|
30
|
-
|
31
|
-
def cancel_at_period_end user
|
32
|
-
customer = get_customer user
|
33
|
-
sub = customer.subscription(remote: true)
|
34
|
-
|
35
|
-
# Customer has an existing subscription, change plans
|
36
|
-
remote_sub = Stripe::Subscription.update(sub[:id], {
|
37
|
-
cancel_at_period_end: true
|
38
|
-
})
|
39
|
-
|
40
|
-
customer.subscription.update(Subscription.from_stripe(remote_sub))
|
41
|
-
render_success
|
42
|
-
rescue StandardError
|
43
|
-
render_error
|
44
|
-
end
|
45
|
-
|
46
|
-
def resume_subscription user
|
47
|
-
customer = get_customer user
|
48
|
-
sub = customer.subscription
|
49
|
-
remote_sub = Stripe::Subscription.update(sub.stripe_id, {
|
50
|
-
cancel_at_period_end: false
|
51
|
-
})
|
52
|
-
|
53
|
-
customer.subscription.update(Subscription.from_stripe(remote_sub))
|
54
|
-
render_success
|
55
|
-
rescue StandardError
|
56
|
-
render_error
|
57
|
-
end
|
58
|
-
|
59
|
-
def cancel_now user
|
60
|
-
customer = get_customer user
|
61
|
-
Stripe::Subscription.delete(customer.subscription.stripe_id)
|
62
|
-
end
|
63
|
-
|
64
|
-
|
65
|
-
private
|
66
|
-
|
67
|
-
def render_success
|
68
|
-
respond_to do |format|
|
69
|
-
format.html { redirect_to SaasPayments.config.account_path }
|
70
|
-
format.json { render json: { message: "success" } }
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
def render_error
|
75
|
-
format.html {
|
76
|
-
flash[:sp_error] = 'Failed to update plan'
|
77
|
-
redirect_back fallback_location: root_path
|
78
|
-
}
|
79
|
-
format.json { render json: { message: "update_failed" }, status: :bad_request }
|
80
|
-
end
|
81
|
-
|
82
|
-
def render_action_required
|
83
|
-
respond_to do |format|
|
84
|
-
format.html {
|
85
|
-
flash[:sp_notice] = 'Payment requires customer action'
|
86
|
-
redirect_to SaasPayments.successPath
|
87
|
-
}
|
88
|
-
format.json { render json: { message: "action_required" } }
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
def render_card_failure
|
93
|
-
respond_to do |format|
|
94
|
-
format.html {
|
95
|
-
flash[:sp_error] = 'Failed to process card'
|
96
|
-
redirect_back fallback_location: root_path
|
97
|
-
}
|
98
|
-
format.json { render json: { message: "card_error" }, status: :bad_request }
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
def get_customer user
|
103
|
-
# Try to find a customer in the database by the user id
|
104
|
-
customer = SaasPayments::Customer.find_by_user_id(user.id)
|
105
|
-
|
106
|
-
# If no customer exists, create a new one
|
107
|
-
if !customer.present?
|
108
|
-
# Create a new customer in stripe
|
109
|
-
c = Stripe::Customer.create({
|
110
|
-
email: user.email,
|
111
|
-
source: params.require(:stripeToken),
|
112
|
-
metadata: { user_id: user.id }
|
113
|
-
})
|
114
|
-
|
115
|
-
# Create a new customer locally that maps to the stripe customer
|
116
|
-
customer = Customer.create(Customer.from_stripe(c).merge({ user_id: user.id }))
|
117
|
-
end
|
118
|
-
customer
|
119
|
-
end
|
120
|
-
|
121
|
-
def update_subscription customer, plan
|
122
|
-
sub = customer.subscription(remote: true)
|
123
|
-
if sub.present?
|
124
|
-
update_plan sub, customer, plan
|
125
|
-
else
|
126
|
-
sub = create_subscription customer, plan
|
127
|
-
end
|
128
|
-
|
129
|
-
sub
|
130
|
-
end
|
131
|
-
|
132
|
-
def update_plan sub, customer, plan
|
133
|
-
# Customer has an existing subscription, change plans
|
134
|
-
remote_sub = Stripe::Subscription.update(sub[:id], {
|
135
|
-
cancel_at_period_end: false,
|
136
|
-
items: [{
|
137
|
-
id: sub[:items][:data][0][:id],
|
138
|
-
plan: plan.stripe_id
|
139
|
-
}]
|
140
|
-
})
|
141
|
-
|
142
|
-
customer.subscription.update(Subscription.from_stripe(remote_sub))
|
143
|
-
end
|
144
|
-
|
145
|
-
def create_subscription customer, plan
|
146
|
-
# Customer does not have a subscription, sign them up
|
147
|
-
sub = Stripe::Subscription.create({
|
148
|
-
customer: customer.stripe_id,
|
149
|
-
items: [{ plan: plan.stripe_id }]
|
150
|
-
})
|
151
|
-
complete_subscription sub
|
152
|
-
end
|
153
|
-
|
154
|
-
def complete_subscription sub
|
155
|
-
if incomplete?(sub)
|
156
|
-
raise FailedPayment if payment_intent(sub, :requires_payment_method)
|
157
|
-
raise ActionRequired if payment_intent(sub, :requires_action)
|
158
|
-
end
|
159
|
-
|
160
|
-
# Successful payment, or trialling: provision the subscription
|
161
|
-
# Create a local subscription
|
162
|
-
Subscription.create(Subscription.from_stripe(sub))
|
163
|
-
end
|
164
|
-
|
165
|
-
def incomplete? sub
|
166
|
-
sub[:status] == "incomplete"
|
167
|
-
end
|
168
|
-
|
169
|
-
def payment_intent sub, intent
|
170
|
-
sub[:latest_invoice][:payment_intent][:status] == intent.to_s
|
171
|
-
end
|
172
|
-
end
|
173
|
-
end
|
@@ -1,22 +0,0 @@
|
|
1
|
-
module SaasPayments
|
2
|
-
module WebhookConcerns
|
3
|
-
extend ActiveSupport::Concern
|
4
|
-
Stripe.api_key = SaasPayments.config.stripe_secret_key
|
5
|
-
SECRET = SaasPayments.config.webhook_secret
|
6
|
-
|
7
|
-
def webhook
|
8
|
-
event = params
|
9
|
-
if SaasPayments.config.webhook_secret.present?
|
10
|
-
header = request.headers['HTTP_STRIPE_SIGNATURE']
|
11
|
-
event = Stripe::Webhook.construct_event(request.body.read, header, SECRET)
|
12
|
-
end
|
13
|
-
WebhookService.new(event).process
|
14
|
-
head :ok
|
15
|
-
rescue ActiveRecord::RecordNotFound
|
16
|
-
head :not_found
|
17
|
-
rescue StandardError => e
|
18
|
-
head :bad_request
|
19
|
-
end
|
20
|
-
|
21
|
-
end
|
22
|
-
end
|