flowcommerce_spree 0.0.3 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +35 -6
  3. data/app/controllers/concerns/current_zone_loader_decorator.rb +11 -17
  4. data/app/controllers/flowcommerce_spree/inventory_controller.rb +23 -0
  5. data/app/controllers/flowcommerce_spree/orders_controller.rb +20 -0
  6. data/app/controllers/flowcommerce_spree/webhooks_controller.rb +16 -18
  7. data/app/controllers/users/sessions_controller_decorator.rb +19 -2
  8. data/app/helpers/spree/core/controller_helpers/flow_io_order_helper_decorator.rb +0 -16
  9. data/app/models/flowcommerce_spree/settings.rb +1 -0
  10. data/app/models/spree/address_decorator.rb +1 -1
  11. data/app/models/spree/calculator/flow_io.rb +24 -12
  12. data/app/models/spree/calculator/shipping/flow_io.rb +5 -2
  13. data/app/models/spree/flow_io_credit_card_decorator.rb +21 -0
  14. data/app/models/spree/flow_io_order_decorator.rb +181 -0
  15. data/app/models/spree/flow_io_product_decorator.rb +5 -0
  16. data/app/models/spree/flow_io_variant_decorator.rb +16 -6
  17. data/app/models/spree/gateway/flow_io.rb +61 -24
  18. data/app/models/spree/{credit_card_decorator.rb → payment_capture_event_decorator.rb} +1 -1
  19. data/app/models/spree/zones/flow_io_product_zone_decorator.rb +4 -0
  20. data/app/overrides/spree/admin/order_sidebar_summary_flow_link.rb +13 -0
  21. data/app/overrides/spree/admin/products/order_price_flow_message.rb +9 -0
  22. data/app/serializers/api/v2/order_serializer_decorator.rb +20 -0
  23. data/app/services/flowcommerce_spree/import_experience_items.rb +1 -21
  24. data/app/services/flowcommerce_spree/import_item.rb +45 -0
  25. data/app/services/flowcommerce_spree/order_sync.rb +50 -222
  26. data/app/services/flowcommerce_spree/order_updater.rb +78 -0
  27. data/app/services/flowcommerce_spree/webhooks/capture_upserted_v2.rb +76 -0
  28. data/app/services/flowcommerce_spree/webhooks/card_authorization_upserted_v2.rb +66 -0
  29. data/app/services/flowcommerce_spree/webhooks/experience_upserted_v2.rb +25 -0
  30. data/app/services/flowcommerce_spree/webhooks/fraud_status_changed.rb +35 -0
  31. data/app/services/flowcommerce_spree/webhooks/local_item_upserted.rb +40 -0
  32. data/app/views/spree/admin/payments/source_views/_flow_io_gateway.html.erb +21 -0
  33. data/app/workers/flowcommerce_spree/import_item_worker.rb +24 -0
  34. data/config/routes.rb +3 -1
  35. data/db/migrate/20201021755957_add_meta_to_spree_tables.rb +6 -4
  36. data/lib/flow/simple_gateway.rb +0 -36
  37. data/lib/flowcommerce_spree.rb +6 -2
  38. data/lib/flowcommerce_spree/engine.rb +6 -1
  39. data/lib/flowcommerce_spree/experience_service.rb +1 -27
  40. data/lib/flowcommerce_spree/logging_http_client.rb +29 -13
  41. data/lib/flowcommerce_spree/session.rb +5 -25
  42. data/lib/flowcommerce_spree/version.rb +1 -1
  43. data/lib/tasks/flowcommerce_spree.rake +4 -1
  44. metadata +77 -28
  45. data/app/mailers/spree/spree_order_mailer_decorator.rb +0 -24
  46. data/app/models/spree/line_item_decorator.rb +0 -15
  47. data/app/models/spree/order_decorator.rb +0 -244
  48. data/app/views/spree/order_mailer/confirm_email.html.erb +0 -86
  49. data/app/views/spree/order_mailer/confirm_email.text.erb +0 -38
  50. data/lib/flow/error.rb +0 -73
  51. data/lib/flow/pay_pal.rb +0 -25
  52. data/lib/flowcommerce_spree/webhook_service.rb +0 -184
  53. data/lib/simple_csv_writer.rb +0 -44
