effective_orders 5.0.1 → 5.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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +67 -1
  3. data/app/assets/config/effective_orders_manifest.js +3 -0
  4. data/app/assets/images/effective_orders/logo.png +0 -0
  5. data/app/assets/javascripts/effective_orders.js +1 -1
  6. data/app/assets/javascripts/effective_orders/providers/moneris_checkout.js.coffee +71 -0
  7. data/app/assets/stylesheets/effective_orders/_order.scss +4 -0
  8. data/app/controllers/effective/concerns/purchase.rb +14 -21
  9. data/app/controllers/effective/orders_controller.rb +2 -2
  10. data/app/controllers/effective/providers/free.rb +0 -1
  11. data/app/controllers/effective/providers/mark_as_paid.rb +1 -2
  12. data/app/controllers/effective/providers/moneris_checkout.rb +59 -0
  13. data/app/controllers/effective/providers/pretend.rb +1 -2
  14. data/app/controllers/effective/providers/refund.rb +1 -2
  15. data/app/controllers/effective/providers/stripe.rb +1 -2
  16. data/app/datatables/admin/effective_orders_datatable.rb +5 -8
  17. data/app/datatables/effective_orders_datatable.rb +3 -7
  18. data/app/helpers/effective_moneris_checkout_helper.rb +65 -0
  19. data/app/helpers/effective_orders_helper.rb +3 -26
  20. data/app/mailers/effective/orders_mailer.rb +6 -4
  21. data/app/models/concerns/acts_as_purchasable.rb +2 -0
  22. data/app/models/concerns/acts_as_subscribable.rb +1 -0
  23. data/app/models/concerns/acts_as_subscribable_buyer.rb +2 -1
  24. data/app/models/effective/order.rb +56 -35
  25. data/app/models/effective/order_item.rb +10 -0
  26. data/app/models/effective/product.rb +2 -0
  27. data/app/views/effective/orders/_checkout_step2.html.haml +3 -0
  28. data/app/views/effective/orders/_order.html.haml +1 -1
  29. data/app/views/effective/orders/_order_actions.html.haml +1 -1
  30. data/app/views/effective/orders/_order_footer.html.haml +3 -1
  31. data/app/views/effective/orders/_order_header.html.haml +4 -20
  32. data/app/views/effective/orders/_order_payment.html.haml +7 -18
  33. data/app/views/effective/orders/declined.html.haml +2 -4
  34. data/app/views/effective/orders/deferred.html.haml +2 -4
  35. data/app/views/effective/orders/moneris_checkout/_element.html.haml +4 -0
  36. data/app/views/effective/orders/moneris_checkout/_form.html.haml +23 -0
  37. data/app/views/effective/orders/purchased.html.haml +2 -4
  38. data/app/views/effective/orders/show.html.haml +5 -2
  39. data/config/effective_orders.rb +19 -0
  40. data/config/routes.rb +1 -0
  41. data/lib/effective_orders.rb +23 -2
  42. data/lib/effective_orders/engine.rb +2 -2
  43. data/lib/effective_orders/version.rb +1 -1
  44. data/lib/generators/templates/effective_orders_mailer_preview.rb +6 -0
  45. metadata +69 -6
