flowcommerce_spree 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
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