@@ -7,6 +7,7 @@ module Spree
7
7
  base.serialize :meta, ActiveRecord::Coders::JSON.new(symbolize_keys: true)
8
8
 
9
9
  base.store_accessor :meta, :flow_data, :zone_ids
10
+ base.after_save :sync_variants_with_flow
10
11
  end
11
12
 
12
13
  def price_in_zone(currency, product_zone)
@@ -86,6 +87,10 @@ module Spree
86
87
  prices
87
88
  end
88
89
 
90
+ def sync_variants_with_flow
91
+ variants_including_master.each(&:sync_product_to_flow)
92
+ end
93
+
89
94
  Spree::Product.prepend(self) if Spree::Product.included_modules.exclude?(self)
90
95
  end
91
96
  end
@@ -49,15 +49,23 @@ module Spree
49
49
  product.update_columns(meta: product.meta.to_json)
50
50
  end
51
51
 
52
+ def sync_flow_info?
53
+ if FlowcommerceSpree::API_KEY.blank? || FlowcommerceSpree::API_KEY == 'test_key'
54
+ return { error: 'Api Keys not configured' }
55
+ end
56
+ return { error: 'Price is 0' } if price == 0
57
+ return { error: 'Country of Origin is empty.' } unless country_of_origin
58
+ end
59
+
52
60
  # upload product variant to Flow's Product Catalog
53
61
  def sync_product_to_flow
54
- # initial Spree seed will fail, so skip unless we have Flow data field
55
- return unless respond_to?(:flow_data)
62
+ error = sync_flow_info?
63
+ return error if error.present?
56
64
 
57
- return if FlowcommerceSpree::API_KEY.blank? || FlowcommerceSpree::API_KEY == 'test_key'
58
-
59
- return { error: 'Price is 0' } if price == 0
65
+ update_flow_data
66
+ end
60
67
 
68
+ def update_flow_data
61
69
  additional_attrs = {}
62
70
  attr_name = nil
63
71
  export_required = false
@@ -82,7 +90,7 @@ module Spree
82
90
  flow_item_sh1 = Digest::SHA1.hexdigest(flow_item.to_json)
83
91
 
84
92
  # skip if sync not needed
85
- return nil if flow_data&.[](:last_sync_sh1) == flow_item_sh1
93
+ return { error: 'Synchronization not needed' } if flow_data&.[](:last_sync_sh1) == flow_item_sh1
86
94
 
87
95
  response = FlowcommerceSpree.client.items.put_by_number(FlowcommerceSpree::ORGANIZATION, sku, flow_item)
88
96
  self.flow_data ||= {}
@@ -91,6 +99,8 @@ module Spree
91
99
  # after successful put, write cache
92
100
  update_column(:meta, meta.to_json)
93
101
 
102
+ FlowcommerceSpree::ImportItemWorker.perform_async(sku)
103
+
94
104
  response
95
105
  rescue Net::OpenTimeout => e
96
106
  { error: e.message }
@@ -5,6 +5,8 @@
5
5
  module Spree
6
6
  class Gateway
7
7
  class FlowIo < Gateway
8
+ REFUND_SUCCESS = 'succeeded'
9
+
8
10
  def provider_class
9
11
  self.class
10
12
  end
@@ -23,7 +25,7 @@ module Spree
23
25
  end
24
26
 
25
27
  def method_type
26
- 'gateway'
28
+ 'flow_io_gateway'
27
29
  end
28
30
 
29
31
  def preferences
@@ -40,34 +42,29 @@ module Spree
40
42
  order.cc_authorization
41
43
  end
42
44
 
43
- def capture(_amount, _payment_method, options = {})
44
- order = load_order options
45
- order.cc_capture
46
- end
47
-
48
- def purchase(_amount, _payment_method, options = {})
49
- order = load_order options
50
- flow_auth = order.cc_authorization
51
-
52
- if flow_auth.success?
53
- order.cc_capture
54
- else
55
- flow_auth
56
- end
45
+ def refund(payment, amount, _options = {})
46
+ request_refund_store_result(payment.order, amount)
47
+ rescue StandardError => e
48
+ ActiveMerchant::Billing::Response.new(false, e.to_s, {}, {})
57
49
  end
