effective_orders 6.12.2 → 6.12.4

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: e90f1a83b2e9268d377b28a05fbc423bef5618f2270231e018f04bb0f297ea02
4
- data.tar.gz: a4ed543e612da4fddbc2086b30de01ed5252cb53f033db9d538468a4cdd361df
3
+ metadata.gz: 6d365ebd0599f72ed9c5bbf7e9e9f26284647135153befe60799452b87fc1e0c
4
+ data.tar.gz: 913c9fa4e2d528382652c297d6806fb827c79974baebb9890ddd1854449f98d1
5
5
  SHA512:
6
- metadata.gz: 923d207995eb5f2861cd8425c57902357c9d5141d5169eec8911703051e9be4ecf9e90da6ee71aad88d777ec503d18bcc6478323620ade521a2b995c839a8d64
7
- data.tar.gz: 700ab00a86078101fd05a561b7b8c02098a8941f5a9d645e859bfdbafcc40dd363be9dadc3abfefda4b1a9c33673829451f71eefac07261715cec817641f6b2e
6
+ metadata.gz: d5fde239931a219f5e15a29a085dd4f056f12174fb4e6331b5b05369d8798518781e7eddae549edb3d3e8d34a9a8a18896a6a271fa0883a15161f75766cad95b
7
+ data.tar.gz: 2b0daaa3d6a0b0111efe48cac3db3d2b0073a3a8ed3b51ec5e9beaeaadaee604ae515cdc5c309b33c8a4ad28598bfcbdd3c6f345aa95052543588f31dcc1a6bc
@@ -19,7 +19,7 @@ module Admin
19
19
  scope :all
20
20
  scope :purchased
21
21
 
22
- scope :deferred if EffectiveOrders.deferred_providers.present?
22
+ scope :deferred if EffectiveOrders.deferred? || EffectiveOrders.delayed?
23
23
  scope :voided
24
24
 
25
25
  scope :pending_refunds if EffectiveOrders.refund && !EffectiveOrders.buyer_purchases_refund?
@@ -6,7 +6,7 @@ class EffectiveOrdersDatatable < Effective::Datatable
6
6
  scope :all
7
7
  scope :purchased
8
8
 
9
- scope :deferred if EffectiveOrders.deferred_providers.present?
9
+ scope :deferred if EffectiveOrders.deferred? || EffectiveOrders.delayed?
10
10
  scope :refunds if EffectiveOrders.refund
11
11
  scope :not_purchased
12
12
  end
@@ -45,7 +45,7 @@ module EffectiveOrdersHelper
45
45
  when :deluxe
46
46
  'Pay Now'
47
47
  when :deluxe_delayed
48
- 'Save now and charge me later'
48
+ 'Save card and charge me later'
49
49
  when :etransfer
50
50
  'Pay by E-transfer'
51
51
  when :free
@@ -39,8 +39,19 @@ module ActsAsPurchasableParent
39
39
  end
40
40
 
41
41
  included do
42
- has_many :orders, -> { order(:id) }, as: :parent, class_name: 'Effective::Order', dependent: :nullify
42
+ has_many :orders, -> { order(:id) }, as: :parent, class_name: 'Effective::Order'
43
+
43
44
  accepts_nested_attributes_for :orders
45
+
46
+ before_destroy do
47
+ orders.each do |order|
48
+ raise('unable to destroy a purchasable_parent with purchased orders') if order.purchased?
49
+ order.voided? ? order.save! : order.void!
50
+ end
51
+
52
+ true
53
+ end
54
+
44
55
  end
45
56
 
46
57
  end
@@ -23,15 +23,51 @@ module Effective
23
23
  self.currency = currency || EffectiveOrders.deluxe.fetch(:currency)
24
24
  end
25
25
 
