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.
Files changed (135) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +1004 -0
  4. data/app/assets/images/effective_orders/stripe.png +0 -0
  5. data/app/assets/javascripts/effective_orders.js +6 -0
  6. data/app/assets/javascripts/effective_orders/customers.js.coffee +32 -0
  7. data/app/assets/javascripts/effective_orders/providers/stripe.js.coffee +77 -0
  8. data/app/assets/javascripts/effective_orders/subscriptions.js.coffee +81 -0
  9. data/app/assets/stylesheets/effective_orders.scss +2 -0
  10. data/app/assets/stylesheets/effective_orders/_cart.scss +4 -0
  11. data/app/assets/stylesheets/effective_orders/_order.scss +58 -0
  12. data/app/controllers/admin/customers_controller.rb +24 -0
  13. data/app/controllers/admin/order_items_controller.rb +16 -0
  14. data/app/controllers/admin/orders_controller.rb +223 -0
  15. data/app/controllers/effective/carts_controller.rb +85 -0
  16. data/app/controllers/effective/concerns/purchase.rb +62 -0
  17. data/app/controllers/effective/customers_controller.rb +20 -0
  18. data/app/controllers/effective/orders_controller.rb +162 -0
  19. data/app/controllers/effective/providers/cheque.rb +22 -0
  20. data/app/controllers/effective/providers/free.rb +33 -0
  21. data/app/controllers/effective/providers/mark_as_paid.rb +33 -0
  22. data/app/controllers/effective/providers/moneris.rb +60 -0
  23. data/app/controllers/effective/providers/paypal.rb +33 -0
  24. data/app/controllers/effective/providers/phone.rb +22 -0
  25. data/app/controllers/effective/providers/pretend.rb +26 -0
  26. data/app/controllers/effective/providers/refund.rb +33 -0
  27. data/app/controllers/effective/providers/stripe.rb +72 -0
  28. data/app/controllers/effective/subscripter_controller.rb +18 -0
  29. data/app/controllers/effective/webhooks_controller.rb +109 -0
  30. data/app/datatables/admin/effective_customers_datatable.rb +22 -0
  31. data/app/datatables/admin/effective_orders_datatable.rb +100 -0
  32. data/app/datatables/effective_orders_datatable.rb +79 -0
  33. data/app/helpers/effective_carts_helper.rb +113 -0
  34. data/app/helpers/effective_orders_helper.rb +143 -0
  35. data/app/helpers/effective_paypal_helper.rb +49 -0
  36. data/app/helpers/effective_stripe_helper.rb +85 -0
  37. data/app/helpers/effective_subscriptions_helper.rb +34 -0
  38. data/app/mailers/effective/orders_mailer.rb +196 -0
  39. data/app/models/concerns/acts_as_purchasable.rb +118 -0
  40. data/app/models/concerns/acts_as_subscribable.rb +90 -0
  41. data/app/models/concerns/acts_as_subscribable_buyer.rb +49 -0
  42. data/app/models/effective/access_denied.rb +17 -0
  43. data/app/models/effective/cart.rb +88 -0
  44. data/app/models/effective/cart_item.rb +40 -0
  45. data/app/models/effective/customer.rb +92 -0
  46. data/app/models/effective/order.rb +541 -0
  47. data/app/models/effective/order_item.rb +63 -0
  48. data/app/models/effective/product.rb +23 -0
  49. data/app/models/effective/sold_out_validator.rb +7 -0
  50. data/app/models/effective/subscripter.rb +185 -0
  51. data/app/models/effective/subscription.rb +95 -0
  52. data/app/models/effective/tax_rate_calculator.rb +48 -0
  53. data/app/views/admin/customers/_actions.html.haml +2 -0
  54. data/app/views/admin/customers/index.html.haml +6 -0
  55. data/app/views/admin/customers/show.html.haml +6 -0
  56. data/app/views/admin/order_items/index.html.haml +3 -0
  57. data/app/views/admin/orders/_datatable_actions.html.haml +18 -0
  58. data/app/views/admin/orders/_form.html.haml +35 -0
  59. data/app/views/admin/orders/_form_note_internal.html.haml +7 -0
  60. data/app/views/admin/orders/_order_actions.html.haml +9 -0
  61. data/app/views/admin/orders/_order_item_fields.html.haml +14 -0
  62. data/app/views/admin/orders/checkout.html.haml +3 -0
  63. data/app/views/admin/orders/edit.html.haml +6 -0
  64. data/app/views/admin/orders/index.html.haml +6 -0
  65. data/app/views/admin/orders/new.html.haml +4 -0
  66. data/app/views/admin/orders/show.html.haml +4 -0
  67. data/app/views/effective/carts/_cart.html.haml +28 -0
  68. data/app/views/effective/carts/_cart_actions.html.haml +3 -0
  69. data/app/views/effective/carts/show.html.haml +17 -0
  70. data/app/views/effective/customers/_customer.html.haml +72 -0
  71. data/app/views/effective/customers/_form.html.haml +21 -0
  72. data/app/views/effective/customers/edit.html.haml +4 -0
  73. data/app/views/effective/customers/update.js.erb +5 -0
  74. data/app/views/effective/orders/_checkout_actions.html.haml +3 -0
  75. data/app/views/effective/orders/_checkout_step1.html.haml +4 -0
  76. data/app/views/effective/orders/_checkout_step2.html.haml +37 -0
  77. data/app/views/effective/orders/_datatable_actions.html.haml +2 -0
  78. data/app/views/effective/orders/_fields.html.haml +31 -0
  79. data/app/views/effective/orders/_fields_note.html.haml +7 -0
  80. data/app/views/effective/orders/_fields_terms.html.haml +8 -0
  81. data/app/views/effective/orders/_order.html.haml +11 -0
  82. data/app/views/effective/orders/_order_actions.html.haml +18 -0
  83. data/app/views/effective/orders/_order_deferred.html.haml +9 -0
  84. data/app/views/effective/orders/_order_footer.html.haml +1 -0
  85. data/app/views/effective/orders/_order_header.html.haml +23 -0
  86. data/app/views/effective/orders/_order_items.html.haml +72 -0
  87. data/app/views/effective/orders/_order_notes.html.haml +17 -0
  88. data/app/views/effective/orders/_order_payment.html.haml +24 -0
  89. data/app/views/effective/orders/_order_shipping.html.haml +30 -0
  90. data/app/views/effective/orders/_orders_table.html.haml +23 -0
  91. data/app/views/effective/orders/cheque/_form.html.haml +4 -0
  92. data/app/views/effective/orders/declined.html.haml +12 -0
  93. data/app/views/effective/orders/deferred.html.haml +13 -0
  94. data/app/views/effective/orders/deferred/_form.html.haml +16 -0
  95. data/app/views/effective/orders/edit.html.haml +3 -0
  96. data/app/views/effective/orders/free/_form.html.haml +5 -0
  97. data/app/views/effective/orders/index.html.haml +3 -0
  98. data/app/views/effective/orders/mark_as_paid/_form.html.haml +23 -0
  99. data/app/views/effective/orders/moneris/_form.html.haml +47 -0
  100. data/app/views/effective/orders/new.html.haml +3 -0
  101. data/app/views/effective/orders/paypal/_form.html.haml +5 -0
  102. data/app/views/effective/orders/phone/_form.html.haml +4 -0
  103. data/app/views/effective/orders/pretend/_form.html.haml +8 -0
  104. data/app/views/effective/orders/purchased.html.haml +11 -0
  105. data/app/views/effective/orders/refund/_form.html.haml +5 -0
  106. data/app/views/effective/orders/show.html.haml +6 -0
  107. data/app/views/effective/orders/stripe/_element.html.haml +8 -0
  108. data/app/views/effective/orders/stripe/_form.html.haml +31 -0
  109. data/app/views/effective/orders_mailer/order_error.html.haml +11 -0
  110. data/app/views/effective/orders_mailer/order_receipt_to_admin.html.haml +2 -0
  111. data/app/views/effective/orders_mailer/order_receipt_to_buyer.html.haml +2 -0
  112. data/app/views/effective/orders_mailer/payment_request_to_buyer.html.haml +13 -0
  113. data/app/views/effective/orders_mailer/pending_order_invoice_to_buyer.html.haml +13 -0
  114. data/app/views/effective/orders_mailer/refund_notification_to_admin.html.haml +15 -0
  115. data/app/views/effective/orders_mailer/subscription_canceled.html.haml +9 -0
  116. data/app/views/effective/orders_mailer/subscription_created.html.haml +13 -0
  117. data/app/views/effective/orders_mailer/subscription_event_to_admin.html.haml +13 -0
  118. data/app/views/effective/orders_mailer/subscription_payment_failed.html.haml +9 -0
  119. data/app/views/effective/orders_mailer/subscription_payment_succeeded.html.haml +9 -0
  120. data/app/views/effective/orders_mailer/subscription_trial_expired.html.haml +5 -0
  121. data/app/views/effective/orders_mailer/subscription_trialing.html.haml +7 -0
  122. data/app/views/effective/orders_mailer/subscription_updated.html.haml +13 -0
  123. data/app/views/effective/subscripter/_form.html.haml +60 -0
  124. data/app/views/effective/subscripter/_plan.html.haml +23 -0
  125. data/app/views/layouts/effective_orders_mailer_layout.html.haml +25 -0
  126. data/config/effective_orders.rb +279 -0
  127. data/config/routes.rb +70 -0
  128. data/db/migrate/01_create_effective_orders.rb.erb +137 -0
  129. data/lib/effective_orders.rb +243 -0
  130. data/lib/effective_orders/engine.rb +60 -0
  131. data/lib/effective_orders/version.rb +3 -0
  132. data/lib/generators/effective_orders/install_generator.rb +63 -0
  133. data/lib/generators/templates/effective_orders_mailer_preview.rb +120 -0
  134. data/lib/tasks/effective_orders_tasks.rake +69 -0
  135. 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,7 @@
1
+ module Effective
2
+ class SoldOutValidator < ActiveModel::Validator
3
+ def validate(record)
4
+ record.errors[:base] << 'sold out' if record.sold_out?
5
+ end
6
+ end
7
+ 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,2 @@
1
+ = dropdown(variation: :dropleft) do
2
+ = dropdown_link_to 'Manage', "https://dashboard.stripe.com/#{'test/' if Rails.env.development?}customers/#{customer.stripe_customer_id}"
@@ -0,0 +1,6 @@
1
+ %h1.effective-admin-heading= @page_title
2
+
3
+ %p.text-right.effective-admin-actions
4
+ = link_to 'Stripe Customers', "https://dashboard.stripe.com/#{'test/' if Rails.env.development?}customers", class: 'btn btn-primary'
5
+
6
+ = render_datatable(@datatable)
@@ -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
@@ -0,0 +1,3 @@
1
+ %h1.effective-admin-heading= @page_title
2
+
3
+ = render_datatable(@datatable)