58
50
 
59
- def refund(_money, _authorization_key, options = {})
60
- order = load_order options
61
- order.cc_refund
51
+ def cancel(authorization)
52
+ original_payment = Spree::Payment.find_by(response_code: authorization)
53
+ request_refund_store_result(original_payment.order, original_payment.amount)
54
+ rescue StandardError => e
55
+ ActiveMerchant::Billing::Response.new(false, e.to_s, {}, {})
62
56
  end
63
57
 
64
- def void(money, authorization_key, options = {})
65
- # binding.pry
58
+ def void(authorization_id, _source, options = {})
59
+ amount = (options[:subtotal] + options[:shipping]) * 0.01
60
+ reversal_form = Io::Flow::V0::Models::ReversalForm.new(key: options[:order_id],
61
+ authorization_id: authorization_id,
62
+ amount: amount,
63
+ currency: options[:currency])
64
+ FlowcommerceSpree.client.reversals.post(FlowcommerceSpree::ORGANIZATION, reversal_form)
66
65
  end
67
66
 
68
67
  def create_profile(payment)
69
- # binding.pry
70
-
71
68
  # payment.order.state
72
69
  @credit_card = payment.source
73
70
 
@@ -77,12 +74,52 @@ module Spree
77
74
 
78
75
  private
79
76
 
77
+ def request_refund_store_result(order, amount)
78
+ refund_form = Io::Flow::V0::Models::RefundForm.new(order_number: order.number,
79
+ amount: amount,
80
+ currency: order.currency)
81
+ response = FlowcommerceSpree.client.refunds.post(FlowcommerceSpree::ORGANIZATION, refund_form)
82
+ response_status = response.status.value
83
+ if response_status == REFUND_SUCCESS
84
+ add_refund_to_order(response, order)
85
+ map_refund_to_payment(response, order)
86
+ ActiveMerchant::Billing::Response.new(true, REFUND_SUCCESS, {}, {})
87
+ else
88
+ msg = "Partial refund fail. Details: #{response_status}"
89
+ ActiveMerchant::Billing::Response.new(false, msg, {}, {})
90
+ end
91
+ end
92
+
93
+ def add_refund_to_order(response, order)
94
+ order.flow_data ||= {}
95
+ order.flow_data['refunds'] ||= []
96
+ order_refunds = order.flow_data['refunds']
97
+ order_refunds.delete_if { |r| r['id'] == response.id }
98
+ order_refunds << response.to_hash
99
+ order.update_column(:meta, order.meta.to_json)
100
+ end
101
+
102
+ def map_refund_to_payment(response, order)
103
+ original_payment = Spree::Payment.find_by(response_code: response.authorization.id)
104
+ payment = order.payments.create!(state: 'completed',
105
+ response_code: response.authorization.id,
106
+ payment_method_id: original_payment&.payment_method_id,
107
+ amount: - response.amount,
108
+ source_id: original_payment&.source_id,
109
+ source_type: original_payment&.source_type)
110
+
111
+ # For now this additional update is overwriting the generated identifier with flow.io payment identifier.
112
+ # TODO: Check and possibly refactor in Spree 3.0, where the `before_create :set_unique_identifier`
113
+ # has been removed.
114
+ payment.update_column(:identifier, response.id)
115
+ end
116
+
80
117
  # hard inject Flow as payment method unless defined
81
118
  def profile_ensure_payment_method_is_present!
82
119
  return if @credit_card.payment_method_id
83
120
 
84
- flow_payment = Spree::PaymentMethod.where(active: true, type: 'Spree::Gateway::FlowIo').first
85
- @credit_card.payment_method_id = flow_payment.id if flow_payment
121
+ flow_payment_method = Spree::PaymentMethod.find_by(active: true, type: 'Spree::Gateway::FlowIo')
122
+ @credit_card.payment_method_id = flow_payment_method.id if flow_payment_method
86
123
  end
87
124
 