@@ -0,0 +1,65 @@
1
+ module EffectiveMonerisCheckoutHelper
2
+
3
+ def moneris_checkout_preload_request(order)
4
+ # Make the Preload Request
5
+ params = {
6
+ # Required
7
+ environment: EffectiveOrders.moneris_checkout.fetch(:environment),
8
+
9
+ api_token: EffectiveOrders.moneris_checkout.fetch(:api_token),
10
+ store_id: EffectiveOrders.moneris_checkout.fetch(:store_id),
11
+ checkout_id: EffectiveOrders.moneris_checkout.fetch(:checkout_id),
12
+
13
+ action: :preload,
14
+ txn_total: price_to_currency(order.total).gsub(',', '').gsub('$', ''),
15
+
16
+ # Optional
17
+ order_no: order.transaction_id, # Has to be unique. This is order number, billing name and Time.now
18
+ cust_id: order.user_id,
19
+ language: 'en',
20
+
21
+ contact_details: {
22
+ first_name: order.billing_first_name,
23
+ last_name: order.billing_last_name,
24
+ email: order.email,
25
+ }
26
+ }
27
+
28
+ if (address = order.billing_address).present?
29
+ params.merge!(
30
+ billing_details: {
31
+ address_1: address.address1,
32
+ address_2: address.address2,
33
+ city: address.city,
34
+ province: address.state_code,
35
+ country: address.country_code,
36
+ postal_code: address.postal_code
37
+ }
38
+ )
39
+ end
40
+
41
+ if (address = order.shipping_address).present?
42
+ params.merge!(
43
+ shipping_details: {
44
+ address_1: address.address1,
45
+ address_2: address.address2,
46
+ city: address.city,
47
+ province: address.state_code,
48
+ country: address.country_code,
49
+ postal_code: address.postal_code
50
+ }
51
+ )
52
+ end
53
+
54
+ response = Effective::Http.post(EffectiveOrders.moneris_request_url, params: params)
55
+ preload = response['response'] if response
56
+
57
+ raise("moneris preload error #{response}") unless preload && preload['success'].to_s == 'true'
58
+
59
+ payload = {
60
+ environment: EffectiveOrders.moneris_checkout.fetch(:environment),
61
+ ticket: preload['ticket']
62
+ }
63
+ end
64
+
65
+ end
@@ -42,6 +42,8 @@ module EffectiveOrdersHelper
42
42
  'Admin: Mark as Paid'
43
43
  when :moneris
44
44
  'Checkout with Credit Card'
45
+ when :moneris_checkout
46
+ 'Pay Now' # Doesn't get displayed anyway
45
47
  when :paypal
46
48
  'Checkout with PayPal'
47
49
  when :phone
@@ -107,35 +109,10 @@ module EffectiveOrdersHelper
107
109
  end
108
110
  end
109
111
 
110
- def render_orders(obj, opts = {})
111
- orders = Array(obj.kind_of?(User) ? Effective::Order.purchased_by(obj) : obj)
112
-
113
- if orders.any? { |order| order.kind_of?(Effective::Order) == false }
114
- raise 'expected a User or Effective::Order'
115
- end
116
-
112
+ def render_orders(orders, opts = {})
117
113
  render(partial: 'effective/orders/orders_table', locals: { orders: orders }.merge(opts))
118
114
  end
119
115
 
120
- def payment_card_label(card)
121
- card = card.to_s.downcase.gsub(' ', '').strip
122
-
123
- case card
124
- when ''
125
- 'None'
126
- when 'v', 'visa'
127
- 'Visa'
128
- when 'm', 'mc', 'master', 'mastercard'
129
- 'MasterCard'
130
- when 'a', 'ax', 'american', 'americanexpress'
131
- 'American Express'
132
- when 'd', 'discover'
133
- 'Discover'
134
- else
135
- card.to_s
136
- end
137
- end
138
-
139
116
  def checkout_icon_to(path, options = {})
140
117
  icon_to('shopping-cart', path, { title: 'Checkout' }.merge(options))
141
118
  end
@@ -1,6 +1,8 @@
1
1
  module Effective
2
2
  class OrdersMailer < ActionMailer::Base
3
- default from: -> { EffectiveOrders.mailer[:default_from] }
3
+ default from: -> { EffectiveOrders.mailer[:default_from].presence }
4
+ default cc: -> { EffectiveOrders.mailer[:default_cc].presence }
5
+ default bcc: -> { EffectiveOrders.mailer[:default_bcc].presence }
4
6
  layout -> { EffectiveOrders.mailer[:layout].presence || 'effective_orders_mailer_layout' }
5
7
 
6
8
  helper EffectiveOrdersHelper
@@ -27,7 +29,7 @@ module Effective
27
29
 
28
30
  @subject = subject_for(@order, :order_receipt_to_buyer, "Order Receipt: ##{@order.to_param}")
29
31
 
30
- mail(to: @order.email, cc: @order.cc, subject: @subject)
32
+ mail({to: @order.email, cc: @order.cc, subject: @subject}.compact)
31
33
  end
32
34
  end
33
35
 
@@ -43,7 +45,7 @@ module Effective
43
45
 
44
46
  @subject = subject_for(@order, :payment_request_to_buyer, "Request for Payment: Invoice ##{@order.to_param}")
45
47
 
