effective_orders 5.0.1 → 5.1.0

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