effective_orders 6.9.10 → 6.11.0
Sign up to get free protection for your applications and to get access to all the features.
- 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/helpers/effective_deluxe_helper.rb +15 -0
- data/app/helpers/effective_orders_helper.rb +2 -0
- data/app/models/concerns/acts_as_purchasable.rb +12 -0
- data/app/models/concerns/acts_as_purchasable_parent.rb +12 -0
- data/app/models/effective/deluxe_api.rb +277 -0
- data/app/models/effective/order.rb +44 -7
- 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: 6a3716b7b619276755bac8d33027424eed7b97519fdc1454d498349074e2a022
|
4
|
+
data.tar.gz: 5071eb8c4733b659c1d6e72f0ad7974301af0c32d0df6701547eedddaefc723b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 30ee25cf450e876da9a219c303c51d1469994fc055d039fe610e7abcddd3d988bc7fa5558ac17dcf05f06ff8afc0d23d6094a1aa106271b091b0fe2c69390310
|
7
|
+
data.tar.gz: abc8e9d7fac67cac46be97ad522f7ffce5e64a47a4a12e8bc9d6f1c17368788dceab075b9ef302f7d937872ebf7f16cbcaabd5d5f9b38ba4f316a46c5dcedc9e
|
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
|
@@ -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
|
@@ -62,6 +62,14 @@ module ActsAsPurchasable
|
|
62
62
|
module ClassMethods
|
63
63
|
def acts_as_purchasable?; true; end
|
64
64
|
|
65
|
+
def before_defer(&block)
|
66
|
+
send :define_method, :before_defer do |order, order_item| self.instance_exec(order, order_item, &block) end
|
67
|
+
end
|
68
|
+
|
69
|
+
def after_defer(&block)
|
70
|
+
send :define_method, :after_defer do |order, order_item| self.instance_exec(order, order_item, &block) end
|
71
|
+
end
|
72
|
+
|
65
73
|
def before_purchase(&block)
|
66
74
|
send :define_method, :before_purchase do |order, order_item| self.instance_exec(order, order_item, &block) end
|
67
75
|
end
|
@@ -70,6 +78,10 @@ module ActsAsPurchasable
|
|
70
78
|
send :define_method, :after_purchase do |order, order_item| self.instance_exec(order, order_item, &block) end
|
71
79
|
end
|
72
80
|
|
81
|
+
def before_decline(&block)
|
82
|
+
send :define_method, :before_decline do |order, order_item| self.instance_exec(order, order_item, &block) end
|
83
|
+
end
|
84
|
+
|
73
85
|
def after_decline(&block)
|
74
86
|
send :define_method, :after_decline do |order, order_item| self.instance_exec(order, order_item, &block) end
|
75
87
|
end
|
@@ -13,6 +13,14 @@ module ActsAsPurchasableParent
|
|
13
13
|
module ClassMethods
|
14
14
|
def acts_as_purchasable_parent?; true; end
|
15
15
|
|
16
|
+
def before_defer(&block)
|
17
|
+
send :define_method, :before_defer do |order| self.instance_exec(order, &block) end
|
18
|
+
end
|
19
|
+
|
20
|
+
def after_defer(&block)
|
21
|
+
send :define_method, :after_defer do |order| self.instance_exec(order, &block) end
|
22
|
+
end
|
23
|
+
|
16
24
|
def before_purchase(&block)
|
17
25
|
send :define_method, :before_purchase do |order| self.instance_exec(order, &block) end
|
18
26
|
end
|
@@ -21,6 +29,10 @@ module ActsAsPurchasableParent
|
|
21
29
|
send :define_method, :after_purchase do |order| self.instance_exec(order, &block) end
|
22
30
|
end
|
23
31
|
|
32
|
+
def before_decline(&block)
|
33
|
+
send :define_method, :before_decline do |order| self.instance_exec(order, &block) end
|
34
|
+
end
|
35
|
+
|
24
36
|
def after_decline(&block)
|
25
37
|
send :define_method, :after_decline do |order| self.instance_exec(order, &block) end
|
26
38
|
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
|
@@ -515,6 +515,10 @@ module Effective
|
|
515
515
|
self[:total] || get_total()
|
516
516
|
end
|
517
517
|
|
518
|
+
def total_to_f
|
519
|
+
((total || 0) / 100.0).to_f
|
520
|
+
end
|
521
|
+
|
518
522
|
def total_with_surcharge
|
519
523
|
get_total_with_surcharge()
|
520
524
|
end
|
@@ -692,11 +696,41 @@ module Effective
|
|
692
696
|
sync_quickbooks!(skip: true)
|
693
697
|
end
|
694
698
|
|
695
|
-
def defer!(provider: 'none', email: true)
|
696
|
-
|
699
|
+
def defer!(provider: 'none', email: true, validate: true)
|
700
|
+
raise('order already purchased') if purchased?
|
701
|
+
|
702
|
+
# Assign attributes
|
703
|
+
assign_attributes(
|
704
|
+
payment_provider: provider,
|
705
|
+
|
706
|
+
status: :deferred,
|
707
|
+
purchased_at: nil,
|
708
|
+
purchased_by: nil,
|
709
|
+
|
710
|
+
deferred_at: (deferred_at.presence || Time.zone.now),
|
711
|
+
deferred_by: (deferred_by.presence || current_user)
|
712
|
+
)
|
713
|
+
|
714
|
+
if current_user&.email.present?
|
715
|
+
assign_attributes(email: current_user.email)
|
716
|
+
end
|
717
|
+
|
718
|
+
error = nil
|
719
|
+
|
720
|
+
begin
|
721
|
+
Effective::Order.transaction do
|
722
|
+
run_purchasable_callbacks(:before_defer)
|
723
|
+
save!(validate: validate)
|
724
|
+
run_purchasable_callbacks(:after_defer)
|
725
|
+
end
|
726
|
+
rescue ActiveRecord::RecordInvalid => e
|
727
|
+
self.status = status_was
|
728
|
+
|
729
|
+
error = e.message
|
730
|
+
raise ::ActiveRecord::Rollback
|
731
|
+
end
|
697
732
|
|
698
|
-
|
699
|
-
deferred!
|
733
|
+
raise "Failed to defer order: #{error || errors.full_messages.to_sentence}" unless error.nil?
|
700
734
|
|
701
735
|
send_payment_request_to_buyer! if email
|
702
736
|
|
@@ -705,11 +739,8 @@ module Effective
|
|
705
739
|
|
706
740
|
def decline!(payment: 'none', provider: 'none', card: 'none', validate: true)
|
707
741
|
return false if declined?
|
708
|
-
|
709
742
|
raise('order already purchased') if purchased?
|
710
743
|
|
711
|
-
error = nil
|
712
|
-
|
713
744
|
assign_attributes(
|
714
745
|
skip_buyer_validations: true,
|
715
746
|
|
@@ -722,6 +753,12 @@ module Effective
|
|
722
753
|
payment_card: (card.presence || 'none')
|
723
754
|
)
|
724
755
|
|
756
|
+
if current_user&.email.present?
|
757
|
+
assign_attributes(email: current_user.email)
|
758
|
+
end
|
759
|
+
|
760
|
+
error = nil
|
761
|
+
|
725
762
|
Effective::Order.transaction do
|
726
763
|
begin
|
727
764
|
run_purchasable_callbacks(:before_decline)
|
@@ -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.11.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-14 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
|