88
125
  # create payment profile with Flow and tokenize Credit Card
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Spree
4
- CreditCard.class_eval do
4
+ PaymentCaptureEvent.class_eval do
5
5
  serialize :meta, ActiveRecord::Coders::JSON.new(symbolize_keys: true)
6
6
 
7
7
  store_accessor :meta, :flow_data
@@ -28,6 +28,10 @@ module Spree
28
28
  flow_data&.[]('key').present? && flow_data['status'] == 'active'
29
29
  end
30
30
 
31
+ def flow_io_active_or_archiving_experience?
32
+ flow_data&.[]('key').present? && %w[active archiving].include?(flow_data['status'])
33
+ end
34
+
31
35
  def update_on_flow; end
32
36
 
33
37
  def remove_on_flow_io
@@ -0,0 +1,13 @@
1
+ Deface::Override.new(
2
+ virtual_path: 'spree/admin/shared/_order_tabs',
3
+ name: 'spree_admin_order_additional_information_flow_message',
4
+ insert_top: '.additional-info',
5
+ text: '
6
+ <% if FlowcommerceSpree::ORGANIZATION.present? && @order.flow_order.present? %>
7
+ <div style="text-align: center">
8
+ <%= link_to "See Flow Order",
9
+ "https://console.flow.io/#{FlowcommerceSpree::ORGANIZATION}/orders/#{@order.number}",
10
+ target: "_blank", class: "button" %>
11
+ </div>
12
+ <% end %>'
13
+ )
@@ -0,0 +1,9 @@
1
+ Deface::Override.new(
2
+ virtual_path: 'spree/admin/prices/index',
3
+ name: 'spree_admin_prices_flow_mesage',
4
+ insert_top: '.no-border-top',
5
+ text: "
6
+ <div class='spree-admin-info' >
7
+ To check localized pricing, please click <a href='#{"https://console.flow.io/#{ENV['FLOW_ORGANIZATION']}/price-books"}' target='_blank'>here</a>.
8
+ </div>"
9
+ )
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ Api::V2::OrderSerializer.class_eval do
4
+ attribute :duty_included, if: proc { object.flow_io_attributes.present? }
5
+ attribute :vat_included, if: proc { object.flow_io_attributes.present? }
6
+
7
+ def duty_included
8
+ flow_io_order_attributes&.[]('duty') == 'included'
9
+ end
10
+
11
+ def vat_included
12
+ flow_io_order_attributes&.[]('vat') == 'included'
13
+ end
14
+
15
+ private
16
+
17
+ def flow_io_order_attributes
18
+ @flow_io_order_attributes ||= Oj.load(object.flow_io_attributes['pricing_key'])
19
+ end
20
+ end
@@ -13,7 +13,7 @@ module FlowcommerceSpree
13
13
  items = []
14
14
  total = 0
15
15
 
16
- while offset == 0 || items.length == 100
16
+ while offset == 0 || items.length != 0
17
17
  # show current list size
18
18
  @logger.info "\nGetting items: #{@experience_key.green}, rows #{offset} - #{offset + page_size}"
19
19
 
@@ -33,14 +33,6 @@ module FlowcommerceSpree
33
33
  item_hash = item.to_hash
34
34
  next unless (variant = Spree::Variant.find_by(sku: item_hash.delete(:number)))
35
35
 
36
- status_in_experience = item_hash.dig(:local, :status)
37
-
38
- if status_in_experience != 'included'
39
- log_str << "[#{status_in_experience.red}]:"
40
- else # If at least a variant is included in experience, include the product too
41
- adjust_product_zone(variant)
42
- end
43
-
44
36
  variant.flow_import_item(item_hash, experience_key: @experience_key)
45
37
 
46
38
  log_str << "#{variant.sku}, "
@@ -60,17 +52,5 @@ module FlowcommerceSpree
60
52
  @organization = organization
61
53
  @zone = zone
62
54
  end
63
-
64
- def adjust_product_zone(variant)
65
- return unless (product = variant.product)
66
-
67
- zone_ids = product.zone_ids || []
68
- zone_id_string = @zone.id.to_s
69
- return if zone_ids.include?(zone_id_string)
70
-
71
- zone_ids << zone_id_string
72
- product.zone_ids = zone_ids
73
- product.update_columns(meta: product.meta.to_json)
74
- end
75
55
  end
