flowcommerce_spree 0.0.3 → 0.0.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.
Files changed (28) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/current_zone_loader_decorator.rb +5 -6
  3. data/app/controllers/flowcommerce_spree/inventory_controller.rb +23 -0
  4. data/app/controllers/flowcommerce_spree/orders_controller.rb +18 -0
  5. data/app/controllers/users/sessions_controller_decorator.rb +19 -2
  6. data/app/helpers/spree/core/controller_helpers/flow_io_order_helper_decorator.rb +0 -16
  7. data/app/models/spree/address_decorator.rb +1 -1
  8. data/app/models/spree/calculator/flow_io.rb +1 -1
  9. data/app/models/spree/flow_io_credit_card_decorator.rb +21 -0
  10. data/app/models/spree/{order_decorator.rb → flow_io_order_decorator.rb} +31 -65
  11. data/app/models/spree/gateway/flow_io.rb +61 -24
  12. data/app/models/spree/{credit_card_decorator.rb → payment_capture_event_decorator.rb} +1 -1
  13. data/app/serializers/api/v2/order_serializer_decorator.rb +20 -0
  14. data/app/services/flowcommerce_spree/import_experience_items.rb +1 -1
  15. data/app/services/flowcommerce_spree/order_sync.rb +26 -155
  16. data/app/services/flowcommerce_spree/order_updater.rb +76 -0
  17. data/app/views/spree/admin/payments/source_views/_flow_io_gateway.html.erb +21 -0
  18. data/config/routes.rb +2 -0
  19. data/db/migrate/20201021755957_add_meta_to_spree_tables.rb +6 -4
  20. data/lib/flow/simple_gateway.rb +0 -36
  21. data/lib/flowcommerce_spree.rb +3 -1
  22. data/lib/flowcommerce_spree/engine.rb +1 -1
  23. data/lib/flowcommerce_spree/logging_http_client.rb +29 -13
  24. data/lib/flowcommerce_spree/session.rb +0 -18
  25. data/lib/flowcommerce_spree/version.rb +1 -1
  26. data/lib/flowcommerce_spree/webhook_service.rb +74 -104
  27. metadata +10 -19
  28. data/app/models/spree/line_item_decorator.rb +0 -15
@@ -9,9 +9,11 @@ require 'flowcommerce_spree/logging_http_handler'
9
9
  require 'flowcommerce_spree/webhook_service'
10
10
  require 'flowcommerce_spree/session'
11
11
  require 'flow/simple_gateway'
12
- require 'request_store'
13
12
 
14
13
  module FlowcommerceSpree
14
+ API_KEY = ENV.fetch('FLOW_TOKEN', 'test_key')
15
+ ENV['FLOW_TOKEN'] = API_KEY
16
+
15
17
  def self.client(logger: FlowcommerceSpree.logger, **opts)
16
18
  FlowCommerce.instance(http_handler: LoggingHttpHandler.new(logger: logger), **opts)
17
19
  end
@@ -24,7 +24,7 @@ module FlowcommerceSpree
24
24
 
25
25
  app.config.flowcommerce_spree[:mounted_path] = ENV.fetch('FLOW_MOUNT_PATH', '/flow')
26
26
 
27
- app.routes.append do
27
+ app.routes.prepend do
28
28
  mount FlowcommerceSpree::Engine => app.config.flowcommerce_spree[:mounted_path]
29
29
  end
30
30
  end
@@ -15,31 +15,47 @@ module FlowcommerceSpree
15
15
 
16
16
  start_time = Time.now.utc.round(10)
17
17
 
18
+ # Contrived example to show how client settings can be adjusted
18
19
  # if request.path.start_with?('/organizations')
19
- # Contrived example to show how client settings can be adjusted
20
- # client.open_timeout = 60
21
- # client.read_timeout = 60
20
+ # client.open_timeout = 60
21
+ # client.read_timeout = 60
22
22
  # end
23
23
 
24
24
  begin
25
25
  response = super
26
26
  rescue Io::Flow::V0::HttpClient::ServerError => e
27
- @error = { error: e }.to_json
27
+ @error = { error: Oj.load(e.body), code: e.code, status: e.details }
28
+ raise exception_to_raise(e), @error.dig(:error, 'messages')
28
29
  ensure