46
- mail(to: @order.email, cc: @order.cc, subject: @subject)
48
+ mail({to: @order.email, cc: @order.cc, subject: @subject}.compact)
47
49
  end
48
50
  end
49
51
 
@@ -58,7 +60,7 @@ module Effective
58
60
 
59
61
  @subject = subject_for(@order, :pending_order_invoice_to_buyer, "Pending Order: ##{@order.to_param}")
60
62
 
61
- mail(to: @order.email, cc: @order.cc, subject: @subject)
63
+ mail({to: @order.email, cc: @order.cc, subject: @subject}.compact)
62
64
  end
63
65
  end
64
66
 
@@ -42,6 +42,8 @@ module ActsAsPurchasable
42
42
  end
43
43
 
44
44
  module ClassMethods
45
+ def acts_as_purchasable?; true; end
46
+
45
47
  def before_purchase(&block)
46
48
  send :define_method, :before_purchase do |order, order_item| self.instance_exec(order, order_item, &block) end
47
49
  end
@@ -40,6 +40,7 @@ module ActsAsSubscribable
40
40
  end
41
41
 
42
42
  module ClassMethods
43
+ def acts_as_subscribable?; true; end
43
44
  end
44
45
 
45
46
  def subscripter
@@ -19,6 +19,8 @@ module ActsAsSubscribableBuyer
19
19
  end
20
20
 
21
21
  module ClassMethods
22
+ def acts_as_subscribable_buyer?; true; end
23
+
22
24
  def after_invoice_payment_succeeded(&block)
23
25
  send :define_method, :after_invoice_payment_succeeded do |event| self.instance_exec(event, &block) end
24
26
  end
@@ -46,4 +48,3 @@ module ActsAsSubscribableBuyer
46
48
  end
47
49
 
48
50
  end
49
-
@@ -34,10 +34,6 @@ module Effective
34
34
  belongs_to :user, polymorphic: true, validate: false # This is the buyer/user of the order. We validate it below.
35
35
  has_many :order_items, -> { order(:id) }, inverse_of: :order, dependent: :delete_all
36
36
 
37
- if defined?(EffectiveQbSync)
38
- has_one :qb_order_item
39
- end
40
-
41
37
  accepts_nested_attributes_for :order_items, allow_destroy: false, reject_if: :all_blank
42
38
  accepts_nested_attributes_for :user, allow_destroy: false, update_only: true
43
39
 
@@ -141,7 +137,7 @@ module Effective
141
137
  validates :payment_provider, presence: true, inclusion: { in: EffectiveOrders.deferred_providers }
142
138
  end
143
139
 
144
- scope :deep, -> { includes(:user, order_items: :purchasable) }
140
+ scope :deep, -> { includes(:addresses, :user, order_items: :purchasable) }
145
141
  scope :sorted, -> { order(:id) }
146
142
 
147
143
  scope :purchased, -> { where(state: EffectiveOrders::PURCHASED) }
@@ -254,21 +250,52 @@ module Effective
254
250
  end
255
251
 
256
252
  def to_s
253
+ [label, ' #', to_param].join
254
+ end
255
+
256
+ def label
257
257
  if refund?
258
- "Refund ##{to_param}"
258
+ "Refund"
259
259
  elsif purchased?
260
- "Receipt ##{to_param}"
260
+ "Receipt"
261
261
  elsif pending?
262
- "Pending Order ##{to_param}"
262
+ "Pending Order"
263
263
  else
264
- "Order ##{to_param}"
264
+ "Order"
265
265
  end
266
266
  end
267
267
 
268
- # first or build
269
- def qb_item_name
270
- raise('expected EffectiveQbSync gem') unless defined?(EffectiveQbSync)
271
- (qb_order_item || build_qb_order_item(name: purchasable.qb_item_name)).name
268
+ # Visa - 1234
269
+ def payment_method
270
+ return nil unless purchased?
271
+
272
+ # Normalize payment card
273
+ card = case payment_card.to_s.downcase.gsub(' ', '').strip
274
+ when '' then nil
275
+ when 'v', 'visa' then 'Visa'
276
+ when 'm', 'mc', 'master', 'mastercard' then 'MasterCard'
277
+ when 'a', 'ax', 'american', 'americanexpress' then 'American Express'
278
+ when 'd', 'discover' then 'Discover'
279
+ else payment_card.to_s
280
+ end unless payment_provider == 'free'
281
+
282
+ # stripe, moneris, moneris_checkout
283
+ last4 = (payment[:active_card] || payment['f4l4'] || payment['first6last4']).to_s.last(4)
284
+
285
+ [card, '-', last4].compact.join(' ')
286
+ end
287
+
288
+ # For moneris and moneris_checkout. Just a unique value.
289
+ def transaction_id
290
+ [to_param, billing_name.to_s.parameterize.presence, Time.zone.now.to_i].compact.join('-')
291
+ end
292
+
293
+ def billing_first_name
294
+ billing_name.to_s.split(' ').first
295
+ end
296
+
297
+ def billing_last_name
298
+ Array(billing_name.to_s.split(' ')[1..-1]).join(' ')
272
299
  end