76
56
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlowcommerceSpree
4
+ # A service object to import the data for product variants belonging to a flow.io Experience
5
+ class ImportItem
6
+ def self.run(variant, client: FlowcommerceSpree.client, organization: ORGANIZATION)
7
+ new(variant, client: client, organization: organization).run
8
+ end
9
+
10
+ def run
11
+ @client.experiences.get(@organization, status: 'active').each do |experience|
12
+ experience_key = experience.key
13
+ zone = Spree::Zones::Product.find_by(name: experience_key.titleize)
14
+ next unless zone
15
+
16
+ import_data(zone)
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def initialize(variant, client:, organization:)
23
+ @client = client
24
+ @logger = client.instance_variable_get(:@http_handler).logger
25
+ @organization = organization
26
+ @variant = variant
27
+ end
28
+
29
+ def import_data(zone)
30
+ experience_key = zone.flow_io_experience
31
+ item = begin
32
+ @client.experiences.get_items_by_number(@organization, @variant.sku, experience: experience_key)
33
+ rescue Io::Flow::V0::HttpClient::PreconditionException, Io::Flow::V0::HttpClient::ServerError => e
34
+ @logger.info "flow.io API error: #{e.message}"
35
+ end
36
+ return unless item
37
+
38
+ item_hash = item.to_hash
39
+
40
+ @variant.flow_import_item(item_hash, experience_key: @experience_key)
41
+
42
+ @logger.info "[#{@variant.sku}][#{experience_key}] Variant experience imported successfully."
43
+ end
44
+ end
45
+ end
@@ -2,108 +2,39 @@
2
2
 
3
3
  module FlowcommerceSpree
4
4
  # represents flow.io order syncing service
5
- # for easy integration we are currently passing:
6
- # - flow experience
7
- # - spree order
8
- # - current customer, if present as @order.user
9
- #
10
- # example:
11
- # flow_order = FlowcommerceSpree::OrderSync.new # init flow-order object
12
- # order: Spree::Order.last,
13
- # experience: @flow_session.experience
14
- # customer: @order.user
15
- # flow_order.build_flow_request # builds json body to be posted to flow.io api
16
- # flow_order.synchronize! # sends order to flow
17
- class OrderSync # rubocop:disable Metrics/ClassLength
5
+ class OrderSync
18
6
  FLOW_CENTER = 'default'
19
- SESSION_EXPIRATION_THRESHOLD = 10 # Refresh session if less than 10 seconds to session expiration remains
20
7
 
21
- attr_reader :digest, :order, :response
8
+ attr_reader :order, :response
22
9
 
23
- delegate :url_helpers, to: 'Rails.application.routes'
24
-
25
- class << self
26
- def clear_cache(order)
27
- return unless order.flow_data['order']
28
-
29
- order.flow_data.delete('order')
30
- order.update_column :meta, order.meta.to_json
31
- end
32
- end
33
-
34
- def initialize(order:)
35
- raise(ArgumentError, 'Experience not defined or not active') unless order.zone&.flow_io_active_experience?
10
+ # @param [Object] order
11
+ # @param [String] flow_session_id
12
+ def initialize(order:, flow_session_id:)
13
+ raise(ArgumentError, 'Experience not defined or not active') unless order&.zone&.flow_io_active_experience?
36
14
 
37
15
  @experience = order.flow_io_experience_key
16
+ @flow_session_id = flow_session_id
38
17
  @order = order
39
- @client = FlowcommerceSpree.client(session_id: fetch_session_id)
18
+ @client = FlowcommerceSpree.client(default_headers: { "Authorization": "Session #{flow_session_id}" },
19
+ authorization: nil)
40
20
  end
41
21
 
42
22
  # helper method to send complete order from Spree to flow.io
43
23
  def synchronize!