29
30
  # client.open_timeout = original_open
30
31
  # client.read_timeout = original_read
31
32
 
32
- duration = ((Time.now.utc.round(10) - start_time) * 1000).round(0)
33
+ log_request(request, response, start_time)
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def exception_to_raise(flow_io_exception)
40
+ resource = flow_io_exception.uri.split('/').last
41
+ exception = case resource
42
+ when 'reversals', 'refunds'
43
+ 'Spree::Core::GatewayError'
44
+ else
45
+ 'StandardError'
46
+ end
47
+ exception.constantize
48
+ end
33
49
 
34
- @logger.info(
35
- "Started #{request.method} #{request.path}\n"\
36
- "headers: #{request.instance_variable_get(:@header)}\nbody: #{request.body}\n"\
37
- "response: #{response&.force_encoding('utf-8')}\n"\
38
- "Completed #{request.method} #{request.path} #{duration} ms\n"
39
- )
50
+ def log_request(request, response, start_time)
51
+ duration = ((Time.now.utc.round(10) - start_time) * 1000).round(0)
40
52
 
41
- @logger.info "Error: #{e.inspect}" if e
42
- end
53
+ @logger.info("Started #{request.method} #{request.path}\n"\
54
+ "headers: #{request.instance_variable_get(:@header)}\nbody: #{request.body}\n"\
55
+ "response: #{response&.force_encoding('utf-8')}\n"\
56
+ "Completed #{request.method} #{request.path} #{duration} ms\n")
57
+
58
+ @logger.info "Error: #{@error.inspect}" if @error
43
59
  end
44
60
  end
45
61
  end
@@ -54,23 +54,5 @@ module FlowcommerceSpree
54
54
  def id
55
55
  @session.id
56
56
  end
57
-
58
- # because we do not get full experience from session, we have to get from exp list
59
- def delivered_duty_options
60
- return nil unless experience
61
-
62
- return unless (flow_experience = Flow::Experience.get(experience.key))
63
-
64
- Hashie::Mash.new(flow_experience.settings.delivered_duty.to_hash)
65
- end
66
-
67
- # if we have more than one choice, we show choice popup
68
- def offers_delivered_duty_choice?
69
- if (options = delivered_duty_options)
70
- options.available.length > 1
71
- else
72
- false
73
- end
74
- end
75
57
  end
76
58
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FlowcommerceSpree
4
- VERSION = '0.0.3'
4
+ VERSION = '0.0.4'
5
5
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FlowcommerceSpree
4
- # communicates with flow api, responds to webhook events
4
+ # responds to webhook events from flow.io
5
5
  class WebhookService
6
6
  attr_accessor :errors
7
7
  alias full_messages errors
@@ -17,30 +17,28 @@ module FlowcommerceSpree
17
17
  end
18
18
 
19
19
  def process
20
- discriminator = @data['discriminator']
21
- hook_method = "hook_#{discriminator}"
20
+ hook_method = @data['discriminator']
22
21
  # If hook processing method registered an error, a self.object of WebhookService with this error will be
23
22
  # returned, else an ActiveRecord object will be returned
24
23
  return __send__(hook_method) if respond_to?(hook_method, true)
25
24
 
26
- errors << { message: "No hook for #{discriminator}" }
25
+ errors << { message: "No hook for #{hook_method}" }
27
26
  self
28
27
  end
29
28
 
30
29
  private
31
30
 
32
- def hook_capture_upserted_v2
33
- capture = @data['capture']
31
+ def capture_upserted_v2
32
+ errors << { message: 'Capture param missing' } && (return self) unless (capture = @data['capture']&.to_hash)
33
+
34
34
  order_number = capture.dig('authorization', 'order', 'number')
35
35
  if (order = Spree::Order.find_by(number: order_number))
36
36
  order.flow_data['captures'] ||= []
37
37
  order_captures = order.flow_data['captures']
38
- order_captures.delete_if do |c|
39
- c['id'] == capture['id']
40
- end
38
+ order_captures.delete_if { |c| c['id'] == capture['id'] }
41
39
  order_captures << capture
42
-
43
40
  order.update_column(:meta, order.meta.to_json)
