effective_orders 6.25.4 → 6.27.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/javascripts/effective_orders/providers/helcim.js +41 -0
- data/app/controllers/admin/orders_controller.rb +3 -3
- data/app/controllers/effective/orders_controller.rb +1 -0
- data/app/controllers/effective/providers/helcim.rb +58 -0
- data/app/datatables/admin/effective_orders_datatable.rb +1 -1
- data/app/helpers/effective_helcim_helper.rb +5 -0
- data/app/helpers/effective_orders_helper.rb +2 -0
- data/app/mailers/effective/orders_mailer.rb +1 -1
- data/app/models/effective/helcim_api.rb +277 -0
- data/app/models/effective/order.rb +22 -11
- data/app/models/effective/order_item.rb +12 -0
- data/app/views/admin/orders/_form_order.html.haml +2 -2
- data/app/views/effective/orders/_checkout_step2.html.haml +3 -0
- data/app/views/effective/orders/_datatable_actions.html.haml +2 -2
- data/app/views/effective/orders/helcim/_element.html.haml +5 -0
- data/app/views/effective/orders/helcim/_form.html.haml +22 -0
- data/config/effective_orders.rb +12 -2
- data/config/locales/effective_orders.en.yml +2 -2
- data/config/routes.rb +4 -3
- data/lib/effective_orders/version.rb +1 -1
- data/lib/effective_orders.rb +17 -3
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8f2d362666e0acf723037a83305a0d8abb7390b9450bdb70ca8edb2917023062
|
4
|
+
data.tar.gz: a9582919b75d433be885f5bd4f58b7fe45f4b49778627cbd1d66152d346c7a02
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: de9920886821096194b4ee8b07ee2a643994e3daa4c726173cb354de7a0b044b74ca28d51deda550857e52f9166fb539cd6484f0eb5b3149c5d353a3f6fb76ca
|
7
|
+
data.tar.gz: 302f8062e198841746e462b589946f8fbd4b1bc52744e1f493402889db788739069708d9cc5729513a12cbfeae6c3c2551e945f470b3ce5c3942b6b88bc2d67a
|
@@ -0,0 +1,41 @@
|
|
1
|
+
// https://devdocs.helcim.com/docs/render-helcimpayjs
|
2
|
+
|
3
|
+
$(document).on('click', 'form[data-helcim-checkout] a', function(event) {
|
4
|
+
event.preventDefault();
|
5
|
+
initializeHelcim();
|
6
|
+
});
|
7
|
+
|
8
|
+
function initializeHelcim() {
|
9
|
+
let $helcim = $('form[data-helcim-checkout]');
|
10
|
+
if($helcim.length == 0) return;
|
11
|
+
|
12
|
+
let token = $helcim.data('helcim-checkout');
|
13
|
+
|
14
|
+
// From HelcimPay.js
|
15
|
+
appendHelcimPayIframe(token)
|
16
|
+
|
17
|
+
// Add our event listener
|
18
|
+
window.addEventListener('message', helcimPayIframeEvent, false);
|
19
|
+
};
|
20
|
+
|
21
|
+
function helcimPayIframeEvent(event) {
|
22
|
+
if(event.data.eventName.startsWith('helcim-pay-js')) {
|
23
|
+
window.removeEventListener('message', helcimPayIframeEvent, false);
|
24
|
+
|
25
|
+
if(event.data.eventStatus == 'HIDE') {
|
26
|
+
let button = $('.effective-helcim-checkout').find('#helcim-checkout-button').get(0);
|
27
|
+
Rails.enableElement(button)
|
28
|
+
}
|
29
|
+
|
30
|
+
if(event.data.eventStatus == 'SUCCESS') {
|
31
|
+
let payment = btoa(event.data.eventMessage);
|
32
|
+
|
33
|
+
let $form = $('form[data-helcim-checkout]').first();
|
34
|
+
$form.find('input[name="helcim[payment]"]').val(payment);
|
35
|
+
$form.submit();
|
36
|
+
|
37
|
+
$('#helcimCheckout').fadeOut('slow');
|
38
|
+
$('#helcim-checkout-loading').text('Thank you! Processing payment information. Please wait...');
|
39
|
+
}
|
40
|
+
}
|
41
|
+
};
|
@@ -16,7 +16,7 @@ module Admin
|
|
16
16
|
|
17
17
|
submit :save, 'Save', success: -> {
|
18
18
|
message = flash_success(resource, params[:action])
|
19
|
-
message << ". A request for payment has been sent to #{resource.emails_send_to}" if resource.
|
19
|
+
message << ". A request for payment has been sent to #{resource.emails_send_to}" if resource.send_payment_request?
|
20
20
|
message
|
21
21
|
}
|
22
22
|
|
@@ -78,7 +78,7 @@ module Admin
|
|
78
78
|
@order = Effective::Order.deep.was_not_purchased.find(params[:id])
|
79
79
|
authorize_effective_order!
|
80
80
|
|
81
|
-
if @order.
|
81
|
+
if @order.send_payment_request!
|
82
82
|
flash[:success] = "A request for payment has been sent to #{@order.emails_send_to}"
|
83
83
|
else
|
84
84
|
flash[:danger] = 'Unable to send payment request'
|
@@ -93,7 +93,7 @@ module Admin
|
|
93
93
|
begin
|
94
94
|
authorize_effective_order!
|
95
95
|
|
96
|
-
@orders.each { |order| order.
|
96
|
+
@orders.each { |order| order.send_payment_request! }
|
97
97
|
render json: { status: 200, message: "Successfully sent #{@orders.length} payment request emails"}
|
98
98
|
rescue Exception => e
|
99
99
|
render json: { status: 500, message: "Bulk send payment request error: #{e.message}" }
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Effective
|
2
|
+
module Providers
|
3
|
+
module Helcim
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
def helcim
|
7
|
+
raise('helcim provider is not available') unless EffectiveOrders.helcim?
|
8
|
+
|
9
|
+
@order = Effective::Order.deep.find(params[:id])
|
10
|
+
@order.current_user = current_user unless admin_checkout?(helcim_params)
|
11
|
+
|
12
|
+
EffectiveResources.authorize!(self, :update, @order)
|
13
|
+
|
14
|
+
# Process and Verify Payment
|
15
|
+
api = Effective::HelcimApi.new
|
16
|
+
|
17
|
+
# Decode the payment payload
|
18
|
+
payment_payload = api.decode_payment_payload(helcim_params[:payment])
|
19
|
+
|
20
|
+
if payment_payload.blank?
|
21
|
+
flash[:danger] = 'Unable to process helcim order without payment. please try again.'
|
22
|
+
return order_not_processed(declined_url: helcim_params[:declined_url])
|
23
|
+
end
|
24
|
+
|
25
|
+
# Verify the payment
|
26
|
+
payment = api.verify_payment(@order, payment_payload)
|
27
|
+
|
28
|
+
# If it's purchased
|
29
|
+
purchased = api.purchased?(payment)
|
30
|
+
|
31
|
+
if purchased == false
|
32
|
+
flash[:danger] = "Payment was unsuccessful. The credit card payment failed with message: #{payment['status'] || 'none'}. Please try again."
|
33
|
+
return order_declined(
|
34
|
+
payment: payment,
|
35
|
+
provider: 'helcim',
|
36
|
+
card: payment['card'],
|
37
|
+
declined_url: helcim_params[:declined_url]
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Valid Authorized and Completed Payment
|
42
|
+
order_purchased(
|
43
|
+
payment: payment,
|
44
|
+
provider: 'helcim',
|
45
|
+
card: payment['card'],
|
46
|
+
purchased_url: helcim_params[:purchased_url]
|
47
|
+
)
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def helcim_params
|
53
|
+
params.require(:helcim).permit(:payment, :purchased_url, :declined_url)
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -10,7 +10,7 @@ module Admin
|
|
10
10
|
|
11
11
|
bulk_action(
|
12
12
|
'Send invoice email to selected purchased orders',
|
13
|
-
effective_orders.
|
13
|
+
effective_orders.bulk_send_order_email_orders_path,
|
14
14
|
data: { confirm: 'Send invoice emails?' }
|
15
15
|
)
|
16
16
|
end
|
@@ -26,7 +26,7 @@ module Effective
|
|
26
26
|
raise('expected an Effective::Order') unless resource.kind_of?(Effective::Order)
|
27
27
|
|
28
28
|
@order = resource
|
29
|
-
@order_email = Effective::OrderEmail.new(resource)
|
29
|
+
@order_email = Effective::OrderEmail.new(resource, opts)
|
30
30
|
|
31
31
|
subject = subject_for(__method__, @order_email.subject, @order, opts)
|
32
32
|
headers = headers_for(@order, opts)
|
@@ -0,0 +1,277 @@
|
|
1
|
+
# https://devdocs.helcim.com/docs/overview-of-helcimpayjs
|
2
|
+
# Effective::HelcimApi.new.health_check
|
3
|
+
module Effective
|
4
|
+
class HelcimApi
|
5
|
+
# All required
|
6
|
+
attr_accessor :environment
|
7
|
+
attr_accessor :api_token
|
8
|
+
attr_accessor :currency
|
9
|
+
attr_accessor :brand_color
|
10
|
+
|
11
|
+
attr_accessor :read_timeout
|
12
|
+
|
13
|
+
def initialize(environment: nil, api_token: nil, currency: nil, brand_color: nil)
|
14
|
+
self.environment = environment || EffectiveOrders.helcim.fetch(:environment)
|
15
|
+
self.api_token = api_token || EffectiveOrders.helcim.fetch(:api_token)
|
16
|
+
self.currency = currency || EffectiveOrders.helcim.fetch(:currency)
|
17
|
+
self.brand_color = brand_color || EffectiveOrders.helcim.fetch(:brand_color)
|
18
|
+
end
|
19
|
+
|
20
|
+
def health_check
|
21
|
+
get('/connection-test')
|
22
|
+
end
|
23
|
+
|
24
|
+
def get_transaction(id)
|
25
|
+
get("/card-transactions/#{id}")
|
26
|
+
end
|
27
|
+
|
28
|
+
# Make the Preload Request
|
29
|
+
# https://devdocs.helcim.com/reference/checkout-init
|
30
|
+
def initialize_request(order)
|
31
|
+
params = {
|
32
|
+
amount: ('%.2f' % order.total_to_f),
|
33
|
+
currency: currency,
|
34
|
+
paymentMethod: 'cc',
|
35
|
+
paymentType: 'purchase', # purchase, preauth, verify
|
36
|
+
allowPartial: 0,
|
37
|
+
hasConvenienceFee: 0,
|
38
|
+
taxAmount: ('%.2f' % order.tax_to_f if order.tax.to_i > 0),
|
39
|
+
hideExistingPaymentDetails: 0,
|
40
|
+
setAsDefaultPaymentMethod: 1,
|
41
|
+
confirmationScreen: false,
|
42
|
+
displayContactFields: 0,
|
43
|
+
customStyling: {
|
44
|
+
brandColor: (brand_color || '815AF0')
|
45
|
+
},
|
46
|
+
invoiceRequest: {
|
47
|
+
invoiceNumber: '#' + order.to_param
|
48
|
+
},
|
49
|
+
customerRequest: {
|
50
|
+
contactName: order.billing_name,
|
51
|
+
businessName: order.organization.to_s.presence,
|
52
|
+
}.compact,
|
53
|
+
}.compact
|
54
|
+
|
55
|
+
params[:invoiceRequest][:lineItems] = order.order_items.map do |item|
|
56
|
+
{
|
57
|
+
description: item.name,
|
58
|
+
quantity: item.quantity,
|
59
|
+
price: ('%.2f' % item.price_to_f),
|
60
|
+
total: ('%.2f' % item.subtotal_to_f),
|
61
|
+
taxAmount: ('%.2f' % item.tax_to_f),
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
address = order.billing_address
|
66
|
+
country = helcim_country(address&.country_code)
|
67
|
+
|
68
|
+
if address.present? && country.to_s.length == 3
|
69
|
+
params[:customerRequest][:billingAddress] = {
|
70
|
+
name: order.billing_name,
|
71
|
+
street1: address.address1,
|
72
|
+
street2: address.address2,
|
73
|
+
city: address.city,
|
74
|
+
province: address.state_code,
|
75
|
+
country: country,
|
76
|
+
postalCode: address.postal_code,
|
77
|
+
email: order.email,
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
address = order.shipping_address
|
82
|
+
country = helcim_country(address&.country_code)
|
83
|
+
|
84
|
+
if address.present? && country.to_s.length == 3
|
85
|
+
params[:customerRequest][:shippingAddress] = {
|
86
|
+
name: order.billing_name,
|
87
|
+
street1: address.address1,
|
88
|
+
street2: address.address2,
|
89
|
+
city: address.city,
|
90
|
+
province: address.state_code,
|
91
|
+
country: country,
|
92
|
+
postalCode: address.postal_code,
|
93
|
+
email: order.email,
|
94
|
+
}
|
95
|
+
end
|
96
|
+
|
97
|
+
response = post('/helcim-pay/initialize', params: params)
|
98
|
+
raise("expected response to be a Hash") unless response.kind_of?(Hash)
|
99
|
+
|
100
|
+
token = response['checkoutToken']
|
101
|
+
raise("expected response to include a checkoutToken") unless token.present?
|
102
|
+
|
103
|
+
# Return the token to the front end form
|
104
|
+
token
|
105
|
+
end
|
106
|
+
|
107
|
+
# Decode the base64 encoded JSON object that was given from the form into a Hash
|
108
|
+
def decode_payment_payload(payload)
|
109
|
+
return if payload.blank?
|
110
|
+
|
111
|
+
raise('expected a string') unless payload.kind_of?(String)
|
112
|
+
|
113
|
+
payment = (JSON.parse(Base64.decode64(payload)) rescue nil)
|
114
|
+
raise('expected payment to be a Hash') unless payment.kind_of?(Hash)
|
115
|
+
|
116
|
+
payment = payment.dig('data', 'data')
|
117
|
+
raise('expected payment data') unless payment.kind_of?(Hash)
|
118
|
+
raise('expected payment data with a transactionId') unless payment['transactionId'].present?
|
119
|
+
|
120
|
+
payment
|
121
|
+
end
|
122
|
+
|
123
|
+
def purchased?(payment)
|
124
|
+
raise('expected a payment Hash') unless payment.kind_of?(Hash)
|
125
|
+
(payment['status'] == 'APPROVED' && payment['type'] == 'purchase')
|
126
|
+
end
|
127
|
+
|
128
|
+
# Considers the insecure payment_payload, requests the real transaction from Helcim and verifies it vs the order
|
129
|
+
def verify_payment(order, payment_payload)
|
130
|
+
raise('expected a payment_payload Hash') unless payment_payload.kind_of?(Hash)
|
131
|
+
|
132
|
+
transaction_id = payment_payload['transactionId']
|
133
|
+
raise('expected a payment_payload with a transactionId') unless transaction_id.present?
|
134
|
+
|
135
|
+
payment = get_transaction(transaction_id)
|
136
|
+
raise('expected an existing card-transaction payment') unless payment.kind_of?(Hash)
|
137
|
+
|
138
|
+
# Compare the payment (trusted truth) and the payment_payload (untrusted)
|
139
|
+
if payment['transactionId'].to_s != payment_payload['transactionId'].to_s
|
140
|
+
raise('expected the payment and payment_payload to have the same transactionId')
|
141
|
+
end
|
142
|
+
|
143
|
+
# Validate order ids
|
144
|
+
if payment['invoiceNumber'].to_s != '#' + order.to_param
|
145
|
+
raise("expected card-transaction invoiceNumber to be the same as the order to_param")
|
146
|
+
end
|
147
|
+
|
148
|
+
# Validate amounts if purchased
|
149
|
+
if purchased?(payment) && (amount = payment['amount'].to_f) != (amountAuthorized = order.total_to_f)
|
150
|
+
raise("expected card-transaction amount #{amount} to be the same as the amountAuthorized #{amountAuthorized} but it was not")
|
151
|
+
end
|
152
|
+
|
153
|
+
# Normalize the card info and scrub out the card number
|
154
|
+
payment = payment.merge(card_info(payment)).except('cardNumber')
|
155
|
+
|
156
|
+
payment
|
157
|
+
end
|
158
|
+
|
159
|
+
# Takes a payment_intent and returns the card info we can store
|
160
|
+
def card_info(payment)
|
161
|
+
# Return the authorization params merged with the card info
|
162
|
+
last4 = payment['cardNumber'].to_s.last(4)
|
163
|
+
card = payment['cardType'].to_s.downcase
|
164
|
+
|
165
|
+
active_card = "**** **** **** #{last4} #{card}" if last4.present?
|
166
|
+
|
167
|
+
{ 'active_card' => active_card, 'card' => card }.compact
|
168
|
+
end
|
169
|
+
|
170
|
+
def get(endpoint, params: nil)
|
171
|
+
query = ('?' + params.compact.map { |k, v| "$#{k}=#{v}" }.join('&')) if params.present?
|
172
|
+
|
173
|
+
uri = URI.parse(api_url + endpoint + query.to_s)
|
174
|
+
|
175
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
176
|
+
http.read_timeout = (read_timeout || 30)
|
177
|
+
http.use_ssl = true
|
178
|
+
|
179
|
+
result = with_retries do
|
180
|
+
puts "[GET] #{uri}" if Rails.env.development?
|
181
|
+
|
182
|
+
response = http.get(uri, headers)
|
183
|
+
raise Exception.new("#{response.code} #{response.body}") unless response.code == '200'
|
184
|
+
|
185
|
+
response
|
186
|
+
end
|
187
|
+
|
188
|
+
JSON.parse(result.body)
|
189
|
+
end
|
190
|
+
|
191
|
+
def post(endpoint, params:)
|
192
|
+
uri = URI.parse(api_url + endpoint)
|
193
|
+
|
194
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
195
|
+
http.read_timeout = (read_timeout || 30)
|
196
|
+
http.use_ssl = true
|
197
|
+
|
198
|
+
puts "[POST] #{uri} #{params}" if Rails.env.development?
|
199
|
+
|
200
|
+
response = http.post(uri.path, params.to_json, headers)
|
201
|
+
raise Exception.new("#{response.code} #{response.body}") unless response.code == '200'
|
202
|
+
|
203
|
+
JSON.parse(response.body)
|
204
|
+
end
|
205
|
+
|
206
|
+
# Effective::HelcimApi.new.set_logo!
|
207
|
+
# Put your file in the apps/tenant/app/assets/images/tenant/helcim-logo.png
|
208
|
+
# Run this once to set the logo
|
209
|
+
def set_logo!(path: nil)
|
210
|
+
path ||= Rails.root.join("apps/#{Tenant.current}/app/assets/images/#{Tenant.current}/helcim-logo.png")
|
211
|
+
raise("Expected #{path} to exist") unless File.exist?(path)
|
212
|
+
|
213
|
+
url = URI.parse(api_url + '/branding/logo')
|
214
|
+
boundary = "AaB03x"
|
215
|
+
|
216
|
+
# Build multipart form data
|
217
|
+
body = [
|
218
|
+
"--#{boundary}",
|
219
|
+
"Content-Disposition: form-data; name=\"logo\"; filename=\"#{File.basename(path)}\"",
|
220
|
+
"Content-Type: image/#{File.extname(path).downcase.delete('.')}",
|
221
|
+
"",
|
222
|
+
File.binread(path),
|
223
|
+
"--#{boundary}--"
|
224
|
+
].join("\r\n")
|
225
|
+
|
226
|
+
# Set up HTTP request
|
227
|
+
http = Net::HTTP.new(url.host, url.port)
|
228
|
+
http.use_ssl = true
|
229
|
+
|
230
|
+
# Create POST request
|
231
|
+
request = Net::HTTP::Post.new(url.path)
|
232
|
+
request.body = body
|
233
|
+
|
234
|
+
request.initialize_http_header(headers.merge({
|
235
|
+
'Content-Type' => "multipart/form-data; boundary=#{boundary}",
|
236
|
+
'Content-Length' => request.body.length.to_s,
|
237
|
+
}))
|
238
|
+
|
239
|
+
# Send request
|
240
|
+
response = http.request(request)
|
241
|
+
raise Exception.new("#{response.code} #{response.body}") unless response.code == '200'
|
242
|
+
|
243
|
+
JSON.parse(response.body)
|
244
|
+
end
|
245
|
+
|
246
|
+
private
|
247
|
+
|
248
|
+
def headers
|
249
|
+
{ "Accept": "application/json", "Content-Type": "application/json", 'api-token': api_token }
|
250
|
+
end
|
251
|
+
|
252
|
+
def api_url
|
253
|
+
'https://api.helcim.com/v2' # No trailing /
|
254
|
+
end
|
255
|
+
|
256
|
+
def helcim_country(country_code)
|
257
|
+
return 'CAN' if country_code == 'CA' || country_code == 'CAD'
|
258
|
+
return 'USA' if country_code == 'US'
|
259
|
+
country_code
|
260
|
+
end
|
261
|
+
|
262
|
+
def with_retries(retries: (Rails.env.development? ? 0 : 3), wait: 2, &block)
|
263
|
+
raise('expected a block') unless block_given?
|
264
|
+
|
265
|
+
begin
|
266
|
+
return yield
|
267
|
+
rescue Exception => e
|
268
|
+
if (retries -= 1) > 0
|
269
|
+
sleep(wait); retry
|
270
|
+
else
|
271
|
+
raise
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
end
|
277
|
+
end
|
@@ -43,7 +43,7 @@ module Effective
|
|
43
43
|
attr_accessor :confirmed_checkout # Set on the Checkout Step 1
|
44
44
|
|
45
45
|
# Settings in the /admin action forms
|
46
|
-
attr_accessor :
|
46
|
+
attr_accessor :send_payment_request # Set by Admin::Orders#new. Should the payment request email be sent after creating an order?
|
47
47
|
attr_accessor :send_mark_as_paid_email_to_buyer # Set by Admin::Orders#mark_as_paid
|
48
48
|
attr_accessor :skip_buyer_validations # Set by Admin::Orders#create
|
49
49
|
attr_accessor :mailer_preview # Set by the mailer preview. Disabled delayed payment validations
|
@@ -539,6 +539,10 @@ module Effective
|
|
539
539
|
((total || 0) / 100.0).to_f
|
540
540
|
end
|
541
541
|
|
542
|
+
def tax_to_f
|
543
|
+
((tax || 0) / 100.0).to_f
|
544
|
+
end
|
545
|
+
|
542
546
|
def total_with_surcharge
|
543
547
|
get_total_with_surcharge()
|
544
548
|
end
|
@@ -635,8 +639,8 @@ module Effective
|
|
635
639
|
self.addresses.clear if addresses.any? { |address| address.valid? == false }
|
636
640
|
save!
|
637
641
|
|
638
|
-
if
|
639
|
-
after_commit {
|
642
|
+
if send_payment_request?
|
643
|
+
after_commit { send_payment_request! }
|
640
644
|
end
|
641
645
|
|
642
646
|
true
|
@@ -893,6 +897,7 @@ module Effective
|
|
893
897
|
false
|
894
898
|
end
|
895
899
|
|
900
|
+
# Used internally to send emails to the buyer or admin depending on order status
|
896
901
|
def send_order_emails!
|
897
902
|
return false if skip_order_emails?
|
898
903
|
|
@@ -911,18 +916,24 @@ module Effective
|
|
911
916
|
end
|
912
917
|
end
|
913
918
|
|
914
|
-
def
|
915
|
-
EffectiveResources.truthy?(
|
919
|
+
def send_payment_request?
|
920
|
+
EffectiveResources.truthy?(send_payment_request)
|
916
921
|
end
|
917
922
|
|
918
923
|
# Admin datatable action
|
919
|
-
def
|
920
|
-
|
924
|
+
def send_payment_request!
|
925
|
+
return if (purchased_or_deferred? || refund?)
|
926
|
+
|
927
|
+
EffectiveOrders.send_email(:order_email, self, payment_request: true)
|
928
|
+
EffectiveOrders.send_email(:order_email_to_admin, self, payment_request: true) if EffectiveOrders.send_payment_request_to_admin
|
921
929
|
end
|
922
930
|
|
923
931
|
# Admin datatable action
|
924
|
-
def
|
925
|
-
|
932
|
+
def send_order_email!
|
933
|
+
return unless purchased_or_deferred?
|
934
|
+
|
935
|
+
EffectiveOrders.send_email(:order_email, self)
|
936
|
+
EffectiveOrders.send_email(:order_email_to_admin, self) if EffectiveOrders.send_order_receipt_to_admin
|
926
937
|
end
|
927
938
|
|
928
939
|
def log_changes_formatted_value(attribute, value)
|
@@ -1010,7 +1021,7 @@ module Effective
|
|
1010
1021
|
# Normalize payment card
|
1011
1022
|
card = case payment_card.to_s.downcase.gsub(' ', '').strip
|
1012
1023
|
when '' then nil
|
1013
|
-
when 'v', 'visa' then 'Visa'
|
1024
|
+
when 'v', 'visa', 'vi' then 'Visa'
|
1014
1025
|
when 'm', 'mc', 'master', 'mastercard' then 'MasterCard'
|
1015
1026
|
when 'a', 'ax', 'american', 'americanexpress' then 'American Express'
|
1016
1027
|
when 'd', 'discover' then 'Discover'
|
@@ -1021,7 +1032,7 @@ module Effective
|
|
1021
1032
|
if card == 'none' && payment['card_type'].present?
|
1022
1033
|
card = case payment['card_type'].to_s.downcase.gsub(' ', '').strip
|
1023
1034
|
when '' then nil
|
1024
|
-
when 'v', 'visa' then 'Visa'
|
1035
|
+
when 'v', 'visa', 'vi' then 'Visa'
|
1025
1036
|
when 'm', 'mc', 'master', 'mastercard' then 'MasterCard'
|
1026
1037
|
when 'a', 'ax', 'american', 'americanexpress' then 'American Express'
|
1027
1038
|
when 'd', 'discover' then 'Discover'
|
@@ -58,10 +58,18 @@ module Effective
|
|
58
58
|
purchasable&.purchased_download_url
|
59
59
|
end
|
60
60
|
|
61
|
+
def price_to_f
|
62
|
+
((price|| 0) / 100.0).to_f
|
63
|
+
end
|
64
|
+
|
61
65
|
def subtotal
|
62
66
|
price * quantity
|
63
67
|
end
|
64
68
|
|
69
|
+
def subtotal_to_f
|
70
|
+
((subtotal || 0) / 100.0).to_f
|
71
|
+
end
|
72
|
+
|
65
73
|
def quantity
|
66
74
|
self[:quantity] || 1
|
67
75
|
end
|
@@ -72,6 +80,10 @@ module Effective
|
|
72
80
|
(subtotal * order.tax_rate / 100.0).round(0).to_i
|
73
81
|
end
|
74
82
|
|
83
|
+
def tax_to_f
|
84
|
+
((tax || 0) / 100.0).to_f
|
85
|
+
end
|
86
|
+
|
75
87
|
def amount_owing
|
76
88
|
total
|
77
89
|
end
|
@@ -17,9 +17,9 @@
|
|
17
17
|
= f.email_cc_field :cc, hint: "Cc the above on any emailed receipts or payment requests."
|
18
18
|
|
19
19
|
- if f.object.new_record?
|
20
|
-
= f.check_box :
|
20
|
+
= f.check_box :send_payment_request,
|
21
21
|
label: 'Yes, send a payment request email to the buyer and any cc.',
|
22
|
-
value: (f.object.
|
22
|
+
value: (f.object.send_payment_request.nil? ? EffectiveOrders.send_payment_request_to_buyer : f.object.send_payment_request?)
|
23
23
|
|
24
24
|
%hr
|
25
25
|
|
@@ -29,6 +29,9 @@
|
|
29
29
|
- if EffectiveOrders.deluxe?
|
30
30
|
= render partial: '/effective/orders/deluxe/form', locals: provider_locals
|
31
31
|
|
32
|
+
- if EffectiveOrders.helcim?
|
33
|
+
= render partial: '/effective/orders/helcim/form', locals: provider_locals
|
34
|
+
|
32
35
|
- if EffectiveOrders.moneris?
|
33
36
|
= render partial: '/effective/orders/moneris/form', locals: provider_locals
|
34
37
|
|
@@ -4,6 +4,6 @@
|
|
4
4
|
- else
|
5
5
|
= dropdown_link_to 'View', effective_orders.order_path(order)
|
6
6
|
|
7
|
-
- if EffectiveResources.authorized?(controller, :
|
8
|
-
= dropdown_link_to 'E-mail Receipt', effective_orders.
|
7
|
+
- if EffectiveResources.authorized?(controller, :send_order_email, order)
|
8
|
+
= dropdown_link_to 'E-mail Receipt', effective_orders.send_order_email_order_path(order),
|
9
9
|
data: { method: :post, confirm: "Send receipt to #{order.emails.first}?" }
|
@@ -0,0 +1,22 @@
|
|
1
|
+
- token = helcim_initialize_request(order)
|
2
|
+
|
3
|
+
.card
|
4
|
+
.card-body
|
5
|
+
%h2 Checkout
|
6
|
+
|
7
|
+
.mt-4
|
8
|
+
|
9
|
+
= effective_form_with(scope: :helcim, url: effective_orders.helcim_order_path(order), data: { 'helcim-checkout': token }) do |f|
|
10
|
+
= f.hidden_field :purchased_url, value: purchased_url
|
11
|
+
= f.hidden_field :declined_url, value: declined_url
|
12
|
+
|
13
|
+
-# This is set by the helcim.js javascript
|
14
|
+
= f.hidden_field :payment
|
15
|
+
|
16
|
+
- if EffectiveOrders.helcim[:environment] == 'sandbox'
|
17
|
+
.alert.alert-info.mb-4
|
18
|
+
This is the #{Rails.env.upcase} SERVER.
|
19
|
+
%br
|
20
|
+
Use credit card number 4242 4242 4242 4242 with any future expiry and cvv 123
|
21
|
+
|
22
|
+
= render('effective/orders/helcim/element')
|
data/config/effective_orders.rb
CHANGED
@@ -106,9 +106,9 @@ EffectiveOrders.setup do |config|
|
|
106
106
|
config.send_order_declined_to_admin = false
|
107
107
|
config.send_order_declined_to_buyer = false
|
108
108
|
config.send_payment_request_to_buyer = true
|
109
|
-
config.
|
110
|
-
config.send_refund_notification_to_admin = true
|
109
|
+
config.send_payment_request_to_admin = true
|
111
110
|
|
111
|
+
config.send_refund_notification_to_admin = true
|
112
112
|
config.send_order_receipts_when_mark_as_paid = true
|
113
113
|
config.send_order_receipts_when_free = true
|
114
114
|
|
@@ -166,6 +166,16 @@ EffectiveOrders.setup do |config|
|
|
166
166
|
# success: 'Thank you! You have indicated that this order will be purchased by e-transfer. Please send us an e-transfer to "payments@example.com" with password "example" at your earliest convenience'
|
167
167
|
# }
|
168
168
|
|
169
|
+
# Helcim
|
170
|
+
config.helcim = false
|
171
|
+
|
172
|
+
# config.helcim = {
|
173
|
+
# environment: (Rails.env.production? ? 'production' : 'sandbox'),
|
174
|
+
# api_token: '',
|
175
|
+
# currency: 'CAD',
|
176
|
+
# brand_color: '815AF0' # 6-digit hex color without any # sign
|
177
|
+
# }
|
178
|
+
|
169
179
|
# Moneris
|
170
180
|
config.moneris = false
|
171
181
|
|
@@ -6,8 +6,8 @@ en:
|
|
6
6
|
activerecord:
|
7
7
|
actions:
|
8
8
|
effective/order:
|
9
|
-
|
10
|
-
|
9
|
+
send_order_email: 'Send order email'
|
10
|
+
send_order_email_confirm: 'Send order email to @resource.emails_send_to?'
|
11
11
|
|
12
12
|
datatables:
|
13
13
|
admin/report_transactions_datatable: 'Report: Individual Transactions'
|
data/config/routes.rb
CHANGED
@@ -7,7 +7,7 @@ EffectiveOrders::Engine.routes.draw do
|
|
7
7
|
get :purchased
|
8
8
|
get :deferred
|
9
9
|
get :declined
|
10
|
-
post :
|
10
|
+
post :send_order_email
|
11
11
|
|
12
12
|
post :cheque
|
13
13
|
post :deluxe # 1-off payment and purchase
|
@@ -16,6 +16,7 @@ EffectiveOrders::Engine.routes.draw do
|
|
16
16
|
|
17
17
|
post :etransfer
|
18
18
|
post :free
|
19
|
+
post :helcim
|
19
20
|
post :mark_as_paid
|
20
21
|
post :moneris_checkout
|
21
22
|
post :phone
|
@@ -25,7 +26,7 @@ EffectiveOrders::Engine.routes.draw do
|
|
25
26
|
end
|
26
27
|
|
27
28
|
collection do
|
28
|
-
post :
|
29
|
+
post :bulk_send_order_email
|
29
30
|
|
30
31
|
post :moneris_postback
|
31
32
|
post :paypal_postback
|
@@ -56,7 +57,7 @@ EffectiveOrders::Engine.routes.draw do
|
|
56
57
|
resources :orders do
|
57
58
|
member do
|
58
59
|
post :send_payment_request
|
59
|
-
post :
|
60
|
+
post :send_order_email
|
60
61
|
|
61
62
|
delete :void
|
62
63
|
post :unvoid
|
data/lib/effective_orders.rb
CHANGED
@@ -36,7 +36,7 @@ module EffectiveOrders
|
|
36
36
|
# Emails
|
37
37
|
:send_order_receipt_to_admin, :send_order_receipt_to_buyer,
|
38
38
|
:send_order_declined_to_admin, :send_order_declined_to_buyer,
|
39
|
-
:
|
39
|
+
:send_payment_request_to_admin, :send_payment_request_to_buyer,
|
40
40
|
:send_order_receipts_when_mark_as_paid, :send_order_receipts_when_free,
|
41
41
|
:send_subscription_events,
|
42
42
|
:send_subscription_trialing, :send_subscription_trial_expired,
|
@@ -49,7 +49,7 @@ module EffectiveOrders
|
|
49
49
|
:free_enabled, :mark_as_paid_enabled, :pretend_enabled, :pretend_message, :buyer_purchases_refund,
|
50
50
|
|
51
51
|
# Payment processors. false or Hash
|
52
|
-
:cheque, :deluxe, :deluxe_delayed, :etransfer, :moneris, :moneris_checkout, :paypal, :phone, :refund, :stripe, :subscriptions, :trial
|
52
|
+
:cheque, :deluxe, :deluxe_delayed, :etransfer, :helcim, :moneris, :moneris_checkout, :paypal, :phone, :refund, :stripe, :subscriptions, :trial
|
53
53
|
]
|
54
54
|
end
|
55
55
|
|
@@ -104,6 +104,10 @@ module EffectiveOrders
|
|
104
104
|
delayed_providers.present?
|
105
105
|
end
|
106
106
|
|
107
|
+
def self.helcim?
|
108
|
+
helcim.kind_of?(Hash)
|
109
|
+
end
|
110
|
+
|
107
111
|
def self.mark_as_paid?
|
108
112
|
mark_as_paid_enabled == true
|
109
113
|
end
|
@@ -164,6 +168,7 @@ module EffectiveOrders
|
|
164
168
|
('deluxe' if deluxe?),
|
165
169
|
('etransfer' if etransfer?),
|
166
170
|
('free' if free?),
|
171
|
+
('helcim' if helcim?),
|
167
172
|
('moneris' if moneris?),
|
168
173
|
('moneris_checkout' if moneris_checkout?),
|
169
174
|
('paypal' if paypal?),
|
@@ -183,6 +188,7 @@ module EffectiveOrders
|
|
183
188
|
('credit card' if mark_as_paid?),
|
184
189
|
('deluxe' if deluxe?),
|
185
190
|
('etransfer' if etransfer?),
|
191
|
+
('helcim' if helcim?),
|
186
192
|
#('free' if free?),
|
187
193
|
('moneris' if moneris?),
|
188
194
|
('moneris_checkout' if moneris_checkout?),
|
@@ -206,7 +212,7 @@ module EffectiveOrders
|
|
206
212
|
end
|
207
213
|
|
208
214
|
def self.credit_card_payment_providers
|
209
|
-
['credit card', 'deluxe', 'moneris', 'moneris_checkout', 'paypal', 'stripe']
|
215
|
+
['credit card', 'deluxe', 'helcim', 'moneris', 'moneris_checkout', 'paypal', 'stripe']
|
210
216
|
end
|
211
217
|
|
212
218
|
def self.qb_sync?
|
@@ -330,6 +336,10 @@ module EffectiveOrders
|
|
330
336
|
end
|
331
337
|
end
|
332
338
|
|
339
|
+
def self.helcim_script_url
|
340
|
+
"https://secure.helcim.app/helcim-pay/services/start.js"
|
341
|
+
end
|
342
|
+
|
333
343
|
def self.moneris_checkout_script_url
|
334
344
|
case EffectiveOrders.moneris_checkout.fetch(:environment)
|
335
345
|
when 'prod' then 'https://gateway.moneris.com/chktv2/js/chkt_v2.00.js'
|
@@ -346,6 +356,10 @@ module EffectiveOrders
|
|
346
356
|
end
|
347
357
|
end
|
348
358
|
|
359
|
+
def self.stripe_script_url
|
360
|
+
"https://js.stripe.com/v3/"
|
361
|
+
end
|
362
|
+
|
349
363
|
class SoldOutException < Exception; end
|
350
364
|
|
351
365
|
end
|
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.27.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: 2025-07-
|
11
|
+
date: 2025-07-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -195,6 +195,7 @@ files:
|
|
195
195
|
- app/assets/javascripts/effective_orders/customers.js.coffee
|
196
196
|
- app/assets/javascripts/effective_orders/providers/deluxe.js
|
197
197
|
- app/assets/javascripts/effective_orders/providers/deluxe_delayed.js
|
198
|
+
- app/assets/javascripts/effective_orders/providers/helcim.js
|
198
199
|
- app/assets/javascripts/effective_orders/providers/moneris_checkout.js.coffee
|
199
200
|
- app/assets/javascripts/effective_orders/providers/stripe.js.coffee
|
200
201
|
- app/assets/javascripts/effective_orders/subscriptions.js.coffee
|
@@ -216,6 +217,7 @@ files:
|
|
216
217
|
- app/controllers/effective/providers/deluxe_delayed_purchase.rb
|
217
218
|
- app/controllers/effective/providers/etransfer.rb
|
218
219
|
- app/controllers/effective/providers/free.rb
|
220
|
+
- app/controllers/effective/providers/helcim.rb
|
219
221
|
- app/controllers/effective/providers/mark_as_paid.rb
|
220
222
|
- app/controllers/effective/providers/moneris.rb
|
221
223
|
- app/controllers/effective/providers/moneris_checkout.rb
|
@@ -239,6 +241,7 @@ files:
|
|
239
241
|
- app/helpers/effective_carts_helper.rb
|
240
242
|
- app/helpers/effective_deluxe_delayed_helper.rb
|
241
243
|
- app/helpers/effective_deluxe_helper.rb
|
244
|
+
- app/helpers/effective_helcim_helper.rb
|
242
245
|
- app/helpers/effective_moneris_checkout_helper.rb
|
243
246
|
- app/helpers/effective_orders_helper.rb
|
244
247
|
- app/helpers/effective_paypal_helper.rb
|
@@ -253,6 +256,7 @@ files:
|
|
253
256
|
- app/models/effective/cart_item.rb
|
254
257
|
- app/models/effective/customer.rb
|
255
258
|
- app/models/effective/deluxe_api.rb
|
259
|
+
- app/models/effective/helcim_api.rb
|
256
260
|
- app/models/effective/item_name.rb
|
257
261
|
- app/models/effective/order.rb
|
258
262
|
- app/models/effective/order_email.rb
|
@@ -326,6 +330,8 @@ files:
|
|
326
330
|
- app/views/effective/orders/edit.html.haml
|
327
331
|
- app/views/effective/orders/etransfer/_form.html.haml
|
328
332
|
- app/views/effective/orders/free/_form.html.haml
|
333
|
+
- app/views/effective/orders/helcim/_element.html.haml
|
334
|
+
- app/views/effective/orders/helcim/_form.html.haml
|
329
335
|
- app/views/effective/orders/index.html.haml
|
330
336
|
- app/views/effective/orders/mark_as_paid/_form.html.haml
|
331
337
|
- app/views/effective/orders/moneris/_form.html.haml
|