44
- sync_body!
45
- check_state!
46
- write_response_in_cache
47
-
48
- # This is for 1st order syncing, when no checkout_token has been fetched yet. In all the subsequent syncs,
49
- # the checkout_token is fetched in the `fetch_session_id` method, calling the refresh_checkout_token method when
50
- # necessary.
51
- refresh_checkout_token if @order.flow_io_checkout_token.blank?
52
- @order.update_column(:meta, @order.meta.to_json)
53
- @response
54
- end
24
+ return unless @order.state == 'cart' && @order.line_items.size > 0
55
25
 
56
- def error
57
- @response['messages'].join(', ')
58
- end
26
+ sync_body!
27
+ write_response_to_order
59
28
 
60
- def error_code
61
- @response['code']
29
+ @order.update_columns(total: @order.total, meta: @order.meta.to_json)
30
+ refresh_checkout_token
62
31
  end
63
32
 
64
33
  def error?
65
34
  @response&.[]('code') && @response&.[]('messages') ? true : false
66
35
  end
67
36
 
68
- def delivery
69
- deliveries.select { |el| el[:active] }.first
70
- end
71
-
72
- # delivery methods are defined in flow console
73
- def deliveries
74
- # if we have error with an order, but still using this method
75
- return [] unless @order.flow_order
76
-
77
- @order.flow_data ||= {}
78
-
79
- delivery_list = @order.flow_order['deliveries'][0]['options'].map do |opts|
80
- name = opts['tier']['name']
81
-
82
- # add original Flow ID
83
- # name += ' (%s)' % opts['tier']['strategy'] if opts['tier']['strategy']
84
-
85
- selection_id = opts['id']
86
-
87
- { id: selection_id,
88
- price: { label: opts['price']['label'] },
89
- active: @order.flow_order['selections'].include?(selection_id),
90
- name: name }
91
- end.to_a
92
-
93
- # make first one active unless we have active element
94
- delivery_list.first[:active] = true unless delivery_list.select { |el| el[:active] }.first
95
-
96
- delivery_list
97
- end
98
-
99
- def total_price
100
- @order.flow_total
101
- end
102
-
103
- def delivered_duty
104
- # paid is default
105
- @order.flow_data['delivered_duty'] || ::Io::Flow::V0::Models::DeliveredDuty.paid.value
106
- end
37
+ private
107
38
 
108
39
  # builds object that can be sent to api.flow.io to sync order data
109
40
  def build_flow_request
@@ -112,97 +43,31 @@ module FlowcommerceSpree
112
43
 
113
44
  try_to_add_customer
114
45
 
115
- if (flow_data = @order.flow_data['order'])
116
- @body[:selections] = flow_data['selections'].presence
117
- @body[:delivered_duty] = flow_data['delivered_duty'].presence
118
- @body[:attributes] = flow_data['attributes'].presence
119
-
120
- if @order.adjustment_total != 0
121
- # discount on full order is applied
122
- @body[:discount] = { amount: @order.adjustment_total, currency: @order.currency }
123
- end
124
- end
125
-
126
- # calculate digest body and cache it
127
- @digest = Digest::SHA1.hexdigest(@opts.to_json + @body.to_json)
128
- end
129
-
130
- private
131
-
132
- # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
133
- def fetch_session_id
134
- session = RequestStore.store[:session]
135
- current_session_id = session&.[]('_f60_session')
136
- session_expire_at = session&.[]('_f60_expires_at')&.to_datetime
137
- session_expired = flow_io_session_expired?(session_expire_at.to_i)
138
- order_flow_session_id = @order.flow_data['session_id']
139
- order_session_expire_at = @order.flow_io_session_expires_at
140
- order_session_expired = flow_io_session_expired?(order_session_expire_at.to_i)
141
-
142
- if order_flow_session_id == current_session_id && session_expire_at == order_session_expire_at &&
143
- @order.flow_io_checkout_token.present? && session_expired == false
144
- return current_session_id
145
- elsif current_session_id && session_expire_at && session_expired == false
146
- # If request flow_session is not expired, don't refresh the flow_session (i.e., don't mark the refresh_session
147
- # lvar as true), just store the flow_session data into the order, if it is new, and refresh the checkout_token
148
- refresh_session = nil
149
- elsif order_flow_session_id && order_session_expire_at && order_session_expired == false && session_expired.nil?
150
- refresh_checkout_token if @order.flow_io_order_id && @order.flow_io_checkout_token.blank?
151
- return order_flow_session_id
152
- else
153
- refresh_session = true
154
- end
155
-
156
- if refresh_session
157
- flow_io_session = Session.new(
158
- ip: '127.0.0.1',
159
- visitor: "session-#{Digest::SHA1.hexdigest(@order.guest_token)}",
160
- experience: @experience
161
- )
162
- flow_io_session.create
163
- current_session_id = flow_io_session.id
164
- session_expire_at = flow_io_session.expires_at.to_s
165
- end
166
-
167
- @order.flow_data['session_id'] = current_session_id
168
- @order.flow_data['session_expires_at'] = session_expire_at
169
-
170
- if session.respond_to?(:[])
171
- session['_f60_session'] = current_session_id
172
- session['_f60_expires_at'] = session_expire_at
173
- end
174
-
175
- # On the 1st OrderSync at this moment the order is not yet created at flow.io, so we couldn't yet retrieve the
176
- # checkout_token. This is done after the order will be synced, in the `synchronize!` method.
177
- refresh_checkout_token if @order.flow_io_order_id
178
-
179
- current_session_id
180
- end
181
- # rubocop:enable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
46
+ return unless (flow_data = @order.flow_data['order'])
182
47
 