273
300
 
274
301
  def pending?
@@ -372,38 +399,31 @@ module Effective
372
399
 
373
400
  # Effective::Order.new(items: Product.first, user: User.first).purchase!(email: false)
374
401
  def purchase!(payment: 'none', provider: 'none', card: 'none', email: true, skip_buyer_validations: false)
375
- return false if purchased?
376
- error = nil
377
-
378
- assign_attributes(
379
- state: EffectiveOrders::PURCHASED,
380
- payment: payment_to_h(payment),
381
- payment_provider: provider,
382
- payment_card: (card.presence || 'none'),
383
- skip_buyer_validations: skip_buyer_validations
384
- )
402
+ # Assign attributes
403
+ self.state = EffectiveOrders::PURCHASED
404
+ self.skip_buyer_validations = skip_buyer_validations
385
405
 
406
+ self.payment_provider ||= provider
407
+ self.payment_card ||= (card.presence || 'none')
386
408
  self.purchased_at ||= Time.zone.now
409
+ self.payment = payment_to_h(payment) if self.payment.blank?
387
410
 
388
- Effective::Order.transaction do
389
- begin
411
+ begin
412
+ Effective::Order.transaction do
390
413
  run_purchasable_callbacks(:before_purchase)
391
414
  save!
392
415
  update_purchasables_purchased_order!
393
- rescue => e
394
- self.state = state_was
395
- self.purchased_at = nil
396
-
397
- error = e.message
398
- raise ::ActiveRecord::Rollback
399
416
  end
400
- end
417
+ rescue => e
418
+ Effective::Order.transaction do
419
+ save!(validate: false)
420
+ update_purchasables_purchased_order!
421
+ end
401
422
 
402
- raise "Failed to purchase order: #{error || errors.full_messages.to_sentence}" unless error.nil?
423
+ raise(e)
424
+ end
403
425
 
404
426
  run_purchasable_callbacks(:after_purchase)
405
-
406
- send_refund_notification! if email && refund?
407
427
  send_order_receipts! if email
408
428
 
409
429
  true
@@ -466,6 +486,7 @@ module Effective
466
486
  def send_order_receipts!
467
487
  send_order_receipt_to_admin! if EffectiveOrders.mailer[:send_order_receipt_to_admin]
468
488
  send_order_receipt_to_buyer! if EffectiveOrders.mailer[:send_order_receipt_to_buyer]
489
+ send_refund_notification! if refund?
469
490
  end
470
491
 
471
492
  def send_order_receipt_to_admin!
@@ -5,6 +5,10 @@ module Effective
5
5
  belongs_to :order
6
6
  belongs_to :purchasable, polymorphic: true
7
7
 
8
+ if defined?(EffectiveQbSync)
9
+ has_one :qb_order_item
10
+ end
11
+
8
12
  effective_resource do
9
13
  name :string
10
14
  quantity :integer
@@ -61,5 +65,11 @@ module Effective
61
65
  end
62
66
  end
63
67
 
68
+ # first or build
69
+ def qb_item_name
70
+ raise('expected EffectiveQbSync gem') unless defined?(EffectiveQbSync)
71
+ (qb_order_item || build_qb_order_item(name: purchasable.qb_item_name)).name
72
+ end
73
+
64
74
  end
65
75
  end
@@ -4,6 +4,8 @@ module Effective
4
4
 
5
5
  acts_as_purchasable
6
6
 
7
+ # belongs_to :purchased_order_id
8
+
7
9
  effective_resource do
8
10
  name :string
