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