41
+ map_payment_captures_to_spree(order) if order.flow_io_payments.present?
44
42
  order
45
43
  else
46
44
  errors << { message: "Order #{order_number} not found" }
@@ -48,21 +46,39 @@ module FlowcommerceSpree
48
46
  end
49
47
  end
50
48
 
51
- def hook_experience_upserted_v2
52
- experience = @data['experience']
53
- Spree::Zones::Product.find_or_initialize_by(name: experience['key'].titleize).store_flow_io_data(experience)
54
- end
55
-
56
- def hook_fraud_status_changed
57
- if (order_number = @data.dig('order', 'number'))
58
- if @data['status'] == 'declined'
59
- if (order = Spree::Order.find_by(number: order_number))
60
- order.update_columns(fraudulent: true)
61
- order.cancel!
62
- return order
49
+ def card_authorization_upserted_v2
50
+ card_auth = @data['authorization']&.to_hash
51
+ errors << { message: 'Authorization param missing' } && (return self) unless card_auth
52
+
53
+ errors << { message: 'Card param missing' } && (return self) unless (flow_io_card = card_auth.delete('card'))
54
+
55
+ if (order_number = card_auth.dig('order', 'number'))
56
+ if (order = Spree::Order.find_by(number: order_number))
57
+ flow_io_card_expiration = flow_io_card.delete('expiration')
58
+
59
+ card = Spree::CreditCard.find_or_initialize_by(month: flow_io_card_expiration['month'].to_s,
60
+ year: flow_io_card_expiration['year'].to_s,
61
+ cc_type: flow_io_card.delete('type'),
62
+ last_digits: flow_io_card.delete('last4'),
63
+ name: flow_io_card.delete('name'),
64
+ user_id: order.user&.id)
65
+ card.flow_data ||= {}
66
+ card.flow_data.merge!(flow_io_card.except('discriminator')) if card.new_record?
67
+ card_auth['method'].delete('images')
68
+ card.push_authorization(card_auth.except('discriminator'))
69
+ if card.new_record?
70
+ card.imported = true
71
+ card.save!
63
72
  else
64
- errors << { message: "Order #{order_number} not found" }
73
+ card.update_column(:meta, card.meta.to_json)
65
74
  end
75
+
76
+ order.payments.where(response_code: card_auth['id'])
77
+ .update_all(source_id: card.id, source_type: 'Spree::CreditCard')
78
+
79
+ return card
80
+ else
81
+ errors << { message: "Order #{order_number} not found" }
66
82
  end
67
83
  else
68
84
  errors << { message: 'Order number param missing' }
@@ -71,7 +87,27 @@ module FlowcommerceSpree
71
87
  self
72
88
  end
73
89
 
74
- def hook_local_item_upserted
90
+ def experience_upserted_v2
91
+ experience = @data['experience']
92
+ Spree::Zones::Product.find_or_initialize_by(name: experience['key'].titleize).store_flow_io_data(experience)
93
+ end
94
+
95
+ def fraud_status_changed
96
+ order_number = @data.dig('order', 'number')
97
+ errors << { message: 'Order number param missing' } && (return self) unless order_number
98
+
99
+ order = Spree::Order.find_by(number: order_number)
100
+ errors << { message: "Order #{order_number} not found" } && (return self) unless order
101
+
102
+ if @data['status'] == 'declined'
103
+ order.update_columns(fraudulent: true)
104
+ order.cancel!
105
+ end
106
+
107
+ order
108
+ end
109
+
110
+ def local_item_upserted
75
111
  errors << { message: 'Local item param missing' } && (return self) unless (local_item = @data['local_item'])
76
112
 
77
113
  errors << { message: 'SKU param missing' } && (return self) unless (flow_sku = local_item.dig('item', 'number'))
@@ -91,94 +127,28 @@ module FlowcommerceSpree
91
127
  self
92
128
  end
93
129
 
94
- def hook_order_placed_v2
95
- order_placed = @data['order_placed']
96
- flow_order = order_placed['order']
97
- flow_allocation = order_placed['allocation']
130
+ def map_payment_captures_to_spree(order)
131
+ payments = order.flow_data&.dig('order', 'payments')
132
+ order.flow_data['captures']&.each do |c|
133
+ next unless c['status'] == 'succeeded'
98
134
 
