effective_orders 4.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/MIT-LICENSE +20 -0
- data/README.md +1004 -0
- data/app/assets/images/effective_orders/stripe.png +0 -0
- data/app/assets/javascripts/effective_orders.js +6 -0
- data/app/assets/javascripts/effective_orders/customers.js.coffee +32 -0
- data/app/assets/javascripts/effective_orders/providers/stripe.js.coffee +77 -0
- data/app/assets/javascripts/effective_orders/subscriptions.js.coffee +81 -0
- data/app/assets/stylesheets/effective_orders.scss +2 -0
- data/app/assets/stylesheets/effective_orders/_cart.scss +4 -0
- data/app/assets/stylesheets/effective_orders/_order.scss +58 -0
- data/app/controllers/admin/customers_controller.rb +24 -0
- data/app/controllers/admin/order_items_controller.rb +16 -0
- data/app/controllers/admin/orders_controller.rb +223 -0
- data/app/controllers/effective/carts_controller.rb +85 -0
- data/app/controllers/effective/concerns/purchase.rb +62 -0
- data/app/controllers/effective/customers_controller.rb +20 -0
- data/app/controllers/effective/orders_controller.rb +162 -0
- data/app/controllers/effective/providers/cheque.rb +22 -0
- data/app/controllers/effective/providers/free.rb +33 -0
- data/app/controllers/effective/providers/mark_as_paid.rb +33 -0
- data/app/controllers/effective/providers/moneris.rb +60 -0
- data/app/controllers/effective/providers/paypal.rb +33 -0
- data/app/controllers/effective/providers/phone.rb +22 -0
- data/app/controllers/effective/providers/pretend.rb +26 -0
- data/app/controllers/effective/providers/refund.rb +33 -0
- data/app/controllers/effective/providers/stripe.rb +72 -0
- data/app/controllers/effective/subscripter_controller.rb +18 -0
- data/app/controllers/effective/webhooks_controller.rb +109 -0
- data/app/datatables/admin/effective_customers_datatable.rb +22 -0
- data/app/datatables/admin/effective_orders_datatable.rb +100 -0
- data/app/datatables/effective_orders_datatable.rb +79 -0
- data/app/helpers/effective_carts_helper.rb +113 -0
- data/app/helpers/effective_orders_helper.rb +143 -0
- data/app/helpers/effective_paypal_helper.rb +49 -0
- data/app/helpers/effective_stripe_helper.rb +85 -0
- data/app/helpers/effective_subscriptions_helper.rb +34 -0
- data/app/mailers/effective/orders_mailer.rb +196 -0
- data/app/models/concerns/acts_as_purchasable.rb +118 -0
- data/app/models/concerns/acts_as_subscribable.rb +90 -0
- data/app/models/concerns/acts_as_subscribable_buyer.rb +49 -0
- data/app/models/effective/access_denied.rb +17 -0
- data/app/models/effective/cart.rb +88 -0
- data/app/models/effective/cart_item.rb +40 -0
- data/app/models/effective/customer.rb +92 -0
- data/app/models/effective/order.rb +541 -0
- data/app/models/effective/order_item.rb +63 -0
- data/app/models/effective/product.rb +23 -0
- data/app/models/effective/sold_out_validator.rb +7 -0
- data/app/models/effective/subscripter.rb +185 -0
- data/app/models/effective/subscription.rb +95 -0
- data/app/models/effective/tax_rate_calculator.rb +48 -0
- data/app/views/admin/customers/_actions.html.haml +2 -0
- data/app/views/admin/customers/index.html.haml +6 -0
- data/app/views/admin/customers/show.html.haml +6 -0
- data/app/views/admin/order_items/index.html.haml +3 -0
- data/app/views/admin/orders/_datatable_actions.html.haml +18 -0
- data/app/views/admin/orders/_form.html.haml +35 -0
- data/app/views/admin/orders/_form_note_internal.html.haml +7 -0
- data/app/views/admin/orders/_order_actions.html.haml +9 -0
- data/app/views/admin/orders/_order_item_fields.html.haml +14 -0
- data/app/views/admin/orders/checkout.html.haml +3 -0
- data/app/views/admin/orders/edit.html.haml +6 -0
- data/app/views/admin/orders/index.html.haml +6 -0
- data/app/views/admin/orders/new.html.haml +4 -0
- data/app/views/admin/orders/show.html.haml +4 -0
- data/app/views/effective/carts/_cart.html.haml +28 -0
- data/app/views/effective/carts/_cart_actions.html.haml +3 -0
- data/app/views/effective/carts/show.html.haml +17 -0
- data/app/views/effective/customers/_customer.html.haml +72 -0
- data/app/views/effective/customers/_form.html.haml +21 -0
- data/app/views/effective/customers/edit.html.haml +4 -0
- data/app/views/effective/customers/update.js.erb +5 -0
- data/app/views/effective/orders/_checkout_actions.html.haml +3 -0
- data/app/views/effective/orders/_checkout_step1.html.haml +4 -0
- data/app/views/effective/orders/_checkout_step2.html.haml +37 -0
- data/app/views/effective/orders/_datatable_actions.html.haml +2 -0
- data/app/views/effective/orders/_fields.html.haml +31 -0
- data/app/views/effective/orders/_fields_note.html.haml +7 -0
- data/app/views/effective/orders/_fields_terms.html.haml +8 -0
- data/app/views/effective/orders/_order.html.haml +11 -0
- data/app/views/effective/orders/_order_actions.html.haml +18 -0
- data/app/views/effective/orders/_order_deferred.html.haml +9 -0
- data/app/views/effective/orders/_order_footer.html.haml +1 -0
- data/app/views/effective/orders/_order_header.html.haml +23 -0
- data/app/views/effective/orders/_order_items.html.haml +72 -0
- data/app/views/effective/orders/_order_notes.html.haml +17 -0
- data/app/views/effective/orders/_order_payment.html.haml +24 -0
- data/app/views/effective/orders/_order_shipping.html.haml +30 -0
- data/app/views/effective/orders/_orders_table.html.haml +23 -0
- data/app/views/effective/orders/cheque/_form.html.haml +4 -0
- data/app/views/effective/orders/declined.html.haml +12 -0
- data/app/views/effective/orders/deferred.html.haml +13 -0
- data/app/views/effective/orders/deferred/_form.html.haml +16 -0
- data/app/views/effective/orders/edit.html.haml +3 -0
- data/app/views/effective/orders/free/_form.html.haml +5 -0
- data/app/views/effective/orders/index.html.haml +3 -0
- data/app/views/effective/orders/mark_as_paid/_form.html.haml +23 -0
- data/app/views/effective/orders/moneris/_form.html.haml +47 -0
- data/app/views/effective/orders/new.html.haml +3 -0
- data/app/views/effective/orders/paypal/_form.html.haml +5 -0
- data/app/views/effective/orders/phone/_form.html.haml +4 -0
- data/app/views/effective/orders/pretend/_form.html.haml +8 -0
- data/app/views/effective/orders/purchased.html.haml +11 -0
- data/app/views/effective/orders/refund/_form.html.haml +5 -0
- data/app/views/effective/orders/show.html.haml +6 -0
- data/app/views/effective/orders/stripe/_element.html.haml +8 -0
- data/app/views/effective/orders/stripe/_form.html.haml +31 -0
- data/app/views/effective/orders_mailer/order_error.html.haml +11 -0
- data/app/views/effective/orders_mailer/order_receipt_to_admin.html.haml +2 -0
- data/app/views/effective/orders_mailer/order_receipt_to_buyer.html.haml +2 -0
- data/app/views/effective/orders_mailer/payment_request_to_buyer.html.haml +13 -0
- data/app/views/effective/orders_mailer/pending_order_invoice_to_buyer.html.haml +13 -0
- data/app/views/effective/orders_mailer/refund_notification_to_admin.html.haml +15 -0
- data/app/views/effective/orders_mailer/subscription_canceled.html.haml +9 -0
- data/app/views/effective/orders_mailer/subscription_created.html.haml +13 -0
- data/app/views/effective/orders_mailer/subscription_event_to_admin.html.haml +13 -0
- data/app/views/effective/orders_mailer/subscription_payment_failed.html.haml +9 -0
- data/app/views/effective/orders_mailer/subscription_payment_succeeded.html.haml +9 -0
- data/app/views/effective/orders_mailer/subscription_trial_expired.html.haml +5 -0
- data/app/views/effective/orders_mailer/subscription_trialing.html.haml +7 -0
- data/app/views/effective/orders_mailer/subscription_updated.html.haml +13 -0
- data/app/views/effective/subscripter/_form.html.haml +60 -0
- data/app/views/effective/subscripter/_plan.html.haml +23 -0
- data/app/views/layouts/effective_orders_mailer_layout.html.haml +25 -0
- data/config/effective_orders.rb +279 -0
- data/config/routes.rb +70 -0
- data/db/migrate/01_create_effective_orders.rb.erb +137 -0
- data/lib/effective_orders.rb +243 -0
- data/lib/effective_orders/engine.rb +60 -0
- data/lib/effective_orders/version.rb +3 -0
- data/lib/generators/effective_orders/install_generator.rb +63 -0
- data/lib/generators/templates/effective_orders_mailer_preview.rb +120 -0
- data/lib/tasks/effective_orders_tasks.rake +69 -0
- metadata +276 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
module Effective
|
|
2
|
+
class OrderItem < ActiveRecord::Base
|
|
3
|
+
self.table_name = EffectiveOrders.order_items_table_name.to_s
|
|
4
|
+
|
|
5
|
+
belongs_to :order, class_name: 'Effective::Order'
|
|
6
|
+
belongs_to :purchasable, polymorphic: true
|
|
7
|
+
|
|
8
|
+
# Attributes
|
|
9
|
+
# name :string
|
|
10
|
+
# quantity :integer
|
|
11
|
+
# price :integer, default: 0
|
|
12
|
+
# tax_exempt :boolean
|
|
13
|
+
# timestamps
|
|
14
|
+
|
|
15
|
+
validates :purchasable, associated: true, presence: true
|
|
16
|
+
accepts_nested_attributes_for :purchasable
|
|
17
|
+
|
|
18
|
+
validates :name, presence: true
|
|
19
|
+
validates :quantity, presence: true, numericality: { greater_than: 0 }
|
|
20
|
+
validates :price, presence: true
|
|
21
|
+
validates :tax_exempt, inclusion: { in: [true, false] }
|
|
22
|
+
|
|
23
|
+
scope :purchased, -> { where(order_id: Effective::Order.purchased) }
|
|
24
|
+
scope :purchased_by, lambda { |user| where(order_id: Effective::Order.purchased_by(user)) }
|
|
25
|
+
|
|
26
|
+
def to_s
|
|
27
|
+
(quantity || 0) > 1 ? "#{quantity}x #{name}" : name
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def purchased_download_url
|
|
31
|
+
purchasable&.purchased_download_url
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def subtotal
|
|
35
|
+
price * quantity
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def quantity
|
|
39
|
+
self[:quantity] || 1
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def tax
|
|
43
|
+
return 0 if tax_exempt?
|
|
44
|
+
raise 'parent Effective::Order must have a tax_rate to compute order item tax' unless order.try(:tax_rate).present?
|
|
45
|
+
(subtotal * order.tax_rate / 100.0).round(0).to_i
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def total
|
|
49
|
+
return subtotal if tax_exempt?
|
|
50
|
+
raise 'parent Effective::Order must have a tax_rate to compute order item total' unless order.try(:tax_rate).present?
|
|
51
|
+
subtotal + tax
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def price=(value)
|
|
55
|
+
if value.kind_of?(Integer)
|
|
56
|
+
super
|
|
57
|
+
else
|
|
58
|
+
raise 'expected price to be an Integer representing the number of cents.'
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Effective
|
|
2
|
+
class Product < ActiveRecord::Base
|
|
3
|
+
self.table_name = EffectiveOrders.products_table_name.to_s
|
|
4
|
+
|
|
5
|
+
acts_as_purchasable
|
|
6
|
+
|
|
7
|
+
# Attributes
|
|
8
|
+
# name :string
|
|
9
|
+
# price :integer, default: 0
|
|
10
|
+
# tax_exempt :boolean, default: false
|
|
11
|
+
#
|
|
12
|
+
# timestamps
|
|
13
|
+
|
|
14
|
+
validates :name, presence: true
|
|
15
|
+
validates :price, presence: true
|
|
16
|
+
validates :tax_exempt, inclusion: { in: [true, false] }
|
|
17
|
+
|
|
18
|
+
def to_s
|
|
19
|
+
name || 'New Product'
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# Form object to select a plan, and build the correct subscription and customer
|
|
2
|
+
|
|
3
|
+
module Effective
|
|
4
|
+
class Subscripter
|
|
5
|
+
include ActiveModel::Model
|
|
6
|
+
|
|
7
|
+
attr_accessor :current_user, :user, :subscribable, :customer
|
|
8
|
+
attr_accessor :subscribable_global_id, :stripe_token, :stripe_plan_id, :quantity
|
|
9
|
+
|
|
10
|
+
validates :user, presence: true
|
|
11
|
+
validates :subscribable, presence: true, if: -> { stripe_plan_id.present? }
|
|
12
|
+
validates :customer, presence: true
|
|
13
|
+
|
|
14
|
+
validates :stripe_plan_id, inclusion: {
|
|
15
|
+
allow_blank: true,
|
|
16
|
+
in: EffectiveOrders.stripe_plans.map { |plan| plan[:id] },
|
|
17
|
+
message: 'unknown plan'
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
validate(if: -> { stripe_plan_id && plan && plan[:amount] > 0 }) do
|
|
21
|
+
self.errors.add(:stripe_token, 'updated payment card required') if stripe_token.blank? && token_required?
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
validate(if: -> { stripe_plan_id && subscribable }) do
|
|
25
|
+
quantity_used = [subscribable.subscribable_quantity_used, 0].max
|
|
26
|
+
self.errors.add(:quantity, "must be #{quantity_used} or greater") unless quantity >= quantity_used
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def to_s
|
|
30
|
+
'Your Plan'
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def customer
|
|
34
|
+
@customer ||= Effective::Customer.deep.where(user: user).first_or_initialize
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def current_user=(user)
|
|
38
|
+
@user = user
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def subscribable_global_id
|
|
42
|
+
subscribable&.to_global_id
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def subscribable_global_id=(global_id)
|
|
46
|
+
@subscribable = GlobalID::Locator.locate(global_id)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def plan
|
|
50
|
+
EffectiveOrders.stripe_plans.find { |plan| plan[:id] == stripe_plan_id }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def quantity=(value)
|
|
54
|
+
@quantity = (value.to_i if value)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def token_required?
|
|
58
|
+
customer.token_required?
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def save!
|
|
62
|
+
raise 'is invalid' unless valid?
|
|
63
|
+
|
|
64
|
+
create_customer!
|
|
65
|
+
create_stripe_token!
|
|
66
|
+
save_subscription!
|
|
67
|
+
true
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def destroy!
|
|
71
|
+
subscription = subscribable.subscription
|
|
72
|
+
return true if subscription.blank?
|
|
73
|
+
|
|
74
|
+
Rails.logger.info " -> [STRIPE] delete subscription"
|
|
75
|
+
subscription.stripe_subscription.delete
|
|
76
|
+
subscription.destroy!
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
protected
|
|
80
|
+
|
|
81
|
+
def create_customer!
|
|
82
|
+
customer.create_stripe_customer!
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Update stripe customer card
|
|
86
|
+
def create_stripe_token!
|
|
87
|
+
return if stripe_token.blank?
|
|
88
|
+
|
|
89
|
+
Rails.logger.info "[STRIPE] update source: #{stripe_token}"
|
|
90
|
+
customer.stripe_customer.source = stripe_token
|
|
91
|
+
customer.stripe_customer.save
|
|
92
|
+
|
|
93
|
+
return if customer.stripe_customer.default_source.blank?
|
|
94
|
+
|
|
95
|
+
card = customer.stripe_customer.sources.retrieve(customer.stripe_customer.default_source)
|
|
96
|
+
customer.active_card = "**** **** **** #{card.last4} #{card.brand} #{card.exp_month}/#{card.exp_year}"
|
|
97
|
+
customer.save!
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def save_subscription!
|
|
101
|
+
return unless plan.present?
|
|
102
|
+
|
|
103
|
+
subscription.assign_attributes(stripe_plan_id: stripe_plan_id, quantity: quantity)
|
|
104
|
+
|
|
105
|
+
cancel_subscription!
|
|
106
|
+
create_subscription! || update_subscription!
|
|
107
|
+
true
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def cancel_subscription!
|
|
111
|
+
return false unless subscription.persisted? && subscription.stripe_plan_id_changed?
|
|
112
|
+
|
|
113
|
+
item = items.first
|
|
114
|
+
stripe_item = subscription.stripe_subscription.items.first
|
|
115
|
+
|
|
116
|
+
return false unless stripe_item.present? && item[:plan] != stripe_item['plan']['id']
|
|
117
|
+
|
|
118
|
+
Rails.logger.info " -> [STRIPE] cancel plan: #{stripe_item['plan']['id']}"
|
|
119
|
+
subscription.stripe_subscription.delete
|
|
120
|
+
subscription.assign_attributes(stripe_subscription: nil, stripe_subscription_id: nil)
|
|
121
|
+
|
|
122
|
+
true
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def create_subscription!
|
|
126
|
+
return false unless subscription.stripe_subscription.blank?
|
|
127
|
+
|
|
128
|
+
Rails.logger.info "[STRIPE] create subscription: #{items}"
|
|
129
|
+
stripe_subscription = Stripe::Subscription.create(customer: customer.stripe_customer_id, items: items, metadata: metadata)
|
|
130
|
+
|
|
131
|
+
subscription.update!(
|
|
132
|
+
stripe_subscription: stripe_subscription,
|
|
133
|
+
stripe_subscription_id: stripe_subscription.id,
|
|
134
|
+
status: stripe_subscription.status,
|
|
135
|
+
name: stripe_subscription.plan.nickname,
|
|
136
|
+
interval: stripe_subscription.plan.interval,
|
|
137
|
+
quantity: quantity
|
|
138
|
+
)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def update_subscription!
|
|
142
|
+
return false unless subscription.stripe_subscription.present?
|
|
143
|
+
|
|
144
|
+
stripe_item = subscription.stripe_subscription.items.first
|
|
145
|
+
item = items.first
|
|
146
|
+
|
|
147
|
+
return false unless stripe_item.present? && item[:plan] == stripe_item['plan']['id']
|
|
148
|
+
return false unless item[:quantity] != subscription.stripe_subscription.quantity
|
|
149
|
+
|
|
150
|
+
Rails.logger.info " -> [STRIPE] update plan: #{item[:plan]}"
|
|
151
|
+
stripe_item.quantity = item[:quantity]
|
|
152
|
+
stripe_item.save
|
|
153
|
+
|
|
154
|
+
subscription.update!(status: subscription.stripe_subscription.status)
|
|
155
|
+
|
|
156
|
+
# Invoice immediately
|
|
157
|
+
Rails.logger.info " -> [STRIPE] generate invoice"
|
|
158
|
+
Stripe::Invoice.create(customer: customer.stripe_customer_id).pay
|
|
159
|
+
|
|
160
|
+
true
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
private
|
|
164
|
+
|
|
165
|
+
def subscription
|
|
166
|
+
return nil unless subscribable
|
|
167
|
+
customer.subscriptions.find { |sub| sub.subscribable == subscribable } || customer.subscriptions.build(subscribable: subscribable, customer: customer)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def items
|
|
171
|
+
[{ plan: subscription.stripe_plan_id, quantity: subscription.quantity }]
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# The stripe metadata limit is 500 characters
|
|
175
|
+
def metadata
|
|
176
|
+
{
|
|
177
|
+
:user_id => user.id.to_s,
|
|
178
|
+
:user => user.to_s.truncate(500),
|
|
179
|
+
(subscription.subscribable_type.downcase + '_id').to_sym => subscription.subscribable.id.to_s,
|
|
180
|
+
subscription.subscribable_type.downcase.to_sym => subscription.subscribable.to_s
|
|
181
|
+
}
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
end
|
|
185
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
module Effective
|
|
2
|
+
class Subscription < ActiveRecord::Base
|
|
3
|
+
self.table_name = EffectiveOrders.subscriptions_table_name.to_s
|
|
4
|
+
|
|
5
|
+
attr_accessor :stripe_subscription
|
|
6
|
+
|
|
7
|
+
belongs_to :customer, class_name: 'Effective::Customer', counter_cache: true
|
|
8
|
+
belongs_to :subscribable, polymorphic: true
|
|
9
|
+
|
|
10
|
+
# Attributes
|
|
11
|
+
# stripe_plan_id :string
|
|
12
|
+
# stripe_subscription_id :string
|
|
13
|
+
# name :string
|
|
14
|
+
# description :string
|
|
15
|
+
# interval :string
|
|
16
|
+
# quantity :integer
|
|
17
|
+
#
|
|
18
|
+
# status :string
|
|
19
|
+
#
|
|
20
|
+
# timestamps
|
|
21
|
+
|
|
22
|
+
before_validation(if: -> { plan && (stripe_plan_id_changed? || new_record?) }) do
|
|
23
|
+
self.name = plan[:name]
|
|
24
|
+
self.description = plan[:description]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
after_save do
|
|
28
|
+
subscribable.subscription_name = name if subscribable.respond_to?(:subscription_name=)
|
|
29
|
+
subscribable.subscription_description = description if subscribable.respond_to?(:subscription_description=)
|
|
30
|
+
subscribable.subscription_interval = interval if subscribable.respond_to?(:subscription_interval=)
|
|
31
|
+
subscribable.subscription_quantity = quantity if subscribable.respond_to?(:subscription_quantity=)
|
|
32
|
+
subscribable.subscription_status = status if subscribable.respond_to?(:subscription_status=)
|
|
33
|
+
subscribable.save!(validate: false)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
after_destroy do
|
|
37
|
+
subscribable.subscription_name = nil if subscribable.respond_to?(:subscription_name=)
|
|
38
|
+
subscribable.subscription_description = nil if subscribable.respond_to?(:subscription_description=)
|
|
39
|
+
subscribable.subscription_interval = nil if subscribable.respond_to?(:subscription_interval=)
|
|
40
|
+
subscribable.subscription_quantity = nil if subscribable.respond_to?(:subscription_quantity=)
|
|
41
|
+
subscribable.subscription_status = nil if subscribable.respond_to?(:subscription_status=)
|
|
42
|
+
subscribable.save!(validate: false)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
validates :customer, presence: true
|
|
46
|
+
validates :subscribable, presence: true
|
|
47
|
+
|
|
48
|
+
validates :stripe_plan_id, presence: true
|
|
49
|
+
validates :stripe_plan_id, inclusion: { in: EffectiveOrders.stripe_plans.map { |plan| plan[:id] } }
|
|
50
|
+
|
|
51
|
+
validates :stripe_subscription_id, presence: true
|
|
52
|
+
|
|
53
|
+
validates :name, presence: true
|
|
54
|
+
validates :interval, presence: true
|
|
55
|
+
validates :quantity, presence: true, numericality: { greater_than: 0 }
|
|
56
|
+
|
|
57
|
+
validates :status, inclusion: { in: EffectiveOrders::STATUSES.keys }
|
|
58
|
+
|
|
59
|
+
before_validation do
|
|
60
|
+
self.name ||= EffectiveOrders.stripe_plans.find { |plan| plan[:id] == stripe_plan_id }&[:name] || 'Subscribed Plan'
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def to_s
|
|
64
|
+
name || 'New Subscription'
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def plan
|
|
68
|
+
EffectiveOrders.stripe_plans.find { |plan| plan[:id] == stripe_plan_id }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def stripe_subscription
|
|
72
|
+
@stripe_subscription ||= if stripe_subscription_id.present?
|
|
73
|
+
Rails.logger.info "[STRIPE] get subscription: #{stripe_subscription_id}"
|
|
74
|
+
::Stripe::Subscription.retrieve(stripe_subscription_id)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def <=>(other)
|
|
79
|
+
name.to_s <=> other&.name.to_s
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def active?
|
|
83
|
+
status == 'active'
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def past_due?
|
|
87
|
+
status == 'past_due'
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def canceled?
|
|
91
|
+
status == 'canceled'
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module Effective
|
|
2
|
+
class TaxRateCalculator
|
|
3
|
+
attr_reader :order, :country_code, :state_code
|
|
4
|
+
|
|
5
|
+
RATES = {
|
|
6
|
+
'CA' => { # Canada
|
|
7
|
+
'AB' => 5.00, # Alberta
|
|
8
|
+
'BC' => 5.00, # British Columbia
|
|
9
|
+
'MB' => 5.00, # Manitoba
|
|
10
|
+
'NB' => 15.0, # New Brunswick
|
|
11
|
+
'NL' => 15.0, # Newfoundland and Labrador
|
|
12
|
+
'NT' => 5.00, # Northwest Territories
|
|
13
|
+
'NS' => 15.0, # Nova Scotia
|
|
14
|
+
'ON' => 13.0, # Ontario
|
|
15
|
+
'PE' => 15.0, # Prince Edward Island
|
|
16
|
+
'QC' => 5.00, # Quebec
|
|
17
|
+
'SK' => 5.00, # Saskatchewan
|
|
18
|
+
'YT' => 5.00, # Yukon Territory
|
|
19
|
+
'NU' => 5.00 # Nunavut
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
def initialize(order: nil, country_code: nil, state_code: nil)
|
|
24
|
+
@order = order
|
|
25
|
+
@country_code = country_code
|
|
26
|
+
@state_code = state_code
|
|
27
|
+
|
|
28
|
+
raise 'expected an order, or a country and state code' unless (order || country_code)
|
|
29
|
+
raise 'expected an order OR a country and state code. Not both.' if (order && country_code)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def tax_rate
|
|
33
|
+
country = country_code || (order.billing_address.country_code if order.billing_address.present?)
|
|
34
|
+
state = state_code || (order.billing_address.state_code if order.billing_address.present?)
|
|
35
|
+
|
|
36
|
+
rate = RATES[country]
|
|
37
|
+
return rate if rate.kind_of?(Numeric)
|
|
38
|
+
return unknown_tax_rate() if rate.nil?
|
|
39
|
+
|
|
40
|
+
rate[state].presence || unknown_tax_rate()
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def unknown_tax_rate
|
|
44
|
+
(order && order.skip_buyer_validations?) ? nil : 0
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
%h1.effective-admin-heading= @page_title
|
|
2
|
+
|
|
3
|
+
%p.text-right.effective-admin-actions
|
|
4
|
+
= link_to 'Stripe Customer', "https://dashboard.stripe.com/#{'test/' if Rails.env.development?}customers/#{@customer.stripe_customer_id}", class: 'btn btn-primary'
|
|
5
|
+
|
|
6
|
+
= render 'effective/customers/customer', customer: @customer
|