effective_orders 4.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|