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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0b9ab6059f70c4d3a00f14475e7729a4f1a444fdbfda1e979b931b8b70dda1b1
4
- data.tar.gz: 9784af2c4f2134dd5a3cf51278d0ca4c3cca2c938297aeb47f3c2ccb863e7e21
3
+ metadata.gz: d05dc976af35c0751fd5c638d2ab2fa0ae63343230ee620694b7363e86e14a61
4
+ data.tar.gz: 0fc4e26794e6fa3bd90b1037eb1984c297eb1de4a60a0bce88aaeff9ae2d43c7
5
5
  SHA512:
6
- metadata.gz: 8d9f5572457678473ffc196391ccc27d6742f7940449d62fa6f190d79a28d63282f748775b14a722bd87eaf86757756b64c3ccb15762b30b22cf3b5146b5b9a2
7
- data.tar.gz: a9ca5ce96d0669a425a887979c6bbe0fca9ea56dbeee90bb38361e494a97ff3e05d5774ff309ff5d7dd5bf2ba3fd56dfb83bd3b797822d44685d842e891d7afd
6
+ metadata.gz: c5f6e85d70807fc53e113e9e11221f7d168367c5728287101b1fb8613251f5e19aedcd3dd0d0c98f4408e9504889c698d5325c3a065a1c812eaae48421ee7719
7
+ data.tar.gz: b0ced315e41f158d6efb30deb427f44e93577b4bd245d5b96553765e9ff04b8c7f030a829b480d5d8872042e931b959e30516909d71efbdc0c1821b7db75dd11
@@ -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() });
@@ -56,6 +56,10 @@ form.new_effective_order {
56
56
  height: 720px;
57
57
  }
58
58
 
59
+ .effective-deluxe-checkout {
60
+ #deluxeCheckout { height: 280px; }
61
+ }
62
+
59
63
  @media print {
60
64
  .effective-orders-page-content { display: none; }
61
65
 
@@ -4,6 +4,7 @@ module Effective
4
4
  include Concerns::Purchase
5
5
 
6
6
  include Providers::Cheque
7
+ include Providers::Deluxe
7
8
  include Providers::Etransfer
8
9
  include Providers::Free
9
10
  include Providers::MarkAsPaid
@@ -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.where(user: current_user)
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
@@ -42,6 +42,8 @@ module EffectiveOrdersHelper
42
42
  case processor
43
43
  when :cheque
44
44
  'Pay by Cheque'
45
+ when :deluxe
46
+ 'Pay Now'
45
47
  when :etransfer
46
48
  'Pay by E-transfer'
47
49
  when :free
@@ -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,12 @@
1
+ :css
2
+ #dppPaymentContainer {
3
+ .form-control { }
4
+ .form-label { }
5
+
6
+ button {
7
+ color: #fff;
8
+ background-color: #0d6efd;
9
+ border_color: #0d6efd;
10
+ padding: 0.375rem 0.75rem;
11
+ }
12
+ }
@@ -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')
@@ -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
@@ -8,6 +8,7 @@ EffectiveOrders::Engine.routes.draw do
8
8
  post :send_buyer_receipt
9
9
 
10
10
  post :cheque
11
+ post :deluxe
11
12
  post :etransfer
12
13
  post :free
13
14
  post :mark_as_paid
@@ -1,3 +1,3 @@
1
1
  module EffectiveOrders
2
- VERSION = '6.9.9'.freeze
2
+ VERSION = '6.10.0'.freeze
3
3
  end
@@ -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.9.9
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-04-16 00:00:00.000000000 Z
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