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,113 @@
1
+ module EffectiveCartsHelper
2
+ # TODO: Consider unique
3
+ def current_cart(for_user = nil)
4
+ @cart ||= (
5
+ user = for_user || (current_user rescue nil) # rescue protects me against Devise not being installed
6
+
7
+ if user.present?
8
+ user_cart = Effective::Cart.where(user: user).first_or_create
9
+
10
+ # Merge session cart into user cart.
11
+ if session[:cart].present?
12
+ session_cart = Effective::Cart.where(user: nil).where(id: session[:cart]).first
13
+
14
+ if session_cart
15
+ session_cart.cart_items.each { |i| user_cart.add(i.purchasable, quantity: i.quantity, unique: i.unique) }
16
+ session_cart.destroy
17
+ end
18
+
19
+ session[:cart] = nil
20
+ end
21
+
22
+ user_cart
23
+ elsif session[:cart].present?
24
+ Effective::Cart.where(user_id: nil).where(id: session[:cart]).first_or_create
25
+ else
26
+ cart = Effective::Cart.create!
27
+ session[:cart] = cart.id
28
+ cart
29
+ end
30
+ )
31
+ end
32
+
33
+ def link_to_current_cart(opts = {})
34
+ options = {
35
+ label: 'My Cart',
36
+ id: 'current_cart',
37
+ rel: :nofollow,
38
+ class: 'btn btn-secondary'
39
+ }.merge(opts)
40
+
41
+ label = options.delete(:label)
42
+ options[:class] = ((options[:class] || '') + ' btn-current-cart')
43
+
44
+ link_to (current_cart.size == 0 ? label : "#{label} (#{current_cart.size})"), effective_orders.cart_path, options
45
+ end
46
+
47
+ def link_to_add_to_cart(purchasable, opts = {})
48
+ raise 'expecting an acts_as_purchasable object' unless purchasable.kind_of?(ActsAsPurchasable)
49
+
50
+ options = { label: 'Add to Cart', class: 'btn btn-primary', rel: :nofollow }.merge(opts)
51
+
52
+ label = options.delete(:label)
53
+ options[:class] = ((options[:class] || '') + ' btn-add-to-cart')
54
+
55
+ link_to(label, effective_orders.add_to_cart_path(purchasable_type: purchasable.class.name, purchasable_id: purchasable.id.to_i), options)
56
+ end
57
+
58
+ def link_to_remove_from_cart(cart_item, opts = {})
59
+ raise 'expecting an Effective::CartItem object' unless cart_item.kind_of?(Effective::CartItem)
60
+
61
+ options = {
62
+ label: 'Remove',
63
+ class: 'btn btn-primary',
64
+ rel: :nofollow,
65
+ data: { confirm: 'Are you sure? This cannot be undone!' },
66
+ method: :delete
67
+ }.merge(opts)
68
+
69
+ label = options.delete(:label)
70
+ options[:class] = ((options[:class] || '') + ' btn-remove-from-cart')
71
+
72
+ link_to(label, effective_orders.remove_from_cart_path(cart_item), options)
73
+ end
74
+
75
+ def link_to_empty_cart(opts = {})
76
+ options = {
77
+ label: 'Empty Cart',
78
+ class: 'btn btn-danger',
79
+ rel: :nofollow,
80
+ data: { confirm: 'This will clear your entire cart. Are you sure?' },
81
+ method: :delete
82
+ }.merge(opts)
83
+
84
+ label = options.delete(:label)
85
+ options[:class] = ((options[:class] || '') + ' btn-empty-cart')
86
+
87
+ link_to(label, effective_orders.cart_path, options)
88
+ end
89
+
90
+ def link_to_checkout(opts = {})
91
+ options = { label: 'Checkout', class: 'btn btn-primary', rel: :nofollow }.merge(opts)
92
+
93
+ order = options.delete(:order)
94
+ label = options.delete(:label)
95
+ options[:class] = ((options[:class] || '') + ' btn-checkout')
96
+
97
+ if order.present?
98
+ link_to(label, effective_orders.edit_order_path(order), options)
99
+ else
100
+ link_to(label, effective_orders.new_order_path, options)
101
+ end
102
+ end
103
+
104
+ def render_cart(cart = nil)
105
+ cart ||= current_cart
106
+ render(partial: 'effective/carts/cart', locals: { cart: cart })
107
+ end
108
+
109
+ def render_purchasables(*purchasables)
110
+ render(partial: 'effective/orders/order_items', locals: { order: Effective::Order.new(purchasables) })
111
+ end
112
+
113
+ end
@@ -0,0 +1,143 @@
1
+ module EffectiveOrdersHelper
2
+ def price_to_currency(price)
3
+ price = price || 0
4
+ raise 'price_to_currency expects an Integer representing the number of cents' unless price.kind_of?(Integer)
5
+ number_to_currency(price / 100.0)
6
+ end
7
+
8
+ def tax_rate_to_percentage(tax_rate, options = {})
9
+ options[:strip_insignificant_zeros] = true if options[:strip_insignificant_zeros].nil?
10
+ number_to_percentage(tax_rate, strip_insignificant_zeros: true)
11
+ end
12
+
13
+ def order_summary(order)
14
+ order_item_list = content_tag(:ul) do
15
+ order.order_items.map do |item|
16
+ content_tag(:li) do
17
+ names = item.name.split('<br>')
18
+ "#{item.quantity}x #{names.first} for #{price_to_currency(item.price)}".tap do |output|
19
+ names[1..-1].each { |line| output << "<br>#{line}" }
20
+ end.html_safe
21
+ end
22
+ end.join.html_safe
23
+ end
24
+ content_tag(:p, "#{price_to_currency(order.total)} total for #{pluralize(order.num_items, 'item')}:") + order_item_list
25
+ end
26
+
27
+ def order_item_summary(order_item)
28
+ if order_item.quantity > 1
29
+ content_tag(:p, "#{price_to_currency(order_item.total)} total for #{pluralize(order_item.quantity, 'item')}")
30
+ else
31
+ content_tag(:p, "#{price_to_currency(order_item.total)} total")
32
+ end
33
+ end
34
+
35
+ def order_checkout_label(processor = nil)
36
+ case processor
37
+ when :cheque
38
+ 'Pay by Cheque'
39
+ when :free
40
+ 'Checkout Free'
41
+ when :mark_as_paid
42
+ 'Admin: Mark as Paid'
43
+ when :moneris
44
+ 'Checkout with Credit Card'
45
+ when :paypal
46
+ 'Checkout with PayPal'
47
+ when :phone
48
+ 'Pay by Phone'
49
+ when :pretend
50
+ 'Purchase Order (skip payment processor)'
51
+ when :refund
52
+ 'Accept Refund'
53
+ when :stripe
54
+ 'Pay Now'
55
+ else
56
+ 'Checkout'
57
+ end
58
+ end
59
+
60
+ # This is called on the My Sales Page and is intended to be overridden in the app if needed
61
+ def acts_as_purchasable_path(purchasable, action = :show)
62
+ polymorphic_path(purchasable)
63
+ end
64
+
65
+ def order_payment_to_html(order)
66
+ content_tag(:pre) do
67
+ raw JSON.pretty_generate(order.payment).html_safe.gsub('\"', '').gsub("[\n\n ]", '[]').gsub("{\n }", '{}')
68
+ end
69
+ end
70
+
71
+ def render_order(order)
72
+ render(partial: 'effective/orders/order', locals: { order: order })
73
+ end
74
+
75
+ def render_checkout(order, namespace: nil, purchased_url: nil, declined_url: nil, deferred_url: nil)
76
+ raise 'unable to checkout an order without a user' unless order && order.user
77
+
78
+ locals = { order: order, purchased_url: purchased_url, declined_url: declined_url, deferred_url: deferred_url, namespace: namespace }
79
+
80
+ if order.purchased?
81
+ render(partial: 'effective/orders/order', locals: locals)
82
+ elsif (order.confirmed? || order.deferred?) && order.errors.blank?
83
+ render(partial: 'effective/orders/checkout_step2', locals: locals)
84
+ else
85
+ render(partial: 'effective/orders/checkout_step1', locals: locals)
86
+ end
87
+ end
88
+
89
+ def render_checkout_step1(order, namespace: nil, purchased_url: nil, declined_url: nil)
90
+ locals = { order: order, purchased_url: purchased_url, declined_url: declined_url, namespace: namespace }
91
+ render(partial: 'effective/orders/checkout_step1', locals: locals)
92
+ end
93
+
94
+ def render_checkout_step2(order, namespace: nil, purchased_url: nil, declined_url: nil)
95
+ locals = { order: order, purchased_url: purchased_url, declined_url: declined_url, namespace: namespace }
96
+ render(partial: 'effective/orders/checkout_step2', locals: locals)
97
+ end
98
+
99
+ def checkout_step1_form_url(order, namespace = nil)
100
+ raise 'expected an order' unless order
101
+ raise 'invalid namespace, expecting nil or :admin' unless [nil, :admin].include?(namespace)
102
+
103
+ if order.new_record?
104
+ namespace == nil ? effective_orders.orders_path : effective_orders.admin_orders_path
105
+ else
106
+ namespace == nil ? effective_orders.order_path(order) : effective_orders.checkout_admin_order_path(order)
107
+ end
108
+ end
109
+
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
+
117
+ render(partial: 'effective/orders/orders_table', locals: { orders: orders }.merge(opts))
118
+ end
119
+
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
+ def checkout_icon_to(path, options = {})
140
+ icon_to('shopping-cart', path, { title: 'Checkout' }.merge(options))
141
+ end
142
+
143
+ end
@@ -0,0 +1,49 @@
1
+ module EffectivePaypalHelper
2
+ class ConfigReader
3
+ def self.cert_or_key(config)
4
+ if File.exist?(EffectiveOrders.paypal[config])
5
+ (File.read(EffectiveOrders.paypal[config]) rescue nil)
6
+ else
7
+ EffectiveOrders.paypal[config]
8
+ end
9
+ end
10
+ end
11
+
12
+ # These're constants so they only get read once, not every order request
13
+ if EffectiveOrders.paypal?
14
+ PAYPAL_CERT_PEM = ConfigReader.cert_or_key(:paypal_cert)
15
+ APP_CERT_PEM = ConfigReader.cert_or_key(:app_cert)
16
+ APP_KEY_PEM = ConfigReader.cert_or_key(:app_key)
17
+ end
18
+
19
+ def paypal_encrypted_payload(order)
20
+ raise 'required paypal paypal_cert is missing' unless PAYPAL_CERT_PEM.present?
21
+ raise 'required paypal app_cert is missing' unless APP_CERT_PEM.present?
22
+ raise 'required paypal app_key is missing' unless APP_KEY_PEM.present?
23
+
24
+ values = {
25
+ business: EffectiveOrders.paypal[:seller_email],
26
+ custom: EffectiveOrders.paypal[:secret],
27
+ cmd: '_cart',
28
+ upload: 1,
29
+ return: effective_orders.order_purchased_url(order),
30
+ notify_url: effective_orders.paypal_postback_url,
31
+ cert_id: EffectiveOrders.paypal[:cert_id],
32
+ currency_code: EffectiveOrders.paypal[:currency],
33
+ invoice: order.id,
34
+ amount: (order.subtotal / 100.0).round(2),
35
+ tax_cart: (order.tax / 100.0).round(2)
36
+ }
37
+
38
+ order.order_items.each_with_index do |item, x|
39
+ values["item_number_#{x+1}"] = x+1
40
+ values["item_name_#{x+1}"] = item.name
41
+ values["quantity_#{x+1}"] = item.quantity
42
+ values["amount_#{x+1}"] = '%.2f' % (item.price / 100.0)
43
+ values["tax_#{x+1}"] = '%.2f' % ((item.tax / 100.0) / item.quantity) # Tax for 1 of these items
44
+ end
45
+
46
+ signed = OpenSSL::PKCS7::sign(OpenSSL::X509::Certificate.new(APP_CERT_PEM), OpenSSL::PKey::RSA.new(APP_KEY_PEM, ''), values.map { |k, v| "#{k}=#{v}" }.join("\n"), [], OpenSSL::PKCS7::BINARY)
47
+ OpenSSL::PKCS7::encrypt([OpenSSL::X509::Certificate.new(PAYPAL_CERT_PEM)], signed.to_der, OpenSSL::Cipher::Cipher::new("DES3"), OpenSSL::PKCS7::BINARY).to_s.gsub("\n", "")
48
+ end
49
+ end
@@ -0,0 +1,85 @@
1
+ module EffectiveStripeHelper
2
+
3
+ def stripe_plan_description(obj)
4
+ plan = (
5
+ case obj
6
+ when Hash ; obj
7
+ when ::Stripe::Plan ; EffectiveOrders.stripe_plans.find { |plan| plan[:id] == obj.id }
8
+ else ; raise 'unexpected object'
9
+ end
10
+ )
11
+
12
+ raise("unknown stripe plan: #{obj}") unless plan.kind_of?(Hash) && plan[:id].present?
13
+
14
+ plan[:description]
15
+ end
16
+
17
+ def stripe_invoice_line_description(line, simple: false)
18
+ [
19
+ "#{line.quantity}x",
20
+ line.plan.name,
21
+ price_to_currency(line.amount),
22
+ ("#{Time.zone.at(line.period.start).strftime('%F')}" unless simple),
23
+ ('to' unless simple),
24
+ ("#{Time.zone.at(line.period.end).strftime('%F')}" unless simple),
25
+ line.description.presence
26
+ ].compact.join(' ')
27
+ end
28
+
29
+ def stripe_coupon_description(coupon)
30
+ amount = coupon.amount_off.present? ? price_to_currency(coupon.amount_off) : "#{coupon.percent_off}%"
31
+
32
+ if coupon.duration_in_months.present?
33
+ "#{coupon.id} - #{amount} off for #{coupon.duration_in_months} months"
34
+ else
35
+ "#{coupon.id} - #{amount} off #{coupon.duration}"
36
+ end
37
+ end
38
+
39
+ def stripe_site_image_url
40
+ return nil unless EffectiveOrders.stripe? && (url = EffectiveOrders.stripe[:site_image].to_s).present?
41
+ url.start_with?('http') ? url : asset_url(url)
42
+ end
43
+
44
+ def stripe_order_description(order)
45
+ "#{order.num_items} items (#{price_to_currency(order.total)})"
46
+ end
47
+
48
+ def stripe_payment_intent(order)
49
+ customer = Effective::Customer.for_user(order.user)
50
+ customer.create_stripe_customer! # Only creates if customer not already present
51
+
52
+ payment = {
53
+ amount: order.total,
54
+ currency: EffectiveOrders.stripe[:currency],
55
+ customer: customer.stripe_customer_id,
56
+ payment_method: customer.payment_method_id.presence,
57
+ description: stripe_order_description(order),
58
+ metadata: { order_id: order.id },
59
+ }
60
+
61
+ token_required = customer.token_required?
62
+
63
+ intent = begin
64
+ Rails.logger.info "[STRIPE] create payment intent : #{payment}"
65
+ Stripe::PaymentIntent.create(payment)
66
+ rescue Stripe::CardError => e
67
+ token_required = true
68
+ Rails.logger.info "[STRIPE] (error) get payment intent : #{e.error.payment_intent.id}"
69
+ Stripe::PaymentIntent.retrieve(e.error.payment_intent.id)
70
+ end
71
+
72
+ payload = {
73
+ key: EffectiveOrders.stripe[:publishable_key],
74
+ client_secret: intent.client_secret,
75
+ payment_method: intent.payment_method,
76
+
77
+ active_card: customer.active_card,
78
+ email: customer.email,
79
+ token_required: token_required
80
+ }
81
+
82
+ payload
83
+ end
84
+
85
+ end
@@ -0,0 +1,34 @@
1
+ module EffectiveSubscriptionsHelper
2
+
3
+ def subscripter_stripe_data(subscripter)
4
+ {
5
+ email: current_user.email,
6
+ image: stripe_site_image_url,
7
+ key: EffectiveOrders.stripe[:publishable_key],
8
+ name: EffectiveOrders.stripe[:site_title],
9
+ }
10
+ end
11
+
12
+ def subscripter_stripe_plans(subscripter)
13
+ EffectiveOrders.stripe_plans
14
+ end
15
+
16
+ def subscribable_form_with(subscribable)
17
+ raise 'form object must be an acts_as_subscribable object' unless subscribable.respond_to?(:subscripter)
18
+
19
+ subscripter = subscribable.subscripter
20
+ raise 'subscribable.subscribable_buyer must match current_user' unless subscripter.user == current_user
21
+
22
+ render('effective/subscripter/form', subscripter: subscripter)
23
+ end
24
+
25
+ def customer_form_with(customer)
26
+ raise 'form object must be an Effective::Customer object' unless customer.kind_of?(Effective::Customer)
27
+ raise 'expected customer user to match current user' if customer.user != current_user
28
+
29
+ subscripter = Effective::Subscripter.new(customer: customer, user: customer.user)
30
+
31
+ render('effective/customers/form', subscripter: subscripter)
32
+ end
33
+
34
+ end
@@ -0,0 +1,196 @@
1
+ module Effective
2
+ class OrdersMailer < ActionMailer::Base
3
+ default from: EffectiveOrders.mailer[:default_from]
4
+
5
+ helper EffectiveOrdersHelper
6
+ layout EffectiveOrders.mailer[:layout].presence || 'effective_orders_mailer_layout'
7
+
8
+ def order_receipt_to_admin(order_param)
9
+ return true unless EffectiveOrders.mailer[:send_order_receipt_to_admin]
10
+
11
+ @order = (order_param.kind_of?(Effective::Order) ? order_param : Effective::Order.find(order_param))
12
+ @user = @order.user
13
+
14
+ @subject = subject_for(@order, :order_receipt_to_admin, "Order Receipt: ##{@order.to_param}")
15
+
16
+ mail(to: EffectiveOrders.mailer[:admin_email], from: EffectiveOrders.mailer[:default_from], subject: @subject)
17
+ end
18
+
19
+ def order_receipt_to_buyer(order_param) # Buyer
20
+ return true unless EffectiveOrders.mailer[:send_order_receipt_to_buyer]
21
+
22
+ @order = (order_param.kind_of?(Effective::Order) ? order_param : Effective::Order.find(order_param))
23
+ @user = @order.user
24
+
25
+ @subject = subject_for(@order, :order_receipt_to_buyer, "Order Receipt: ##{@order.to_param}")
26
+
27
+ mail(to: @order.email, cc: @order.cc, subject: @subject)
28
+ end
29
+
30
+ # This is sent when an admin creates a new order or /admin/orders/new
31
+ # Or when Pay by Cheque or Pay by Phone (deferred payments)
32
+ # Or uses the order action Send Payment Request
33
+ def payment_request_to_buyer(order_param)
34
+ return true unless EffectiveOrders.mailer[:send_payment_request_to_buyer]
35
+
36
+ @order = (order_param.kind_of?(Effective::Order) ? order_param : Effective::Order.find(order_param))
37
+ @user = @order.user
38
+
39
+ @subject = subject_for(@order, :payment_request_to_buyer, "Request for Payment: Invoice ##{@order.to_param}")
40
+
41
+ mail(to: @order.email, cc: @order.cc, subject: @subject)
42
+ end
43
+
44
+ # This is sent when someone chooses to Pay by Cheque
45
+ def pending_order_invoice_to_buyer(order_param)
46
+ return true unless EffectiveOrders.mailer[:send_pending_order_invoice_to_buyer]
47
+
48
+ @order = (order_param.kind_of?(Effective::Order) ? order_param : Effective::Order.find(order_param))
49
+ @user = @order.user
50
+
51
+ @subject = subject_for(@order, :pending_order_invoice_to_buyer, "Pending Order: ##{@order.to_param}")
52
+
53
+ mail(to: @order.email, cc: @order.cc, subject: @subject)
54
+ end
55
+
56
+ # This is sent to admin when someone Accepts Refund
57
+ def refund_notification_to_admin(order_param)
58
+ @order = (order_param.kind_of?(Effective::Order) ? order_param : Effective::Order.find(order_param))
59
+ @user = @order.user
60
+
61
+ @subject = subject_for(@order, :refund_notification_to_admin, "New Refund: ##{@order.to_param}")
62
+
63
+ mail(to: EffectiveOrders.mailer[:admin_email], subject: @subject)
64
+ end
65
+
66
+ # Sent by the invoice.payment_succeeded webhook event
67
+ def subscription_payment_succeeded(customer_param)
68
+ return true unless EffectiveOrders.mailer[:send_subscription_payment_succeeded]
69
+
70
+ @customer = (customer_param.kind_of?(Effective::Customer) ? customer_param : Effective::Customer.find(customer_param))
71
+ @subscriptions = @customer.subscriptions
72
+ @user = @customer.user
73
+
74
+ @subject = subject_for(@customer, :subscription_payment_succeeded, 'Thank you for your payment')
75
+
76
+ mail(to: @customer.user.email, subject: @subject)
77
+ end
78
+
79
+ # Sent by the invoice.payment_failed webhook event
80
+ def subscription_payment_failed(customer_param)
81
+ return true unless EffectiveOrders.mailer[:send_subscription_payment_failed]
82
+
83
+ @customer = (customer_param.kind_of?(Effective::Customer) ? customer_param : Effective::Customer.find(customer_param))
84
+ @subscriptions = @customer.subscriptions
85
+ @user = @customer.user
86
+
87
+ @subject = subject_for(@customer, :subscription_payment_failed, 'Payment failed - please update your card details')
88
+
89
+ mail(to: @customer.user.email, subject: @subject)
90
+ end
91
+
92
+ # Sent by the customer.subscription.created webhook event
93
+ def subscription_created(customer_param)
94
+ return true unless EffectiveOrders.mailer[:send_subscription_created]
95
+
96
+ @customer = (customer_param.kind_of?(Effective::Customer) ? customer_param : Effective::Customer.find(customer_param))
97
+ @subscriptions = @customer.subscriptions
98
+ @user = @customer.user
99
+
100
+ @subject = subject_for(@customer, :subscription_created, 'New Subscription')
101
+
102
+ mail(to: @customer.user.email, subject: @subject)
103
+ end
104
+
105
+ # Sent by the customer.subscription.updated webhook event
106
+ def subscription_updated(customer_param)
107
+ return true unless EffectiveOrders.mailer[:send_subscription_updated]
108
+
109
+ @customer = (customer_param.kind_of?(Effective::Customer) ? customer_param : Effective::Customer.find(customer_param))
110
+ @subscriptions = @customer.subscriptions
111
+ @user = @customer.user
112
+
113
+ @subject = subject_for(@customer, :subscription_updated, 'Subscription Changed')
114
+
115
+ mail(to: @customer.user.email, subject: @subject)
116
+ end
117
+
118
+ # Sent by the invoice.payment_failed webhook event
119
+ def subscription_canceled(customer_param)
120
+ return true unless EffectiveOrders.mailer[:send_subscription_canceled]
121
+
122
+ @customer = (customer_param.kind_of?(Effective::Customer) ? customer_param : Effective::Customer.find(customer_param))
123
+ @subscriptions = @customer.subscriptions
124
+ @user = @customer.user
125
+
126
+ @subject = subject_for(@customer, :subscription_canceled, 'Subscription canceled')
127
+
128
+ mail(to: @customer.user.email, subject: @subject)
129
+ end
130
+
131
+ # Sent by the effective_orders:notify_trial_users rake task.
132
+ def subscription_trialing(subscribable)
133
+ return true unless EffectiveOrders.mailer[:send_subscription_trialing]
134
+
135
+ @subscribable = subscribable
136
+ @user = @subscribable.subscribable_buyer
137
+
138
+ @subject = subject_for(@customer, :subscription_trialing, 'Trial is active')
139
+
140
+ mail(to: @subscribable.subscribable_buyer.email, subject: @subject)
141
+ end
142
+
143
+ # Sent by the effective_orders:notify_trial_users rake task.
144
+ def subscription_trial_expired(subscribable)
145
+ return true unless EffectiveOrders.mailer[:send_subscription_trial_expired]
146
+
147
+ @subscribable = subscribable
148
+ @user = @subscribable.subscribable_buyer
149
+
150
+ @subject = subject_for(@customer, :subscription_trial_expired, 'Trial expired')
151
+
152
+ mail(to: @subscribable.subscribable_buyer.email, subject: @subject)
153
+ end
154
+
155
+ def subscription_event_to_admin(event, customer_param)
156
+ return true unless EffectiveOrders.mailer[:send_subscription_event_to_admin]
157
+
158
+ @customer = (customer_param.kind_of?(Effective::Customer) ? customer_param : Effective::Customer.find(customer_param))
159
+ @subscriptions = @customer.subscriptions
160
+ @user = @customer.user
161
+ @event = event.to_s
162
+
163
+ @subject = subject_for(@customer, :subscription_event_to_admin, "Subscription event - @event - @customer").gsub('@event', @event.to_s).gsub('@customer', @customer.to_s)
164
+
165
+ mail(to: EffectiveOrders.mailer[:admin_email], subject: @subject)
166
+ end
167
+
168
+ def order_error(order: nil, error: nil, to: nil, from: nil, subject: nil, template: 'order_error')
169
+ @order = (order.kind_of?(Effective::Order) ? order : Effective::Order.find(order))
170
+ @error = error.to_s
171
+
172
+ @subject = subject_for(@order, :error, "An error occurred with order: ##{@order.try(:to_param)}")
173
+
174
+ mail(
175
+ to: (to || EffectiveOrders.mailer[:admin_email]),
176
+ from: (from || EffectiveOrders.mailer[:default_from]),
177
+ subject: (subject || @subject)
178
+ ) do |format|
179
+ format.html { render(template) }
180
+ end
181
+ end
182
+
183
+ private
184
+
185
+ def subject_for(order, action, fallback)
186
+ subject = EffectiveOrders.mailer["subject_for_#{action}".to_sym]
187
+ prefix = EffectiveOrders.mailer[:subject_prefix].to_s
188
+
189
+ subject = self.instance_exec(order, &subject) if subject.respond_to?(:call)
190
+ subject = subject.presence || fallback
191
+
192
+ prefix.present? ? (prefix.chomp(' ') + ' ' + subject) : subject
193
+ end
194
+
195
+ end
196
+ end