effective_orders 6.9.9 → 6.10.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 +4 -4
- data/app/assets/images/effective_orders/deluxe.png +0 -0
- data/app/assets/javascripts/effective_orders/providers/deluxe.js +32 -0
- data/app/assets/stylesheets/effective_orders/_order.scss +4 -0
- data/app/controllers/effective/orders_controller.rb +1 -0
- data/app/controllers/effective/providers/deluxe.rb +75 -0
- data/app/datatables/admin/effective_orders_datatable.rb +15 -14
- data/app/datatables/effective_orders_datatable.rb +7 -1
- data/app/helpers/effective_deluxe_helper.rb +15 -0
- data/app/helpers/effective_orders_helper.rb +2 -0
- data/app/models/effective/deluxe_api.rb +277 -0
- data/app/models/effective/order.rb +12 -0
- data/app/views/effective/orders/_checkout_step2.html.haml +3 -0
- data/app/views/effective/orders/deluxe/_css.html.haml +12 -0
- data/app/views/effective/orders/deluxe/_element.html.haml +9 -0
- data/app/views/effective/orders/deluxe/_form.html.haml +19 -0
- data/config/effective_orders.rb +11 -0
- data/config/routes.rb +1 -0
- data/lib/effective_orders/version.rb +1 -1
- data/lib/effective_orders.rb +13 -3
- metadata +10 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d05dc976af35c0751fd5c638d2ab2fa0ae63343230ee620694b7363e86e14a61
|
4
|
+
data.tar.gz: 0fc4e26794e6fa3bd90b1037eb1984c297eb1de4a60a0bce88aaeff9ae2d43c7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c5f6e85d70807fc53e113e9e11221f7d168367c5728287101b1fb8613251f5e19aedcd3dd0d0c98f4408e9504889c698d5325c3a065a1c812eaae48421ee7719
|
7
|
+
data.tar.gz: b0ced315e41f158d6efb30deb427f44e93577b4bd245d5b96553765e9ff04b8c7f030a829b480d5d8872042e931b959e30516909d71efbdc0c1821b7db75dd11
|
Binary file
|
@@ -0,0 +1,32 @@
|
|
1
|
+
// https://developer.deluxe.com/s/article-hosted-payment-form
|
2
|
+
|
3
|
+
function initializeDeluxe() {
|
4
|
+
let $deluxe = $('form[data-deluxe-checkout]:not(.initialized)');
|
5
|
+
if($deluxe.length == 0) return;
|
6
|
+
|
7
|
+
let options = $deluxe.data('deluxe-checkout');
|
8
|
+
|
9
|
+
HostedForm.init(options, {
|
10
|
+
onFailure: (data) => { $('#deluxe-checkout-errors').text(JSON.stringify(data)); },
|
11
|
+
onInvalid: (data) => { $('#deluxe-checkout-errors').text(JSON.stringify(data)); },
|
12
|
+
|
13
|
+
onSuccess: (data) => {
|
14
|
+
let value = btoa(JSON.stringify(data)); // A base64 encoded JSON object
|
15
|
+
|
16
|
+
$form = $('form[data-deluxe-checkout]').first();
|
17
|
+
$form.find('input[name="deluxe[payment_intent]"]').val(value);
|
18
|
+
$form.submit();
|
19
|
+
|
20
|
+
$('#deluxeCheckout').fadeOut('slow');
|
21
|
+
$('#deluxe-checkout-loading').text('Thank you! Processing payment information. Please wait...');
|
22
|
+
},
|
23
|
+
}).then((instance) => {
|
24
|
+
$('#deluxe-checkout-loading').text('');
|
25
|
+
instance.renderHpf();
|
26
|
+
});
|
27
|
+
|
28
|
+
$deluxe.addClass('initialized');
|
29
|
+
};
|
30
|
+
|
31
|
+
$(document).ready(function() { initializeDeluxe() });
|
32
|
+
$(document).on('turbolinks:load', function() { initializeDeluxe() });
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Effective
|
2
|
+
module Providers
|
3
|
+
module Deluxe
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
def deluxe
|
7
|
+
raise('deluxe provider is not available') unless EffectiveOrders.deluxe?
|
8
|
+
|
9
|
+
@order = Effective::Order.deep.find(params[:id])
|
10
|
+
|
11
|
+
EffectiveResources.authorize!(self, :update, @order)
|
12
|
+
|
13
|
+
## Process Payment Intent
|
14
|
+
|
15
|
+
# The payment_intent is set by the Deluxe HostedPaymentForm
|
16
|
+
payment_intent = deluxe_params[:payment_intent]
|
17
|
+
|
18
|
+
if payment_intent.blank?
|
19
|
+
flash[:danger] = 'Unable to process deluxe order without payment. please try again.'
|
20
|
+
return order_not_processed(declined_url: payment_intent[:declined_url])
|
21
|
+
end
|
22
|
+
|
23
|
+
# Decode the base64 encoded JSON object into a Hash
|
24
|
+
payment_intent = (JSON.parse(Base64.decode64(payment_intent)) rescue nil)
|
25
|
+
raise('expected payment_intent to be a Hash') unless payment_intent.kind_of?(Hash)
|
26
|
+
raise('expected a token payment') unless payment_intent['type'] == 'Token'
|
27
|
+
|
28
|
+
valid = payment_intent['status'] == 'success'
|
29
|
+
|
30
|
+
if valid == false
|
31
|
+
card_info = deluxe_api.card_info(payment_intent)
|
32
|
+
return order_declined(payment: card_info, provider: 'deluxe', card: card_info['card'], declined_url: declined_url)
|
33
|
+
end
|
34
|
+
|
35
|
+
## Process Authorization
|
36
|
+
authorization = deluxe_api.authorize_payment(@order, payment_intent)
|
37
|
+
valid = [0].include?(authorization['responseCode'])
|
38
|
+
|
39
|
+
if valid == false
|
40
|
+
flash[:danger] = "Payment was unsuccessful. The credit card authorization failed with message: #{Array(authorization['responseMessage']).to_sentence.presence || 'none'}. Please try again."
|
41
|
+
return order_declined(payment: authorization, provider: 'deluxe', card: authorization['card'], declined_url: deluxe_params[:declined_url])
|
42
|
+
end
|
43
|
+
|
44
|
+
## Complete Payment
|
45
|
+
payment = deluxe_api.complete_payment(@order, authorization)
|
46
|
+
valid = [0].include?(payment['responseCode'])
|
47
|
+
|
48
|
+
if valid == false
|
49
|
+
flash[:danger] = "Payment was unsuccessful. The credit card payment failed with message: #{Array(payment['responseMessage']).to_sentence.presence || 'none'}. Please try again."
|
50
|
+
return order_declined(payment: payment, provider: 'deluxe', card: payment['card'], declined_url: deluxe_params[:declined_url])
|
51
|
+
end
|
52
|
+
|
53
|
+
# Valid Authorized and Completed Payment
|
54
|
+
order_purchased(
|
55
|
+
payment: payment,
|
56
|
+
provider: 'deluxe',
|
57
|
+
card: payment['card'],
|
58
|
+
purchased_url: deluxe_params[:purchased_url],
|
59
|
+
current_user: (current_user unless admin_checkout?(deluxe_params))
|
60
|
+
)
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def deluxe_params
|
66
|
+
params.require(:deluxe).permit(:payment_intent, :purchased_url, :declined_url)
|
67
|
+
end
|
68
|
+
|
69
|
+
def deluxe_api
|
70
|
+
@deluxe_api ||= Effective::DeluxeApi.new
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -50,25 +50,26 @@ module Admin
|
|
50
50
|
col :purchased_by, search: :string, visible: EffectiveOrders.organization_enabled?
|
51
51
|
|
52
52
|
if attributes[:user_id].blank?
|
53
|
-
col :user, search: :string
|
54
|
-
|
55
|
-
if defined?(EffectiveMemberships)
|
56
|
-
col(:member_number, label: 'Member #', sort: false) do |order|
|
57
|
-
order.user.try(:membership).try(:number)
|
58
|
-
end.search do |collection, term|
|
59
|
-
memberships = Effective::Membership.where(owner_type: current_user.class.name).where('number ILIKE ?', "%#{term}%")
|
60
|
-
collection.where(user_id: memberships.select('owner_id'))
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
col :billing_name, visible: false
|
65
|
-
col :email, visible: false
|
53
|
+
col :user, search: :string, visible: !EffectiveOrders.organization_enabled?
|
66
54
|
end
|
67
55
|
|
68
56
|
if attributes[:organization_id].blank?
|
69
|
-
col :organization, visible: EffectiveOrders.organization_enabled?
|
57
|
+
col :organization, search: :string, visible: EffectiveOrders.organization_enabled?
|
58
|
+
end
|
59
|
+
|
60
|
+
if defined?(EffectiveMemberships)
|
61
|
+
col(:member_number, label: 'Member #', sort: false, visible: false) do |order|
|
62
|
+
order.organization.try(:membership).try(:number) || order.user.try(:membership).try(:number)
|
63
|
+
end.search do |collection, term|
|
64
|
+
# TODO add organizations too
|
65
|
+
user_memberships = Effective::Membership.where(owner_type: current_user.class.name).where('number ILIKE ?', "%#{term}%")
|
66
|
+
collection.where(user_id: user_memberships.select('owner_id'))
|
67
|
+
end
|
70
68
|
end
|
71
69
|
|
70
|
+
col :billing_name, visible: false
|
71
|
+
col :email, visible: false
|
72
|
+
|
72
73
|
col :parent, visible: false, search: :string
|
73
74
|
|
74
75
|
col :cc, visible: false
|
@@ -23,6 +23,8 @@ class EffectiveOrdersDatatable < Effective::Datatable
|
|
23
23
|
end
|
24
24
|
|
25
25
|
col :parent, visible: false, search: :string
|
26
|
+
col :user, visible: false, search: :string
|
27
|
+
col :organization, search: :string, visible: false
|
26
28
|
|
27
29
|
col :status
|
28
30
|
|
@@ -70,7 +72,11 @@ class EffectiveOrdersDatatable < Effective::Datatable
|
|
70
72
|
end
|
71
73
|
|
72
74
|
collection do
|
73
|
-
scope = Effective::Order.all.deep
|
75
|
+
scope = Effective::Order.all.deep
|
76
|
+
|
77
|
+
if attributes[:user_id].blank? && attributes[:organization_id].blank?
|
78
|
+
scope = scope.for(current_user)
|
79
|
+
end
|
74
80
|
|
75
81
|
if EffectiveOrders.orders_collection_scope.respond_to?(:call)
|
76
82
|
scope = EffectiveOrders.orders_collection_scope.call(scope)
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module EffectiveDeluxeHelper
|
2
|
+
|
3
|
+
# https://developer.deluxe.com/s/article-hosted-payment-form
|
4
|
+
def deluxe_hosted_payment_form_options(order)
|
5
|
+
{
|
6
|
+
xtoken: EffectiveOrders.deluxe.fetch(:access_token),
|
7
|
+
containerId: "deluxeCheckout",
|
8
|
+
xcssid: "deluxeCheckoutCss",
|
9
|
+
xrtype: "Generate Token",
|
10
|
+
xpm: "1", # 0 = CC & ACH, 1 = CC, 2 = ACH
|
11
|
+
xautoprompt: false,
|
12
|
+
xbtntext: order_checkout_label(:deluxe)
|
13
|
+
}
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,277 @@
|
|
1
|
+
# https://developer.deluxe.com/s/article-api-reference
|
2
|
+
# We use Oauth2 client to get an authorization token. Then pass that token into a REST api.
|
3
|
+
# We get a payment_intent from the front end HostedPaymentForm, then call authorize and complete on it.
|
4
|
+
# Effective::DeluxeApi.new.health_check
|
5
|
+
module Effective
|
6
|
+
class DeluxeApi
|
7
|
+
SCRUB = /[^\w\d#,\s]/
|
8
|
+
|
9
|
+
# All required
|
10
|
+
attr_accessor :environment
|
11
|
+
attr_accessor :client_id
|
12
|
+
attr_accessor :client_secret
|
13
|
+
attr_accessor :access_token
|
14
|
+
attr_accessor :currency
|
15
|
+
|
16
|
+
def initialize(environment: nil, client_id: nil, client_secret: nil, access_token: nil, currency: nil)
|
17
|
+
self.environment = environment || EffectiveOrders.deluxe.fetch(:environment)
|
18
|
+
self.client_id = client_id || EffectiveOrders.deluxe.fetch(:client_id)
|
19
|
+
self.client_secret = client_secret || EffectiveOrders.deluxe.fetch(:client_secret)
|
20
|
+
self.access_token = access_token || EffectiveOrders.deluxe.fetch(:access_token)
|
21
|
+
self.currency = currency || EffectiveOrders.deluxe.fetch(:currency)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Health Check
|
25
|
+
def health_check
|
26
|
+
get('/')
|
27
|
+
end
|
28
|
+
|
29
|
+
# Authorize Payment
|
30
|
+
def authorize_payment(order, payment_intent)
|
31
|
+
response = post('/payments/authorize', params: authorize_payment_params(order, payment_intent))
|
32
|
+
|
33
|
+
# Sanity check response
|
34
|
+
raise('expected responseCode') unless response.kind_of?(Hash) && response['responseCode'].present?
|
35
|
+
|
36
|
+
# Sanity check response approved vs authorized
|
37
|
+
valid = [0].include?(response['responseCode'])
|
38
|
+
|
39
|
+
# We might be approved for an amount less than the order total. Not sure what to do here
|
40
|
+
if valid && (amountApproved = response['amountApproved']) != (amountAuthorized = order.total_to_f)
|
41
|
+
raise("expected authorize payment amountApproved #{amountApproved} to be the same as the amountAuthorized #{amountAuthorized} but it was not")
|
42
|
+
end
|
43
|
+
|
44
|
+
# Generate the card info we can store
|
45
|
+
card = card_info(payment_intent)
|
46
|
+
|
47
|
+
# Return the authorization params merged with the card info
|
48
|
+
response.reverse_merge(card)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Complete Payment
|
52
|
+
def complete_payment(order, authorization)
|
53
|
+
response = post('/payments/complete', params: complete_payment_params(order, authorization))
|
54
|
+
|
55
|
+
# Sanity check response
|
56
|
+
raise('expected responseCode') unless response.kind_of?(Hash) && response['responseCode'].present?
|
57
|
+
|
58
|
+
# Sanity check response approved vs authorized
|
59
|
+
valid = [0].include?(response['responseCode'])
|
60
|
+
|
61
|
+
# We might be approved for an amount less than the order total. Not sure what to do here
|
62
|
+
if valid && (amountApproved = response['amountApproved']) != (amountAuthorized = order.total_to_f)
|
63
|
+
raise("expected complete payment amountApproved #{amountApproved} to be the same as the amountAuthorized #{amountAuthorized} but it was not")
|
64
|
+
end
|
65
|
+
|
66
|
+
# The authorization information
|
67
|
+
authorization = { 'paymentId' => authorization } if authorization.kind_of?(String)
|
68
|
+
|
69
|
+
# Return the complete params merged with the authorization params
|
70
|
+
response.reverse_merge(authorization)
|
71
|
+
end
|
72
|
+
|
73
|
+
def complete_payment_params(order, payment_intent)
|
74
|
+
raise('expected an Effective::Order') unless order.kind_of?(Effective::Order)
|
75
|
+
|
76
|
+
payment_id = extract_payment_id(payment_intent)
|
77
|
+
amount = { amount: order.total_to_f, currency: currency }
|
78
|
+
|
79
|
+
# Params passed into Complete Payment
|
80
|
+
{
|
81
|
+
paymentId: payment_id,
|
82
|
+
amount: amount
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
def authorize_payment_params(order, payment_intent)
|
87
|
+
raise('expected an Effective::Order') unless order.kind_of?(Effective::Order)
|
88
|
+
|
89
|
+
token = extract_token(payment_intent)
|
90
|
+
|
91
|
+
amount = {
|
92
|
+
amount: order.total_to_f,
|
93
|
+
currency: currency
|
94
|
+
}
|
95
|
+
|
96
|
+
billingAddress = if (address = order.billing_address).present?
|
97
|
+
{
|
98
|
+
email: order.email,
|
99
|
+
address: scrub(address.address1, limit: 250),
|
100
|
+
address2: scrub(address.address2),
|
101
|
+
city: scrub(address.city, limit: 50),
|
102
|
+
state: address.state_code,
|
103
|
+
country: address.country_code,
|
104
|
+
postalCode: address.postal_code
|
105
|
+
}.compact
|
106
|
+
end
|
107
|
+
|
108
|
+
shippingAddress = if (address = order.shipping_address).present?
|
109
|
+
{
|
110
|
+
address: scrub(address.address1, limit: 250),
|
111
|
+
address2: scrub(address.address2),
|
112
|
+
city: scrub(address.city, limit: 50),
|
113
|
+
state: address.state_code,
|
114
|
+
country: address.country_code,
|
115
|
+
postalCode: address.postal_code
|
116
|
+
}.compact
|
117
|
+
end
|
118
|
+
|
119
|
+
paymentMethod = {
|
120
|
+
token: { token: token['token'], expiry: (token['expDate'] || token['expiry']), cvv: token['cvv'] }.compact,
|
121
|
+
billingAddress: billingAddress
|
122
|
+
}.compact
|
123
|
+
|
124
|
+
customData = [
|
125
|
+
({ name: 'order_id', value: order.to_param }),
|
126
|
+
({ name: 'user_id', value: order.user_id.to_s } if order.user_id.present?),
|
127
|
+
({ name: 'organization_id', value: order.organization_id.to_s } if order.organization_id.present?)
|
128
|
+
].compact
|
129
|
+
|
130
|
+
# Params passed into Authorize Payment
|
131
|
+
{
|
132
|
+
amount: amount,
|
133
|
+
paymentMethod: paymentMethod,
|
134
|
+
shippingAddress: shippingAddress,
|
135
|
+
customData: customData,
|
136
|
+
}.compact
|
137
|
+
end
|
138
|
+
|
139
|
+
def get(endpoint, params: nil)
|
140
|
+
query = ('?' + params.compact.map { |k, v| "$#{k}=#{v}" }.join('&')) if params.present?
|
141
|
+
|
142
|
+
uri = URI.parse(api_url + endpoint + query.to_s)
|
143
|
+
|
144
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
145
|
+
http.read_timeout = 10
|
146
|
+
http.use_ssl = true
|
147
|
+
|
148
|
+
result = with_retries do
|
149
|
+
puts "[GET] #{uri}" if Rails.env.development?
|
150
|
+
|
151
|
+
response = http.get(uri, headers)
|
152
|
+
raise Exception.new("#{response.code} #{response.body}") unless response.code == '200'
|
153
|
+
|
154
|
+
response
|
155
|
+
end
|
156
|
+
|
157
|
+
JSON.parse(result.body)
|
158
|
+
end
|
159
|
+
|
160
|
+
def post(endpoint, params:)
|
161
|
+
uri = URI.parse(api_url + endpoint)
|
162
|
+
|
163
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
164
|
+
http.read_timeout = 10
|
165
|
+
http.use_ssl = true
|
166
|
+
|
167
|
+
result = with_retries do
|
168
|
+
puts "[POST] #{uri} #{params}" if Rails.env.development?
|
169
|
+
|
170
|
+
response = http.post(uri.path, params.to_json, headers)
|
171
|
+
raise Exception.new("#{response.code} #{response.body}") unless response.code == '200'
|
172
|
+
|
173
|
+
response
|
174
|
+
end
|
175
|
+
|
176
|
+
JSON.parse(result.body)
|
177
|
+
end
|
178
|
+
|
179
|
+
# Takes a payment_intent and returns the card info we can store
|
180
|
+
def card_info(payment_intent)
|
181
|
+
token = extract_token(payment_intent)
|
182
|
+
|
183
|
+
# Return the authorization params merged with the card info
|
184
|
+
last4 = token['maskedPan'].to_s.last(4)
|
185
|
+
card = token['cardType'].to_s.downcase
|
186
|
+
date = token['expDate']
|
187
|
+
cvv = token['cvv']
|
188
|
+
|
189
|
+
active_card = "**** **** **** #{last4} #{card} #{date}" if last4.present?
|
190
|
+
|
191
|
+
{ 'active_card' => active_card, 'card' => card, 'expDate' => date, 'cvv' => cvv }.compact
|
192
|
+
end
|
193
|
+
|
194
|
+
private
|
195
|
+
|
196
|
+
def headers
|
197
|
+
{ "Content-Type": "application/json", "Authorization": "Bearer #{authorization_token}", "PartnerToken": access_token }
|
198
|
+
end
|
199
|
+
|
200
|
+
def client
|
201
|
+
OAuth2::Client.new(
|
202
|
+
client_id,
|
203
|
+
client_secret,
|
204
|
+
site: client_url,
|
205
|
+
token_url: '/secservices/oauth2/v2/token' # https://sandbox.api.deluxe.com/secservices/oauth2/v2/token
|
206
|
+
)
|
207
|
+
end
|
208
|
+
|
209
|
+
def authorization_token
|
210
|
+
@authorization_token ||= Rails.cache.fetch(authorization_cache_key, expires_in: 60.minutes) do
|
211
|
+
puts "[AUTH] Oauth2 Get Token" if Rails.env.development?
|
212
|
+
client.client_credentials.get_token.token
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
# https://sandbox.api.deluxe.com
|
217
|
+
def client_url
|
218
|
+
case environment
|
219
|
+
when 'production' then 'https://api.deluxe.com'
|
220
|
+
when 'sandbox' then 'https://sandbox.api.deluxe.com' # No trailing /
|
221
|
+
else raise('unexpected deluxe environment')
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
# https://sandbox.api.deluxe.com/dpp/v1/gateway/
|
226
|
+
def api_url
|
227
|
+
client_url + '/dpp/v1/gateway'
|
228
|
+
end
|
229
|
+
|
230
|
+
def extract_token(payment_intent)
|
231
|
+
raise('expected a payment intent') unless payment_intent.kind_of?(Hash)
|
232
|
+
|
233
|
+
token = payment_intent['data'] || payment_intent
|
234
|
+
raise('expected a payment intent Hash') unless token['token'].present? && token['expDate'].present?
|
235
|
+
|
236
|
+
token
|
237
|
+
end
|
238
|
+
|
239
|
+
def extract_payment_id(authorization)
|
240
|
+
return authorization if authorization.kind_of?(String)
|
241
|
+
raise('expected an authorization Hash') unless authorization.kind_of?(Hash)
|
242
|
+
|
243
|
+
payment_id = authorization['paymentId']
|
244
|
+
raise('expected a paymentId') unless payment_id.present?
|
245
|
+
|
246
|
+
payment_id
|
247
|
+
end
|
248
|
+
|
249
|
+
def scrub(value, limit: 100)
|
250
|
+
return value unless value.kind_of?(String)
|
251
|
+
value.gsub(SCRUB, '').first(limit)
|
252
|
+
end
|
253
|
+
|
254
|
+
def authorization_cache_key
|
255
|
+
"deluxe_api_#{client_id}"
|
256
|
+
end
|
257
|
+
|
258
|
+
def with_retries(retries: (Rails.env.development? ? 0 : 3), wait: 2, &block)
|
259
|
+
raise('expected a block') unless block_given?
|
260
|
+
|
261
|
+
begin
|
262
|
+
return yield
|
263
|
+
rescue Exception => e
|
264
|
+
# Reset cache and query for a new authorization token on any error
|
265
|
+
Rails.cache.delete(authorization_cache_key)
|
266
|
+
@authorization_token = nil
|
267
|
+
|
268
|
+
if (retries -= 1) > 0
|
269
|
+
sleep(wait); retry
|
270
|
+
else
|
271
|
+
raise
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
end
|
277
|
+
end
|
@@ -107,6 +107,14 @@ module Effective
|
|
107
107
|
includes(:addresses, :user, :parent, :purchased_by, :organization, order_items: :purchasable)
|
108
108
|
}
|
109
109
|
|
110
|
+
scope :for, -> (user) {
|
111
|
+
if user.respond_to?(:organizations)
|
112
|
+
where(user: user).or(where(organization: user.organizations))
|
113
|
+
else
|
114
|
+
where(user: user)
|
115
|
+
end
|
116
|
+
}
|
117
|
+
|
110
118
|
scope :sorted, -> { order(:id) }
|
111
119
|
|
112
120
|
scope :purchased, -> { where(status: :purchased) }
|
@@ -507,6 +515,10 @@ module Effective
|
|
507
515
|
self[:total] || get_total()
|
508
516
|
end
|
509
517
|
|
518
|
+
def total_to_f
|
519
|
+
((total || 0) / 100.0).to_f
|
520
|
+
end
|
521
|
+
|
510
522
|
def total_with_surcharge
|
511
523
|
get_total_with_surcharge()
|
512
524
|
end
|
@@ -18,6 +18,9 @@
|
|
18
18
|
- if EffectiveOrders.pretend?
|
19
19
|
= render partial: '/effective/orders/pretend/form', locals: provider_locals
|
20
20
|
|
21
|
+
- if EffectiveOrders.deluxe?
|
22
|
+
= render partial: '/effective/orders/deluxe/form', locals: provider_locals
|
23
|
+
|
21
24
|
- if EffectiveOrders.moneris?
|
22
25
|
= render partial: '/effective/orders/moneris/form', locals: provider_locals
|
23
26
|
|
@@ -0,0 +1,9 @@
|
|
1
|
+
.effective-deluxe-checkout
|
2
|
+
#deluxe-checkout-loading.text-center Loading...
|
3
|
+
#deluxe-checkout-errors.text-danger
|
4
|
+
|
5
|
+
%style#deluxeCheckoutCss
|
6
|
+
-# Pass in custom CSS to the Deluxe hosted payment form iframe
|
7
|
+
= render('effective/orders/deluxe/css')
|
8
|
+
|
9
|
+
#deluxeCheckout
|
@@ -0,0 +1,19 @@
|
|
1
|
+
- deluxe = deluxe_hosted_payment_form_options(order)
|
2
|
+
|
3
|
+
.card
|
4
|
+
.card-body
|
5
|
+
%h2 Checkout
|
6
|
+
%p
|
7
|
+
%em This checkout is powered by #{link_to('Deluxe', 'https://www.deluxe.com/', target: '_blank', class: 'btn-link')}
|
8
|
+
|
9
|
+
.my-4.text-center
|
10
|
+
= image_tag('effective_orders/deluxe.png', alt: 'Deluxe.com Logo', width: 200)
|
11
|
+
|
12
|
+
= effective_form_with(scope: :deluxe, url: effective_orders.deluxe_order_path(order), data: { 'deluxe-checkout': deluxe.to_json }) do |f|
|
13
|
+
= f.hidden_field :purchased_url, value: purchased_url
|
14
|
+
= f.hidden_field :declined_url, value: declined_url
|
15
|
+
|
16
|
+
-# This is set by the deluxe.js javascript on Submit
|
17
|
+
= f.hidden_field :payment_intent, required: true
|
18
|
+
|
19
|
+
= render('effective/orders/deluxe/element')
|
data/config/effective_orders.rb
CHANGED
@@ -122,6 +122,17 @@ EffectiveOrders.setup do |config|
|
|
122
122
|
# success: 'Thank you! You have indicated that this order will be purchased by cheque. Please send us a cheque and a copy of this invoice at your earliest convenience.'
|
123
123
|
# }
|
124
124
|
|
125
|
+
# Deluxe
|
126
|
+
config.deluxe = false
|
127
|
+
|
128
|
+
# config.deluxe = {
|
129
|
+
# environment: (Rails.env.production? ? 'production' : 'sandbox'),
|
130
|
+
# client_id: '',
|
131
|
+
# client_secret: '',
|
132
|
+
# access_token: '',
|
133
|
+
# currency: 'CAD'
|
134
|
+
# }
|
135
|
+
|
125
136
|
# E-transfer
|
126
137
|
# This is an deferred payment
|
127
138
|
config.etransfer = false
|
data/config/routes.rb
CHANGED
data/lib/effective_orders.rb
CHANGED
@@ -42,7 +42,7 @@ module EffectiveOrders
|
|
42
42
|
:free_enabled, :mark_as_paid_enabled, :pretend_enabled, :pretend_message, :buyer_purchases_refund,
|
43
43
|
|
44
44
|
# Payment processors. false or Hash
|
45
|
-
:cheque, :etransfer, :moneris, :moneris_checkout, :paypal, :phone, :refund, :stripe, :subscriptions, :trial
|
45
|
+
:cheque, :deluxe, :etransfer, :moneris, :moneris_checkout, :paypal, :phone, :refund, :stripe, :subscriptions, :trial
|
46
46
|
]
|
47
47
|
end
|
48
48
|
|
@@ -81,6 +81,10 @@ module EffectiveOrders
|
|
81
81
|
free_enabled == true
|
82
82
|
end
|
83
83
|
|
84
|
+
def self.deluxe?
|
85
|
+
deluxe.kind_of?(Hash)
|
86
|
+
end
|
87
|
+
|
84
88
|
def self.deferred?
|
85
89
|
deferred_providers.present?
|
86
90
|
end
|
@@ -130,7 +134,7 @@ module EffectiveOrders
|
|
130
134
|
end
|
131
135
|
|
132
136
|
def self.single_payment_processor?
|
133
|
-
[moneris?, moneris_checkout?, paypal?, stripe?].select { |enabled| enabled }.length == 1
|
137
|
+
[deluxe?, moneris?, moneris_checkout?, paypal?, stripe?].select { |enabled| enabled }.length == 1
|
134
138
|
end
|
135
139
|
|
136
140
|
# The Effective::Order.payment_provider value must be in this collection
|
@@ -138,6 +142,7 @@ module EffectiveOrders
|
|
138
142
|
[
|
139
143
|
('cheque' if cheque?),
|
140
144
|
('credit card' if mark_as_paid?),
|
145
|
+
('deluxe' if deluxe?),
|
141
146
|
('etransfer' if etransfer?),
|
142
147
|
('free' if free?),
|
143
148
|
('moneris' if moneris?),
|
@@ -157,6 +162,7 @@ module EffectiveOrders
|
|
157
162
|
[
|
158
163
|
('cheque' if mark_as_paid?),
|
159
164
|
('credit card' if mark_as_paid?),
|
165
|
+
('deluxe' if deluxe?),
|
160
166
|
('etransfer' if etransfer?),
|
161
167
|
#('free' if free?),
|
162
168
|
('moneris' if moneris?),
|
@@ -176,7 +182,7 @@ module EffectiveOrders
|
|
176
182
|
end
|
177
183
|
|
178
184
|
def self.credit_card_payment_providers
|
179
|
-
['credit card', 'moneris', 'moneris_checkout', 'paypal', 'stripe']
|
185
|
+
['credit card', 'deluxe', 'moneris', 'moneris_checkout', 'paypal', 'stripe']
|
180
186
|
end
|
181
187
|
|
182
188
|
def self.qb_sync?
|
@@ -276,6 +282,10 @@ module EffectiveOrders
|
|
276
282
|
stripe_plans.map { |plan| [plan[:name], plan[:id]] }
|
277
283
|
end
|
278
284
|
|
285
|
+
def self.deluxe_script_url
|
286
|
+
"https://hostedform2.deluxe.com/V2/deluxe.js"
|
287
|
+
end
|
288
|
+
|
279
289
|
def self.moneris_checkout_script_url
|
280
290
|
case EffectiveOrders.moneris_checkout.fetch(:environment)
|
281
291
|
when 'prod' then 'https://gateway.moneris.com/chktv2/js/chkt_v2.00.js'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: effective_orders
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 6.
|
4
|
+
version: 6.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Code and Effect
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-05-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -189,10 +189,12 @@ files:
|
|
189
189
|
- MIT-LICENSE
|
190
190
|
- README.md
|
191
191
|
- app/assets/config/effective_orders_manifest.js
|
192
|
+
- app/assets/images/effective_orders/deluxe.png
|
192
193
|
- app/assets/images/effective_orders/logo.png
|
193
194
|
- app/assets/images/effective_orders/stripe.png
|
194
195
|
- app/assets/javascripts/effective_orders.js
|
195
196
|
- app/assets/javascripts/effective_orders/customers.js.coffee
|
197
|
+
- app/assets/javascripts/effective_orders/providers/deluxe.js
|
196
198
|
- app/assets/javascripts/effective_orders/providers/moneris_checkout.js.coffee
|
197
199
|
- app/assets/javascripts/effective_orders/providers/stripe.js.coffee
|
198
200
|
- app/assets/javascripts/effective_orders/subscriptions.js.coffee
|
@@ -208,6 +210,7 @@ files:
|
|
208
210
|
- app/controllers/effective/customers_controller.rb
|
209
211
|
- app/controllers/effective/orders_controller.rb
|
210
212
|
- app/controllers/effective/providers/cheque.rb
|
213
|
+
- app/controllers/effective/providers/deluxe.rb
|
211
214
|
- app/controllers/effective/providers/etransfer.rb
|
212
215
|
- app/controllers/effective/providers/free.rb
|
213
216
|
- app/controllers/effective/providers/mark_as_paid.rb
|
@@ -228,6 +231,7 @@ files:
|
|
228
231
|
- app/datatables/admin/report_transactions_grouped_by_qb_name_datatable.rb
|
229
232
|
- app/datatables/effective_orders_datatable.rb
|
230
233
|
- app/helpers/effective_carts_helper.rb
|
234
|
+
- app/helpers/effective_deluxe_helper.rb
|
231
235
|
- app/helpers/effective_moneris_checkout_helper.rb
|
232
236
|
- app/helpers/effective_orders_helper.rb
|
233
237
|
- app/helpers/effective_paypal_helper.rb
|
@@ -241,6 +245,7 @@ files:
|
|
241
245
|
- app/models/effective/cart.rb
|
242
246
|
- app/models/effective/cart_item.rb
|
243
247
|
- app/models/effective/customer.rb
|
248
|
+
- app/models/effective/deluxe_api.rb
|
244
249
|
- app/models/effective/order.rb
|
245
250
|
- app/models/effective/order_item.rb
|
246
251
|
- app/models/effective/product.rb
|
@@ -291,6 +296,9 @@ files:
|
|
291
296
|
- app/views/effective/orders/declined.html.haml
|
292
297
|
- app/views/effective/orders/deferred.html.haml
|
293
298
|
- app/views/effective/orders/deferred/_form.html.haml
|
299
|
+
- app/views/effective/orders/deluxe/_css.html.haml
|
300
|
+
- app/views/effective/orders/deluxe/_element.html.haml
|
301
|
+
- app/views/effective/orders/deluxe/_form.html.haml
|
294
302
|
- app/views/effective/orders/edit.html.haml
|
295
303
|
- app/views/effective/orders/etransfer/_form.html.haml
|
296
304
|
- app/views/effective/orders/free/_form.html.haml
|