saas_payments 0.0.2 → 0.1.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 +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
|