flowcommerce_spree 0.0.3 → 0.0.8

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 (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