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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +1004 -0
- data/app/assets/images/effective_orders/stripe.png +0 -0
- data/app/assets/javascripts/effective_orders.js +6 -0
- data/app/assets/javascripts/effective_orders/customers.js.coffee +32 -0
- data/app/assets/javascripts/effective_orders/providers/stripe.js.coffee +77 -0
- data/app/assets/javascripts/effective_orders/subscriptions.js.coffee +81 -0
- data/app/assets/stylesheets/effective_orders.scss +2 -0
- data/app/assets/stylesheets/effective_orders/_cart.scss +4 -0
- data/app/assets/stylesheets/effective_orders/_order.scss +58 -0
- data/app/controllers/admin/customers_controller.rb +24 -0
- data/app/controllers/admin/order_items_controller.rb +16 -0
- data/app/controllers/admin/orders_controller.rb +223 -0
- data/app/controllers/effective/carts_controller.rb +85 -0
- data/app/controllers/effective/concerns/purchase.rb +62 -0
- data/app/controllers/effective/customers_controller.rb +20 -0
- data/app/controllers/effective/orders_controller.rb +162 -0
- data/app/controllers/effective/providers/cheque.rb +22 -0
- data/app/controllers/effective/providers/free.rb +33 -0
- data/app/controllers/effective/providers/mark_as_paid.rb +33 -0
- data/app/controllers/effective/providers/moneris.rb +60 -0
- data/app/controllers/effective/providers/paypal.rb +33 -0
- data/app/controllers/effective/providers/phone.rb +22 -0
- data/app/controllers/effective/providers/pretend.rb +26 -0
- data/app/controllers/effective/providers/refund.rb +33 -0
- data/app/controllers/effective/providers/stripe.rb +72 -0
- data/app/controllers/effective/subscripter_controller.rb +18 -0
- data/app/controllers/effective/webhooks_controller.rb +109 -0
- data/app/datatables/admin/effective_customers_datatable.rb +22 -0
- data/app/datatables/admin/effective_orders_datatable.rb +100 -0
- data/app/datatables/effective_orders_datatable.rb +79 -0
- data/app/helpers/effective_carts_helper.rb +113 -0
- data/app/helpers/effective_orders_helper.rb +143 -0
- data/app/helpers/effective_paypal_helper.rb +49 -0
- data/app/helpers/effective_stripe_helper.rb +85 -0
- data/app/helpers/effective_subscriptions_helper.rb +34 -0
- data/app/mailers/effective/orders_mailer.rb +196 -0
- data/app/models/concerns/acts_as_purchasable.rb +118 -0
- data/app/models/concerns/acts_as_subscribable.rb +90 -0
- data/app/models/concerns/acts_as_subscribable_buyer.rb +49 -0
- data/app/models/effective/access_denied.rb +17 -0
- data/app/models/effective/cart.rb +88 -0
- data/app/models/effective/cart_item.rb +40 -0
- data/app/models/effective/customer.rb +92 -0
- data/app/models/effective/order.rb +541 -0
- data/app/models/effective/order_item.rb +63 -0
- data/app/models/effective/product.rb +23 -0
- data/app/models/effective/sold_out_validator.rb +7 -0
- data/app/models/effective/subscripter.rb +185 -0
- data/app/models/effective/subscription.rb +95 -0
- data/app/models/effective/tax_rate_calculator.rb +48 -0
- data/app/views/admin/customers/_actions.html.haml +2 -0
- data/app/views/admin/customers/index.html.haml +6 -0
- data/app/views/admin/customers/show.html.haml +6 -0
- data/app/views/admin/order_items/index.html.haml +3 -0
- data/app/views/admin/orders/_datatable_actions.html.haml +18 -0
- data/app/views/admin/orders/_form.html.haml +35 -0
- data/app/views/admin/orders/_form_note_internal.html.haml +7 -0
- data/app/views/admin/orders/_order_actions.html.haml +9 -0
- data/app/views/admin/orders/_order_item_fields.html.haml +14 -0
- data/app/views/admin/orders/checkout.html.haml +3 -0
- data/app/views/admin/orders/edit.html.haml +6 -0
- data/app/views/admin/orders/index.html.haml +6 -0
- data/app/views/admin/orders/new.html.haml +4 -0
- data/app/views/admin/orders/show.html.haml +4 -0
- data/app/views/effective/carts/_cart.html.haml +28 -0
- data/app/views/effective/carts/_cart_actions.html.haml +3 -0
- data/app/views/effective/carts/show.html.haml +17 -0
- data/app/views/effective/customers/_customer.html.haml +72 -0
- data/app/views/effective/customers/_form.html.haml +21 -0
- data/app/views/effective/customers/edit.html.haml +4 -0
- data/app/views/effective/customers/update.js.erb +5 -0
- data/app/views/effective/orders/_checkout_actions.html.haml +3 -0
- data/app/views/effective/orders/_checkout_step1.html.haml +4 -0
- data/app/views/effective/orders/_checkout_step2.html.haml +37 -0
- data/app/views/effective/orders/_datatable_actions.html.haml +2 -0
- data/app/views/effective/orders/_fields.html.haml +31 -0
- data/app/views/effective/orders/_fields_note.html.haml +7 -0
- data/app/views/effective/orders/_fields_terms.html.haml +8 -0
- data/app/views/effective/orders/_order.html.haml +11 -0
- data/app/views/effective/orders/_order_actions.html.haml +18 -0
- data/app/views/effective/orders/_order_deferred.html.haml +9 -0
- data/app/views/effective/orders/_order_footer.html.haml +1 -0
- data/app/views/effective/orders/_order_header.html.haml +23 -0
- data/app/views/effective/orders/_order_items.html.haml +72 -0
- data/app/views/effective/orders/_order_notes.html.haml +17 -0
- data/app/views/effective/orders/_order_payment.html.haml +24 -0
- data/app/views/effective/orders/_order_shipping.html.haml +30 -0
- data/app/views/effective/orders/_orders_table.html.haml +23 -0
- data/app/views/effective/orders/cheque/_form.html.haml +4 -0
- data/app/views/effective/orders/declined.html.haml +12 -0
- data/app/views/effective/orders/deferred.html.haml +13 -0
- data/app/views/effective/orders/deferred/_form.html.haml +16 -0
- data/app/views/effective/orders/edit.html.haml +3 -0
- data/app/views/effective/orders/free/_form.html.haml +5 -0
- data/app/views/effective/orders/index.html.haml +3 -0
- data/app/views/effective/orders/mark_as_paid/_form.html.haml +23 -0
- data/app/views/effective/orders/moneris/_form.html.haml +47 -0
- data/app/views/effective/orders/new.html.haml +3 -0
- data/app/views/effective/orders/paypal/_form.html.haml +5 -0
- data/app/views/effective/orders/phone/_form.html.haml +4 -0
- data/app/views/effective/orders/pretend/_form.html.haml +8 -0
- data/app/views/effective/orders/purchased.html.haml +11 -0
- data/app/views/effective/orders/refund/_form.html.haml +5 -0
- data/app/views/effective/orders/show.html.haml +6 -0
- data/app/views/effective/orders/stripe/_element.html.haml +8 -0
- data/app/views/effective/orders/stripe/_form.html.haml +31 -0
- data/app/views/effective/orders_mailer/order_error.html.haml +11 -0
- data/app/views/effective/orders_mailer/order_receipt_to_admin.html.haml +2 -0
- data/app/views/effective/orders_mailer/order_receipt_to_buyer.html.haml +2 -0
- data/app/views/effective/orders_mailer/payment_request_to_buyer.html.haml +13 -0
- data/app/views/effective/orders_mailer/pending_order_invoice_to_buyer.html.haml +13 -0
- data/app/views/effective/orders_mailer/refund_notification_to_admin.html.haml +15 -0
- data/app/views/effective/orders_mailer/subscription_canceled.html.haml +9 -0
- data/app/views/effective/orders_mailer/subscription_created.html.haml +13 -0
- data/app/views/effective/orders_mailer/subscription_event_to_admin.html.haml +13 -0
- data/app/views/effective/orders_mailer/subscription_payment_failed.html.haml +9 -0
- data/app/views/effective/orders_mailer/subscription_payment_succeeded.html.haml +9 -0
- data/app/views/effective/orders_mailer/subscription_trial_expired.html.haml +5 -0
- data/app/views/effective/orders_mailer/subscription_trialing.html.haml +7 -0
- data/app/views/effective/orders_mailer/subscription_updated.html.haml +13 -0
- data/app/views/effective/subscripter/_form.html.haml +60 -0
- data/app/views/effective/subscripter/_plan.html.haml +23 -0
- data/app/views/layouts/effective_orders_mailer_layout.html.haml +25 -0
- data/config/effective_orders.rb +279 -0
- data/config/routes.rb +70 -0
- data/db/migrate/01_create_effective_orders.rb.erb +137 -0
- data/lib/effective_orders.rb +243 -0
- data/lib/effective_orders/engine.rb +60 -0
- data/lib/effective_orders/version.rb +3 -0
- data/lib/generators/effective_orders/install_generator.rb +63 -0
- data/lib/generators/templates/effective_orders_mailer_preview.rb +120 -0
- data/lib/tasks/effective_orders_tasks.rake +69 -0
- 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
|