26
- def payment
27
- raise('expected purchase response to be present') unless purchase_response.kind_of?(Hash)
28
- purchase_response
26
+ def health_check
27
+ get('/')
28
+ end
29
+
30
+ def healthy?
31
+ response = health_check()
32
+
33
+ return false unless response.kind_of?(Hash)
34
+ return false unless response['appName'].present?
35
+ return false unless response['environment'].present?
36
+
37
+ true
38
+ end
39
+
40
+ # Decode the base64 encoded JSON object into a Hash
41
+ def decode_payment_intent_payload(payload)
42
+ raise('expected a string') unless payload.kind_of?(String)
43
+
44
+ payment_intent = (JSON.parse(Base64.decode64(payload)) rescue nil)
45
+
46
+ raise('expected payment_intent to be a Hash') unless payment_intent.kind_of?(Hash)
47
+ raise('expected a token payment') unless payment_intent['type'] == 'Token'
48
+
49
+ payment_intent
50
+ end
51
+
52
+ # Takes a payment_intent and returns the card info we can store
53
+ def card_info(payment_intent)
54
+ token = extract_token(payment_intent)
55
+
56
+ # Return the authorization params merged with the card info
57
+ last4 = token['maskedPan'].to_s.last(4)
58
+ card = token['cardType'].to_s.downcase
59
+ date = token['expDate']
60
+ cvv = token['cvv']
61
+
62
+ active_card = "**** **** **** #{last4} #{card} #{date}" if last4.present?
63
+
64
+ { 'active_card' => active_card, 'card' => card, 'expDate' => date, 'cvv' => cvv }.compact
29
65
  end
30
66
 
67
+ # After we store a payment intent we can call purchase! immediately or wait till later.
31
68
  # This calls Authorize Payment and Complete Payment
32
- # Returns true if all good.
33
- # Returns false if there was an error.
34
- # Always sets the @purchase_response which is api.payment
69
+ # Returns true when purchased. Returns false when declined.
70
+ # The response is stored in api.payment() after this is run
35
71
  def purchase!(order, payment_intent)
36
72
  payment_intent = decode_payment_intent_payload(payment_intent) if payment_intent.kind_of?(String)
37
73
  raise('expected payment_intent to be a Hash') unless payment_intent.kind_of?(Hash)
@@ -58,21 +94,56 @@ module Effective
58
94
  true
59
95
  end
60
96
 
61
- # Health Check
62
- def health_check
63
- get('/')
97
+ def payment
98
+ raise('expected purchase response to be present') unless purchase_response.kind_of?(Hash)
99
+ purchase_response
64
100
  end
65
101
 
66
- def healthy?
67
- response = health_check()
102
+ def purchase_delayed_orders!(orders)
103
+ now = Time.zone.now
68
104
 
69
- return false unless response.kind_of?(Hash)
70
- return false unless response['timestamp'].to_s.start_with?(Time.zone.now.strftime('%Y-%m-%d'))
71
- return false unless response['environment'].present?
105
+ Array(orders).each do |order|
106
+ puts "Trying order #{order.id}"
107
+
108
+ begin
109
+ raise('expected a delayed order') unless order.delayed?
110
+ raise('expected a deferred order') unless order.deferred?
111
+ raise('expected delayed payment intent') unless order.delayed_payment_intent.present?
112
+
113
+ order.update_columns(delayed_payment_purchase_ran_at: now, delayed_payment_purchase_result: nil)
114
+
115
+ purchased = purchase!(order, order.delayed_payment_intent)
116
+ provider = order.payment_provider
117
+ payment = self.payment()
118
+ card = payment["card"]
119
+
120
+ if purchased
121
+ order.assign_attributes(delayed_payment_purchase_result: "success")
122
+ order.purchase!(payment: payment, provider: provider, card: card, email: true, skip_buyer_validations: true)
123
+
124
+ puts "Successfully purchased order #{order.id}"
125
+ else
126
+ order.assign_attributes(delayed_payment_purchase_result: "failed with message: #{Array(payment['responseMessage']).to_sentence.presence || 'none'}.")
127
+ order.decline!(payment: payment, provider: provider, card: card)
128
+
129
+ puts "Failed to purchase order #{order.id} #{order.delayed_payment_purchase_result}"
130
+ end
131
+
132
+ rescue => e
133
+ order.update_columns(delayed_payment_purchase_ran_at: now, delayed_payment_purchase_result: "error: #{e.message}")
134
+
135
+ EffectiveLogger.error(e.message, associated: order) if defined?(EffectiveLogger)
136
+ ExceptionNotifier.notify_exception(e, data: { order_id: order.id }) if defined?(ExceptionNotifier)
137
+
138
+ puts "Error purchasing #{order.id}: #{e.message}"
139
+ end
140
+ end
72
141
 
73
142
  true
74
143
  end
75
144
 
145
+ protected
146
+
76
147
  # Authorize Payment
77
148
  def authorize_payment(order, payment_intent)
78
149
  response = post('/payments/authorize', params: authorize_payment_params(order, payment_intent))
@@ -117,19 +188,6 @@ module Effective
117
188
  response.reverse_merge(authorization)