183
- def flow_io_session_expired?(expiration_time)
184
- return nil if expiration_time == 0
48
+ @body[:selections] = flow_data['selections'].presence
49
+ @body[:delivered_duty] = flow_data['delivered_duty'].presence
50
+ @body[:attributes] = flow_data['attributes'].presence
185
51
 
186
- expiration_time - Time.zone.now.utc.to_i < SESSION_EXPIRATION_THRESHOLD
52
+ # discount on full order is applied
53
+ @body[:discount] = { amount: @order.adjustment_total, currency: @order.currency } if @order.adjustment_total != 0
187
54
  end
188
55
 
189
56
  def refresh_checkout_token
190
- root_url = url_helpers.root_url
57
+ root_url = Rails.application.routes.url_helpers.root_url
191
58
  order_number = @order.number
192
- confirmation_url = "#{root_url}thankyou?order=#{order_number}&t=#{@order.guest_token}"
193
- checkout_token = FlowcommerceSpree.client.checkout_tokens.post_checkout_and_tokens_by_organization(
194
- FlowcommerceSpree::ORGANIZATION,
195
- discriminator: 'checkout_token_reference_form',
196
- order_number: order_number,
197
- session_id: @order.flow_data['session_id'],
198
- urls: { continue_shopping: root_url,
199
- confirmation: confirmation_url,
200
- invalid_checkout: root_url }
201
- )
202
- @order.add_flow_checkout_token(checkout_token.id)
203
-
59
+ confirmation_url = "#{root_url}flow/order-completed?order=#{order_number}&t=#{@order.guest_token}"
204
60
  @order.flow_io_attribute_add('flow_return_url', confirmation_url)
205
61
  @order.flow_io_attribute_add('checkout_continue_shopping_url', root_url)
62
+
63
+ FlowcommerceSpree.client.checkout_tokens.post_checkout_and_tokens_by_organization(
64
+ FlowcommerceSpree::ORGANIZATION, discriminator: 'checkout_token_reference_form',
65
+ order_number: order_number,
66
+ session_id: @flow_session_id,
67
+ urls: { continue_shopping: root_url,
68
+ confirmation: confirmation_url,
69
+ invalid_checkout: root_url }
70
+ )&.id
206
71
  end
207
72
 
208
73
  # if customer is defined, add customer info
@@ -214,13 +79,14 @@ module FlowcommerceSpree
214
79
  customer_ship_address = customer.ship_address
215
80
  address = customer_ship_address if customer_ship_address&.country&.iso3 == @order.zone.flow_io_experience_country
216
81
 
82
+ customer_profile = customer.user_profile
217
83
  unless address