99
- errors << { message: 'Order number param missing' } && (return self) unless (order_number = flow_order['number'])
135
+ auth = c.dig('authorization', 'id')
136
+ next unless payments&.find { |p| p['reference'] == auth }
100
137
 
101
- if (order = Spree::Order.find_by(number: order_number))
102
- order.flow_data['order'] = flow_order.to_hash
103
- order.flow_data['allocations'] = flow_allocation.to_hash
104
- order_flow_data = order.flow_data['order']
105
- attrs_to_update = { meta: order.meta.to_json }
106
- flow_data_submitted = order_flow_data['submitted_at'].present?
107
- if flow_data_submitted && !order.complete?
108
- if order_flow_data['payments'].present? && (order_flow_data.dig('balance', 'amount')&.to_i == 0)
109
- attrs_to_update[:state] = 'complete'
110
- attrs_to_update[:payment_state] = 'paid'
111
- attrs_to_update[:completed_at] = Time.zone.now.utc
112
- attrs_to_update[:email] = order.flow_customer_email
113
- else
114
- attrs_to_update[:state] = 'confirmed'
115
- end
116
- end
138
+ next unless (payment = Spree::Payment.find_by(response_code: auth))
117
139
 
118
- attrs_to_update.merge!(order.prepare_flow_addresses) if order.complete? || attrs_to_update[:state] == 'complete'
140
+ next if Spree::PaymentCaptureEvent.where("meta -> 'flow_data' ->> 'id' = ?", c['id']).exists?
119
141
 
120
- if flow_data_submitted
121
- order.create_proposed_shipments
122
- order.shipment.update_amounts
123
- order.line_items.each(&:store_ets)
124
- end
142
+ payment.capture_events.create!(amount: c['amount'], meta: { 'flow_data' => { 'id' => c['id'] } })
143
+ return if payment.completed? || payment.capture_events.sum(:amount) < payment.amount
125
144
 
126
- order.update_columns(attrs_to_update)
127
-
128
- # TODO: To be refactored once we have the capture_upserted_v2 webhook configured
129
- if flow_data_submitted
130
- order.create_tax_charge!
131
- order.finalize!
132
- order.update_totals
133
- order.save
134
- end
135
-
136
- return order
137
- else
138
- errors << { message: "Order #{order_number} not found" }
145
+ payment.complete
139
146
  end
140
147
 
141
- self
142
- end
143
-
144
- def hook_order_upserted_v2
145
- errors << { message: 'Order param missing' } && (return self) unless (flow_order = @data['order'])
146
-
147
- errors << { message: 'Order number param missing' } && (return self) unless (order_number = flow_order['number'])
148
-
149
- if (order = Spree::Order.find_by(number: order_number))
150
- order.flow_data['order'] = flow_order.to_hash
151
- order_flow_data = order.flow_data['order']
152
- attrs_to_update = { meta: order.meta.to_json }
153
- flow_data_submitted = order_flow_data['submitted_at'].present?
154
- if flow_data_submitted && !order.complete?
155
- if order_flow_data['payments'].present? && (order_flow_data.dig('balance', 'amount')&.to_i == 0)
156
- attrs_to_update[:state] = 'complete'
157
- attrs_to_update[:payment_state] = 'paid'
158
- attrs_to_update[:completed_at] = Time.zone.now.utc
159
- attrs_to_update[:email] = order.flow_customer_email
160
- else
161
- attrs_to_update[:state] = 'confirmed'
162
- end
163
- end
164
-
165
- attrs_to_update.merge!(order.prepare_flow_addresses) if order.complete? || attrs_to_update[:state] == 'complete'
166
-
167
- order.update_columns(attrs_to_update)
168
- order.create_tax_charge! if flow_data_submitted
169
- return order
170
- else
171
- errors << { message: "Order #{order_number} not found" }
172
- end
173
-
174
- self
175
- end
176
-
177
- # send en email when order is refunded
178
- def hook_refund_upserted_v2
179
- Spree::OrderMailer.refund_complete_email(@data).deliver
148
+ return if order.completed?
149
+ return unless order.flow_io_captures_sum >= order.flow_io_total_amount && order.flow_io_balance_amount <= 0
180
150
 