118
189
  end
119
190
 
120
- def complete_payment_params(order, payment_intent)
121
- raise('expected an Effective::Order') unless order.kind_of?(Effective::Order)
122
-
123
- payment_id = extract_payment_id(payment_intent)
124
- amount = { amount: order.total_to_f, currency: currency }
125
-
126
- # Params passed into Complete Payment
127
- {
128
- paymentId: payment_id,
129
- amount: amount
130
- }
131
- end
132
-
133
191
  def authorize_payment_params(order, payment_intent)
134
192
  raise('expected an Effective::Order') unless order.kind_of?(Effective::Order)
135
193
 
@@ -183,6 +241,19 @@ module Effective
183
241
  }.compact
184
242
  end
185
243
 
244
+ def complete_payment_params(order, payment_intent)
245
+ raise('expected an Effective::Order') unless order.kind_of?(Effective::Order)
246
+
247
+ payment_id = extract_payment_id(payment_intent)
248
+ amount = { amount: order.total_to_f, currency: currency }
249
+
250
+ # Params passed into Complete Payment
251
+ {
252
+ paymentId: payment_id,
253
+ amount: amount
254
+ }
255
+ end
256
+
186
257
  def get(endpoint, params: nil)
187
258
  query = ('?' + params.compact.map { |k, v| "$#{k}=#{v}" }.join('&')) if params.present?
188
259
 
@@ -223,33 +294,6 @@ module Effective
223
294
  JSON.parse(result.body)
224
295
  end
225
296
 
226
- # Takes a payment_intent and returns the card info we can store
227
- def card_info(payment_intent)
228
- token = extract_token(payment_intent)
229
-
230
- # Return the authorization params merged with the card info
231
- last4 = token['maskedPan'].to_s.last(4)
232
- card = token['cardType'].to_s.downcase
233
- date = token['expDate']
234
- cvv = token['cvv']
235
-
236
- active_card = "**** **** **** #{last4} #{card} #{date}" if last4.present?
237
-
238
- { 'active_card' => active_card, 'card' => card, 'expDate' => date, 'cvv' => cvv }.compact
239
- end
240
-
241
- # Decode the base64 encoded JSON object into a Hash
242
- def decode_payment_intent_payload(payload)
243
- raise('expected a string') unless payload.kind_of?(String)
244
-
245
- payment_intent = (JSON.parse(Base64.decode64(payload)) rescue nil)
246
-
247
- raise('expected payment_intent to be a Hash') unless payment_intent.kind_of?(Hash)
248
- raise('expected a token payment') unless payment_intent['type'] == 'Token'
249
-
250
- payment_intent
251
- end
252
-
253
297
  private
254
298
 
255
299
  def headers
@@ -241,7 +241,9 @@ module Effective
241
241
  validates :payment_provider, presence: true
242
242
 
243
243
  validate do
244
- errors.add(:payment_provider, "unknown deferred payment provider") unless EffectiveOrders.deferred_providers.include?(payment_provider)
244
+ unless EffectiveOrders.deferred_providers.include?(payment_provider) || EffectiveOrders.delayed_providers.include?(payment_provider)
245
+ errors.add(:payment_provider, "unknown deferred payment provider")
246
+ end
245
247
  end
246
248
  end
247
249
 
@@ -546,13 +548,20 @@ module Effective
546
548
  return false unless delayed?
547
549
  return false unless deferred?
548
550
  return false unless delayed_payment_intent.present?
549
- return false if delayed_payment_date_future?
551
+ return false if delayed_payment_date_upcoming?
550
552
  return false if delayed_payment_purchase_ran_at.present? # We ran before and probably failed
551
553
 
552
554
  true
553
555
  end
554
556
 
555
- def delayed_payment_date_future?
557
+ def delayed_payment_info
558
+ return unless delayed? && deferred?
559
+ return unless delayed_payment_date_upcoming?
560
+
561
+ "Your #{delayed_payment_method} will be charged on #{delayed_payment_date.strftime('%F')} for the full amount of $#{'%0.2f' % total_to_f}"
562
+ end
563
+
564
+ def delayed_payment_date_upcoming?
556
565
  return false unless delayed?
557
566
  delayed_payment_date > Time.zone.now.to_date
558
567
  end
@@ -673,7 +682,9 @@ module Effective
673
682
 
674
683
  payment: payment_to_h(payment.presence || 'none'),
675
684
  payment_provider: (provider.presence || 'none'),