9
11
  qb_item_name :string
@@ -20,6 +20,9 @@
20
20
  - if EffectiveOrders.moneris?
21
21
  = render partial: '/effective/orders/moneris/form', locals: provider_locals
22
22
 
23
+ - if EffectiveOrders.moneris_checkout?
24
+ = render partial: '/effective/orders/moneris_checkout/form', locals: provider_locals
25
+
23
26
  - if EffectiveOrders.paypal?
24
27
  = render partial: '/effective/orders/paypal/form', locals: provider_locals
25
28
 
@@ -3,9 +3,9 @@
3
3
  = render partial: 'effective/orders/order_actions', locals: { order: order }
4
4
 
5
5
  = render partial: 'effective/orders/order_header', locals: { order: order }
6
+ = render partial: 'effective/orders/order_payment', locals: { order: order }
6
7
  = render partial: 'effective/orders/order_shipping', locals: { order: order }
7
8
  = render partial: 'effective/orders/order_deferred', locals: { order: order }
8
9
  = render partial: 'effective/orders/order_notes', locals: { order: order }
9
10
  = render partial: 'effective/orders/order_items', locals: { order: order }
10
- = render partial: 'effective/orders/order_payment', locals: { order: order }
11
11
  = render partial: 'effective/orders/order_footer', locals: { order: order }
@@ -3,7 +3,7 @@
3
3
  = link_to 'Print', '#', class: 'btn btn-primary print-button', data: { role: 'print-button' }, onClick: 'window.print(); false;'
4
4
 
5
5
  - if order.purchased?
6
- = link_to 'Email receipt to buyer', effective_orders.send_buyer_receipt_order_path(order),
6
+ = link_to 'E-mail Receipt', effective_orders.send_buyer_receipt_order_path(order),
7
7
  class: 'btn btn-secondary',
8
8
  data: { confirm: "Send receipt to #{order.emails_send_to}?" }
9
9
 
@@ -1 +1,3 @@
1
- - # intentionally left blank
1
+ .effective-orders-footer
2
+ .mb-4.text-center
3
+ %p If you have any questions, please contact us or call right away.
@@ -1,23 +1,7 @@
1
1
  .effective-order-header
2
- %h2 Acme Industries Inc.
3
- .row
4
- .col-sm-6
5
- - email_address = EffectiveOrders.mailer[:admin_email] || 'info@acmeindustries.com'
6
- %p= mail_to u(email_address), email_address
7
- %p
8
- 1234 Fake Street
9
- %br
10
- Edmonton, AB, T5H 4A5
11
- %br
12
- %small Customize me by overriding '/app/views/effective/orders/_order_header.html.haml'
2
+ .mb-4.text-center
3
+ = image_tag('effective_orders/logo.png')
13
4
 
14
- .col-sm-6
15
- - if order.purchased?
16
- %p Receipt: ##{order.to_param}
17
- - elsif order.persisted?
18
- %p Order: ##{order.to_param}
5
+ %h2 #{order.label} from Example Inc.
19
6
 
20
- - if order.purchased?
21
- %p Purchased at: #{order.purchased_at.strftime("%F %H:%M")}
22
- - else
23
- %p Date: #{Time.zone.now.strftime("%F %H:%M")}
7
+ %p.text-muted= order.to_s
@@ -1,24 +1,13 @@
1
1
  - if order.purchased?
2
- .effective-order-payment-details
2
+ .effective-order-payment
3
3
  %table.table
4
4
  %thead
5
5
  %tr
6
- %th Payment
7
- %th
6
+ %th Amount Paid
7
+ %th Date Paid
8
+ %th Payment Method
8
9
  %tbody
9
10
  %tr
10
- %td Provider
11
- %td= order.payment_provider
12
-
13
- - if order.payment_card.present?
14
- %tr
15
- %td Card
16
- %td
17
- = payment_card_label(order.payment_card)
18
- - if order.payment.kind_of?(Hash)
19
- = order.payment[:active_card] || order.payment['f4l4']
20
-
21
- %tr
22
- %td Purchased at
23
- %td= order.purchased_at&.strftime('%F %H:%M')
24
-
11
+ %td= price_to_currency(order.total)
12
+ %td= order.purchased_at.strftime('%b %d, %Y')
13
+ %td= order.payment_method