218
- user_profile_address = customer.user_profile&.address
84
+ user_profile_address = customer_profile&.address
219
85
  address = user_profile_address if user_profile_address&.country&.iso3 == @order.zone.flow_io_experience_country
220
86
  end
221
87
 
222
- @body[:customer] = { name: { first: address&.firstname,
223
- last: address&.lastname },
88
+ @body[:customer] = { name: { first: address&.firstname || customer_profile&.first_name,
89
+ last: address&.lastname || customer_profile&.last_name },
224
90
  email: customer.email,
225
91
  number: customer.flow_number,
226
92
  phone: address&.phone }
@@ -230,52 +96,24 @@ module FlowcommerceSpree
230
96
 
231
97
  def add_customer_address(address)
232
98
  streets = []
233
- streets.push address.address1 if address&.address1.present?
234
- streets.push address.address2 if address&.address2.present?
99
+ streets.push address.address1 if address.address1.present?
100
+ streets.push address.address2 if address.address2.present?
235
101
 
236
102
  @body[:destination] = { streets: streets,
237
- city: address&.city,
238
- province: address&.state_name,
239
- postal: address&.zipcode,
240
- country: (address&.country&.iso3 || ''),
103
+ city: address.city,
104
+ province: address.state_name,
105
+ postal: address.zipcode,
106
+ country: (address.country&.iso3 || ''),
241
107
  contact: @body[:customer] }
242
108
 
243
109
  @body[:destination].delete_if { |_k, v| v.nil? }
244
110
  end
245
111
 
246
112
  def sync_body!
247
- build_flow_request if @body.blank?
248
-
249
- @use_get = false
250
-
251
- # use get if order is completed and closed
252
- @use_get = true if @order.flow_data.dig('order', 'submitted_at').present? || @order.state == 'complete'
253
-
254
- # use get if local digest hash check said there is no change
255
- @use_get ||= true if @order.flow_data['digest'] == @digest
256
-
257
- # do not use get if there is no local order cache
258
- @use_get = false unless @order.flow_data['order']
259
-
260
- if @use_get
261
- @response ||= @client.orders.get_by_number(ORGANIZATION, @order.number).to_hash
262
- else
263
- @response = @client.orders.put_by_number(ORGANIZATION, @order.number,
264
- Io::Flow::V0::Models::OrderPutForm.new(@body), @opts).to_hash
265
- end
266
- end
113
+ build_flow_request
267
114
 
268
- def check_state!
269
- # authorize if not authorized
270
- # if !@order.flow_order_authorized?
271
-
272
- # authorize payment on complete, unless authorized
273
- if @order.state == 'complete' && !@order.flow_order_authorized?
274
- simple_gateway = Flow::SimpleGateway.new(@order)
275
- simple_gateway.cc_authorization
276
- end
277
-
278
- @order.flow_finalize! if @order.flow_order_authorized? && @order.state != 'complete'
115
+ @response = @client.orders.put_by_number(ORGANIZATION, @order.number,
116
+ Io::Flow::V0::Models::OrderPutForm.new(@body), @opts).to_hash
279
117
  end
280
118
 
281
119
  def add_item(line_item)
@@ -290,22 +128,12 @@ module FlowcommerceSpree
290
128
  currency: price_root['currency'] || variant.cost_currency } }
291
129
  end
292
130
 
293
- # set cache for total order amount
294
- # written in flow_data field inside spree_orders table
295
- def write_response_in_cache
296
- if !@response || error?
297
- @order.flow_data.delete('digest')
298
- @order.flow_data.delete('order')
299
- else
300
- response_total = @response.dig('total', 'label')
301
- cache_total = @order.flow_data.dig('order', 'total', 'label')
302
-
303
- # return if total is not changed, no products removed or added
304
- return if @use_get && response_total == cache_total
131
+ def write_response_to_order
132
+ return @order.flow_data.delete('order') if !@response || error?
305
133
 
306
- # update local order
307
- @order.flow_data.merge!('digest' => @digest, 'order' => @response)
308
- end
134
+ # update local order
135
+ @order.total = @response[:total]&.[](:amount)
136
+ @order.flow_data.merge!('order' => @response)
309
137
  end
310
138
  end
311
139
  end