676
- payment_card: (card.presence || 'none')
685
+ payment_card: (card.presence || 'none'),
686
+
687
+ delayed_payment_intent: nil # Do not store the delayed payment intent any longer
677
688
  )
678
689
 
679
690
  if current_user&.email.present?
@@ -828,6 +839,15 @@ module Effective
828
839
  unvoided!(skip_buyer_validations: true)
829
840
  end
830
841
 
842
+ def deluxe_delayed_purchase!
843
+ raise('expected a delayed order') unless delayed?
844
+ raise('expected a deferred order') unless deferred?
845
+ raise('expected delayed payment intent') unless delayed_payment_intent.present?
846
+ raise('expected a deluxe_delayed payment provider') unless payment_provider == 'deluxe_delayed'
847
+
848
+ Effective::DeluxeApi.new().purchase_delayed_orders!(self)
849
+ end
850
+
831
851
  # These are all the emails we send all notifications to
832
852
  def emails
833
853
  ([purchased_by.try(:email)] + [email] + [user.try(:email)] + Array(organization.try(:billing_emails))).map(&:presence).compact.uniq
@@ -8,6 +8,6 @@
8
8
  %tr
9
9
  %td
10
10
  - if order.delayed?
11
- Your #{order.delayed_payment_method} will be charged on #{order.delayed_payment_date.strftime('%F')} for the full amount
11
+ Your #{order.delayed_payment_method} will be charged on #{order.delayed_payment_date.strftime('%F')}
12
12
  - else
13
13
  Waiting for payment by #{order.payment_provider}
@@ -8,18 +8,24 @@
8
8
 
9
9
  The payment date for this order
10
10
 
11
- - if order.delayed_payment_date_future?
11
+ - if order.delayed_payment_date_upcoming?
12
12
  is in #{distance} from now on #{order.delayed_payment_date.strftime('%F')}
13
13
  - elsif order.delayed_payment_date_today?
14
14
  was today
15
15
  - else
16
16
  was #{distance} ago on #{order.delayed_payment_date.strftime('%F')}
17
+
18
+ - provider_locals = { order: order, deferred_url: deferred_url, declined_url: declined_url }
17
19
 
18
- %p
19
- Instead of charging your card right away, the following action will securely save a token
20
- representing your card information. The full amount will be charged on the payment date.
20
+ - if order.deferred? && order.delayed?
21
+ %p Your existing #{order.delayed_payment_method} will be charged on the payment date.
21
22
 
22
- - provider_locals = { order: order, deferred_url: deferred_url, declined_url: declined_url }
23
+ = collapse('Change my card info') do
24
+ - EffectiveOrders.delayed_providers.each do |provider|
25
+ = render partial: "/effective/orders/#{provider}/form", locals: provider_locals
26
+ - else
27
+ %p
28
+ Please enter your card information so it can be charged on the payment date.
23
29
 
24
- - EffectiveOrders.delayed_providers.each do |provider|
25
- = render partial: "/effective/orders/#{provider}/form", locals: provider_locals
30
+ - EffectiveOrders.delayed_providers.each do |provider|
31
+ = render partial: "/effective/orders/#{provider}/form", locals: provider_locals
@@ -13,7 +13,7 @@
13
13
 
14
14
  The payment date for this order
15
15
 
16
- - if order.delayed_payment_date_future?
16
+ - if order.delayed_payment_date_upcoming?
17
17
  is in #{distance} from now on #{order.delayed_payment_date.strftime('%F')}
18
18
  - elsif order.delayed_payment_date_today?
19
19
  was today
@@ -1,3 +1,3 @@
1
1
  module EffectiveOrders
2
- VERSION = '6.12.2'.freeze
2
+ VERSION = '6.12.4'.freeze
3
3
  end
@@ -185,8 +185,9 @@ module EffectiveOrders
185
185
  ].compact
186
186
  end
187
187
 
188
+ # Should not include delayed providers
188
189
  def self.deferred_providers
189
- [('cheque' if cheque?), ('deluxe_delayed' if deluxe_delayed?), ('etransfer' if etransfer?), ('phone' if phone?)].compact
190
+ [('cheque' if cheque?), ('etransfer' if etransfer?), ('phone' if phone?)].compact
190
191
  end
191
192
 
192
193
  def self.delayed_providers
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.12.2
4
+ version: 6.12.4
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-05-30 00:00:00.000000000 Z
11
+ date: 2024-06-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails