effective_orders 4.5.0

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