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.
- checksums.yaml +4 -4
- data/README.md +35 -6
- data/app/controllers/concerns/current_zone_loader_decorator.rb +11 -17
- data/app/controllers/flowcommerce_spree/inventory_controller.rb +23 -0
- data/app/controllers/flowcommerce_spree/orders_controller.rb +20 -0
- data/app/controllers/flowcommerce_spree/webhooks_controller.rb +16 -18
- data/app/controllers/users/sessions_controller_decorator.rb +19 -2
- data/app/helpers/spree/core/controller_helpers/flow_io_order_helper_decorator.rb +0 -16
- data/app/models/flowcommerce_spree/settings.rb +1 -0
- data/app/models/spree/address_decorator.rb +1 -1
- data/app/models/spree/calculator/flow_io.rb +24 -12
- data/app/models/spree/calculator/shipping/flow_io.rb +5 -2
- data/app/models/spree/flow_io_credit_card_decorator.rb +21 -0
- data/app/models/spree/flow_io_order_decorator.rb +181 -0
- data/app/models/spree/flow_io_product_decorator.rb +5 -0
- data/app/models/spree/flow_io_variant_decorator.rb +16 -6
- data/app/models/spree/gateway/flow_io.rb +61 -24
- data/app/models/spree/{credit_card_decorator.rb → payment_capture_event_decorator.rb} +1 -1
- data/app/models/spree/zones/flow_io_product_zone_decorator.rb +4 -0
- data/app/overrides/spree/admin/order_sidebar_summary_flow_link.rb +13 -0
- data/app/overrides/spree/admin/products/order_price_flow_message.rb +9 -0
- data/app/serializers/api/v2/order_serializer_decorator.rb +20 -0
- data/app/services/flowcommerce_spree/import_experience_items.rb +1 -21
- data/app/services/flowcommerce_spree/import_item.rb +45 -0
- data/app/services/flowcommerce_spree/order_sync.rb +50 -222
- data/app/services/flowcommerce_spree/order_updater.rb +78 -0
- data/app/services/flowcommerce_spree/webhooks/capture_upserted_v2.rb +76 -0
- data/app/services/flowcommerce_spree/webhooks/card_authorization_upserted_v2.rb +66 -0
- data/app/services/flowcommerce_spree/webhooks/experience_upserted_v2.rb +25 -0
- data/app/services/flowcommerce_spree/webhooks/fraud_status_changed.rb +35 -0
- data/app/services/flowcommerce_spree/webhooks/local_item_upserted.rb +40 -0
- data/app/views/spree/admin/payments/source_views/_flow_io_gateway.html.erb +21 -0
- data/app/workers/flowcommerce_spree/import_item_worker.rb +24 -0
- data/config/routes.rb +3 -1
- data/db/migrate/20201021755957_add_meta_to_spree_tables.rb +6 -4
- data/lib/flow/simple_gateway.rb +0 -36
- data/lib/flowcommerce_spree.rb +6 -2
- data/lib/flowcommerce_spree/engine.rb +6 -1
- data/lib/flowcommerce_spree/experience_service.rb +1 -27
- data/lib/flowcommerce_spree/logging_http_client.rb +29 -13
- data/lib/flowcommerce_spree/session.rb +5 -25
- data/lib/flowcommerce_spree/version.rb +1 -1
- data/lib/tasks/flowcommerce_spree.rake +4 -1
- metadata +77 -28
- data/app/mailers/spree/spree_order_mailer_decorator.rb +0 -24
- data/app/models/spree/line_item_decorator.rb +0 -15
- data/app/models/spree/order_decorator.rb +0 -244
- data/app/views/spree/order_mailer/confirm_email.html.erb +0 -86
- data/app/views/spree/order_mailer/confirm_email.text.erb +0 -38
- data/lib/flow/error.rb +0 -73
- data/lib/flow/pay_pal.rb +0 -25
- data/lib/flowcommerce_spree/webhook_service.rb +0 -184
- 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
|
-
|
55
|
-
return
|
62
|
+
error = sync_flow_info?
|
63
|
+
return error if error.present?
|
56
64
|
|
57
|
-
|
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
|
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
|
-
'
|
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
|
44
|
-
order
|
45
|
-
|
46
|
-
|
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
|
60
|
-
|
61
|
-
order.
|
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(
|
65
|
-
|
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
|
-
|
85
|
-
@credit_card.payment_method_id =
|
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
|
@@ -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
|
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
|
-
|
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 :
|
8
|
+
attr_reader :order, :response
|
22
9
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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(
|
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
|
-
|
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
|
-
|
57
|
-
|
58
|
-
end
|
26
|
+
sync_body!
|
27
|
+
write_response_to_order
|
59
28
|
|
60
|
-
|
61
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
184
|
-
|
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
|
-
|
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}
|
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 =
|
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
|
234
|
-
streets.push address.address2 if address
|
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
|
238
|
-
province: address
|
239
|
-
postal: address
|
240
|
-
country: (address
|
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
|
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
|
-
|
269
|
-
|
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
|
-
|
294
|
-
|
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
|
-
|
307
|
-
|
308
|
-
|
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
|