181
- 'Email delivered'
151
+ FlowcommerceSpree::OrderUpdater.new(order: order).finalize_order
182
152
  end
183
153
  end
184
154
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flowcommerce_spree
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aurel Branzeanu
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2021-02-22 00:00:00.000000000 Z
12
+ date: 2021-03-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: colorize
@@ -101,20 +101,6 @@ dependencies:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
103
  version: '0.21'
104
- - !ruby/object:Gem::Dependency
105
- name: request_store
106
- requirement: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - ">="
109
- - !ruby/object:Gem::Version
110
- version: '0'
111
- type: :runtime
112
- prerelease: false
113
- version_requirements: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - ">="
116
- - !ruby/object:Gem::Version
117
- version: '0'
118
104
  - !ruby/object:Gem::Dependency
119
105
  name: spree_backend
120
106
  requirement: !ruby/object:Gem::Requirement
@@ -159,6 +145,8 @@ files:
159
145
  - app/assets/javascripts/flowcommerce_spree/application.js
160
146
  - app/assets/stylesheets/flowcommerce_spree/application.css
161
147
  - app/controllers/concerns/current_zone_loader_decorator.rb
148
+ - app/controllers/flowcommerce_spree/inventory_controller.rb
149
+ - app/controllers/flowcommerce_spree/orders_controller.rb
162
150
  - app/controllers/flowcommerce_spree/webhooks_controller.rb
163
151
  - app/controllers/users/sessions_controller_decorator.rb
164
152
  - app/helpers/flowcommerce_spree/application_helper.rb
@@ -169,12 +157,12 @@ files:
169
157
  - app/models/spree/address_decorator.rb
170
158
  - app/models/spree/calculator/flow_io.rb
171
159
  - app/models/spree/calculator/shipping/flow_io.rb
172
- - app/models/spree/credit_card_decorator.rb
160
+ - app/models/spree/flow_io_credit_card_decorator.rb
161
+ - app/models/spree/flow_io_order_decorator.rb
173
162
  - app/models/spree/flow_io_product_decorator.rb
174
163
  - app/models/spree/flow_io_variant_decorator.rb
175
164
  - app/models/spree/gateway/flow_io.rb
176
- - app/models/spree/line_item_decorator.rb
177
- - app/models/spree/order_decorator.rb
165
+ - app/models/spree/payment_capture_event_decorator.rb
178
166
  - app/models/spree/promotion_decorator.rb
179
167
  - app/models/spree/promotion_handler/coupon_decorator.rb
180
168
  - app/models/spree/spree_user_decorator.rb
@@ -182,11 +170,14 @@ files:
182
170
  - app/models/spree/zone_decorator.rb
183
171
  - app/models/spree/zones/flow_io_product_zone_decorator.rb
184
172
  - app/models/tracking/setup_decorator.rb
173
+ - app/serializers/api/v2/order_serializer_decorator.rb
185
174
  - app/services/flowcommerce_spree/import_experience_items.rb
186
175
  - app/services/flowcommerce_spree/import_experiences.rb
187
176
  - app/services/flowcommerce_spree/order_sync.rb
177
+ - app/services/flowcommerce_spree/order_updater.rb
188
178
  - app/views/layouts/flowcommerce_spree/application.html.erb
189
179
  - app/views/spree/admin/payments/index.html.erb
180
+ - app/views/spree/admin/payments/source_views/_flow_io_gateway.html.erb
190
181
  - app/views/spree/admin/promotions/edit.html.erb
191
182
  - app/views/spree/admin/shared/_order_summary.html.erb
192
183
  - app/views/spree/admin/shared/_order_summary_flow.html.erb
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Flow (2017)
4
- # Enable this modifications if you want to display flow localized line item
5
- # Example: https://i.imgur.com/7v2ix2G.png
6
- module Spree
7
- LineItem.class_eval do
8
- # admin show line item price
9
- def single_money
10
- price = display_price.to_s
11
- price += " (#{order.flow_line_item_price(self)})" if order.flow_order
12
- price
13
- end
14
- end
15
- end