flowcommerce_spree 0.0.2 → 0.0.3

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.
@@ -14,15 +14,12 @@ module FlowcommerceSpree
14
14
  # original_read = client.read_timeout
15
15
 
16
16
  start_time = Time.now.utc.round(10)
17
- @logger.info "start #{request.method} #{request.path}"
18
- @logger.info "body: #{request.instance_variable_get(:@header)}"
19
- @logger.info "body: #{request.body}"
20
17
 
21
- if request.path.start_with?('/organizations')
18
+ # if request.path.start_with?('/organizations')
22
19
  # Contrived example to show how client settings can be adjusted
23
20
  # client.open_timeout = 60
24
21
  # client.read_timeout = 60
25
- end
22
+ # end
26
23
 
27
24
  begin
28
25
  response = super
@@ -32,10 +29,15 @@ module FlowcommerceSpree
32
29
  # client.open_timeout = original_open
33
30
  # client.read_timeout = original_read
34
31
 
35
- end_time = Time.now.utc.round(10)
36
- duration = ((end_time - start_time) * 1000).round(0)
37
- @logger.info "complete #{request.method} #{request.path} #{duration} ms"
38
- @logger.info "response: #{response}"
32
+ duration = ((Time.now.utc.round(10) - start_time) * 1000).round(0)
33
+
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
+ )
40
+
39
41
  @logger.info "Error: #{e.inspect}" if e
40
42
  end
41
43
  end
@@ -5,25 +5,34 @@ module FlowcommerceSpree
5
5
  class Session
6
6
  attr_accessor :session, :localized, :visitor
7
7
 
8
- def initialize(ip:, visitor:)
8
+ def self.create(ip:, visitor:, experience: nil)
9
+ instance = new(ip: ip, visitor: visitor, experience: experience)
10
+ instance.create
11
+ instance
12
+ end
13
+
14
+ def initialize(ip:, visitor:, experience: nil)
9
15
  ip = '127.0.0.1' if ip == '::1'
10
16
 
11
17
  @ip = ip
12
18
  @visitor = visitor
19
+ @experience = experience
13
20
  end
14
21
 
15
- # create session with blank data
22
+ # create session without or with experience (the latter is useful for creating a new session with the order's
23
+ # experience on refreshing the checkout_token)
16
24
  def create
17
25
  data = { ip: @ip,
18
26
  visit: { id: @visitor,
19
27
  expires_at: (Time.now + 30.minutes).iso8601 } }
28
+ data[:experience] = @experience if @experience
20
29
 
21
30
  session_model = ::Io::Flow::V0::Models::SessionForm.new data
22
31
  @session = FlowCommerce.instance(http_handler: LoggingHttpHandler.new)
23
32
  .sessions.post_organizations_by_organization(ORGANIZATION, session_model)
24
33
  end
25
34
 
26
- # if we want to manualy switch to specific country or experience
35
+ # if we want to manually switch to specific country or experience
27
36
  def update(data)
28
37
  @session = FlowCommerce.instance.sessions.put_by_session(@session.id,
29
38
  ::Io::Flow::V0::Models::SessionPutForm.new(data))
@@ -34,6 +43,10 @@ module FlowcommerceSpree
34
43
  @session.local&.experience
35
44
  end
36
45
 
46
+ def expires_at
47
+ @session.visit.expires_at
48
+ end
49
+
37
50
  def local
38
51
  @session.local
39
52
  end
@@ -42,14 +55,6 @@ module FlowcommerceSpree
42
55
  @session.id
43
56
  end
44
57
 
45
- def localized?
46
- # use flow if we are not in default country
47
- return false unless local
48
- return false if @localized.class == FalseClass
49
-
50
- local.country.iso_3166_3 != ENV.fetch('FLOW_BASE_COUNTRY').upcase
51
- end
52
-
53
58
  # because we do not get full experience from session, we have to get from exp list
54
59
  def delivered_duty_options
55
60
  return nil unless experience
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlowcommerceSpree
4
+ module TestSupport
5
+ FACTORY_PATH = File.expand_path('../../spec/factories/flow_io', __dir__)
6
+ end
7
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FlowcommerceSpree
4
- VERSION = '0.0.2'
4
+ VERSION = '0.0.3'
5
5
  end
@@ -3,7 +3,7 @@
3
3
  module FlowcommerceSpree
4
4
  # communicates with flow api, responds to webhook events
5
5
  class WebhookService
6
- attr_accessor :errors, :product, :variant
6
+ attr_accessor :errors
7
7
  alias full_messages errors
8
8
 
9
9
  def self.process(data, opts = {})
@@ -17,72 +17,158 @@ module FlowcommerceSpree
17
17
  end
18
18
 
19
19
  def process
20
- org = @data['organization']
21
- if org != ORGANIZATION
22
- errors << { message: "Organization name mismatch for #{org}" }
23
- else
24
- discriminator = @data['discriminator']
25
- hook_method = "hook_#{discriminator}"
26
- # If hook processing method registered an error, a self.object of WebhookService with this error will be
27
- # returned, else an ActiveRecord object will be returned
28
- return __send__(hook_method) if respond_to?(hook_method, true)
29
-
30
- errors << { message: "No hook for #{discriminator}" }
31
- end
20
+ discriminator = @data['discriminator']
21
+ hook_method = "hook_#{discriminator}"
22
+ # If hook processing method registered an error, a self.object of WebhookService with this error will be
23
+ # returned, else an ActiveRecord object will be returned
24
+ return __send__(hook_method) if respond_to?(hook_method, true)
32
25
 
26
+ errors << { message: "No hook for #{discriminator}" }
33
27
  self
34
28
  end
35
29
 
36
30
  private
37
31
 
32
+ def hook_capture_upserted_v2
33
+ capture = @data['capture']
34
+ order_number = capture.dig('authorization', 'order', 'number')
35
+ if (order = Spree::Order.find_by(number: order_number))
36
+ order.flow_data['captures'] ||= []
37
+ order_captures = order.flow_data['captures']
38
+ order_captures.delete_if do |c|
39
+ c['id'] == capture['id']
40
+ end
41
+ order_captures << capture
42
+
43
+ order.update_column(:meta, order.meta.to_json)
44
+ order
45
+ else
46
+ errors << { message: "Order #{order_number} not found" }
47
+ self
48
+ end
49
+ end
50
+
38
51
  def hook_experience_upserted_v2
39
52
  experience = @data['experience']
40
53
  Spree::Zones::Product.find_or_initialize_by(name: experience['key'].titleize).store_flow_io_data(experience)
41
54
  end
42
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
63
+ else
64
+ errors << { message: "Order #{order_number} not found" }
65
+ end
66
+ end
67
+ else
68
+ errors << { message: 'Order number param missing' }
69
+ end
70
+
71
+ self
72
+ end
73
+
43
74
  def hook_local_item_upserted
44
- if (local_item = @data['local_item'])
45
- if (received_sku = local_item.dig('item', 'number'))
46
- if (@variant = Spree::Variant.find_by(sku: received_sku))
47
- @variant.add_flow_io_experience_data(
48
- local_item.dig('experience', 'key'),
49
- 'prices' => [local_item.dig('pricing', 'price')], 'status' => local_item['status']
50
- )
51
-
52
- @variant.update_column(:meta, @variant.meta.to_json)
53
- return @variant
75
+ errors << { message: 'Local item param missing' } && (return self) unless (local_item = @data['local_item'])
76
+
77
+ errors << { message: 'SKU param missing' } && (return self) unless (flow_sku = local_item.dig('item', 'number'))
78
+
79
+ if (variant = Spree::Variant.find_by(sku: flow_sku))
80
+ variant.add_flow_io_experience_data(
81
+ local_item.dig('experience', 'key'),
82
+ 'prices' => [local_item.dig('pricing', 'price')], 'status' => local_item['status']
83
+ )
84
+
85
+ variant.update_column(:meta, variant.meta.to_json)
86
+ return variant
87
+ else
88
+ errors << { message: "Variant with sku [#{flow_sku}] not found!" }
89
+ end
90
+
91
+ self
92
+ end
93
+
94
+ def hook_order_placed_v2
95
+ order_placed = @data['order_placed']
96
+ flow_order = order_placed['order']
97
+ flow_allocation = order_placed['allocation']
98
+
99
+ errors << { message: 'Order number param missing' } && (return self) unless (order_number = flow_order['number'])
100
+
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
54
113
  else
55
- errors << { message: "Variant with sku [#{received_sku}] not found!" }
114
+ attrs_to_update[:state] = 'confirmed'
56
115
  end
57
- else
58
- errors << { message: 'SKU param missing' }
59
116
  end
117
+
118
+ attrs_to_update.merge!(order.prepare_flow_addresses) if order.complete? || attrs_to_update[:state] == 'complete'
119
+
120
+ if flow_data_submitted
121
+ order.create_proposed_shipments
122
+ order.shipment.update_amounts
123
+ order.line_items.each(&:store_ets)
124
+ end
125
+
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
60
137
  else
61
- errors << { message: 'Local item param missing' }
138
+ errors << { message: "Order #{order_number} not found" }
62
139
  end
63
140
 
64
141
  self
65
142
  end
66
143
 
67
144
  def hook_order_upserted_v2
68
- errors << { message: 'Order param missing' } unless (received_order = @data['order'])
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'])
69
148
 
70
- if errors.none? && (order_number = received_order['number'])
71
- if (order = Spree::Order.find_by(number: order_number))
72
- order.flow_data['order'] = received_order.to_hash
73
- attrs_to_update = { meta: order.meta.to_json }
74
- if order.flow_data['order']['submitted_at'].present?
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)
75
156
  attrs_to_update[:state] = 'complete'
76
- attrs_to_update[:completed_at] = Time.zone.now
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'
77
162
  end
78
-
79
- order.update_columns(attrs_to_update)
80
- return order
81
- else
82
- errors << { message: "Order #{order_number} not found" }
83
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
84
170
  else
85
- errors << { message: 'Order number param missing' }
171
+ errors << { message: "Order #{order_number} not found" }
86
172
  end
87
173
 
88
174
  self
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.2
4
+ version: 0.0.3
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-18 00:00:00.000000000 Z
12
+ date: 2021-02-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: colorize
@@ -101,6 +101,20 @@ 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'
104
118
  - !ruby/object:Gem::Dependency
105
119
  name: spree_backend
106
120
  requirement: !ruby/object:Gem::Requirement
@@ -146,15 +160,19 @@ files:
146
160
  - app/assets/stylesheets/flowcommerce_spree/application.css
147
161
  - app/controllers/concerns/current_zone_loader_decorator.rb
148
162
  - app/controllers/flowcommerce_spree/webhooks_controller.rb
163
+ - app/controllers/users/sessions_controller_decorator.rb
149
164
  - app/helpers/flowcommerce_spree/application_helper.rb
150
165
  - app/helpers/spree/admin/orders_helper_decorator.rb
151
166
  - app/helpers/spree/core/controller_helpers/flow_io_order_helper_decorator.rb
152
167
  - app/mailers/spree/spree_order_mailer_decorator.rb
153
168
  - app/models/flowcommerce_spree/settings.rb
169
+ - app/models/spree/address_decorator.rb
170
+ - app/models/spree/calculator/flow_io.rb
171
+ - app/models/spree/calculator/shipping/flow_io.rb
154
172
  - app/models/spree/credit_card_decorator.rb
155
173
  - app/models/spree/flow_io_product_decorator.rb
156
174
  - app/models/spree/flow_io_variant_decorator.rb
157
- - app/models/spree/gateway/spree_flow_gateway.rb
175
+ - app/models/spree/gateway/flow_io.rb
158
176
  - app/models/spree/line_item_decorator.rb
159
177
  - app/models/spree/order_decorator.rb
160
178
  - app/models/spree/promotion_decorator.rb
@@ -163,6 +181,7 @@ files:
163
181
  - app/models/spree/taxon_decorator.rb
164
182
  - app/models/spree/zone_decorator.rb
165
183
  - app/models/spree/zones/flow_io_product_zone_decorator.rb
184
+ - app/models/tracking/setup_decorator.rb
166
185
  - app/services/flowcommerce_spree/import_experience_items.rb
167
186
  - app/services/flowcommerce_spree/import_experiences.rb
168
187
  - app/services/flowcommerce_spree/order_sync.rb
@@ -173,7 +192,7 @@ files:
173
192
  - app/views/spree/admin/shared/_order_summary_flow.html.erb
174
193
  - app/views/spree/order_mailer/confirm_email.html.erb
175
194
  - app/views/spree/order_mailer/confirm_email.text.erb
176
- - config/initializers/flowcommerce_spree.rb
195
+ - config/rails_best_practices.yml
177
196
  - config/routes.rb
178
197
  - db/migrate/20201021160159_add_type_and_meta_to_spree_zone.rb
179
198
  - db/migrate/20201021755957_add_meta_to_spree_tables.rb
@@ -190,6 +209,7 @@ files:
190
209
  - lib/flowcommerce_spree/logging_http_handler.rb
191
210
  - lib/flowcommerce_spree/refresher.rb
192
211
  - lib/flowcommerce_spree/session.rb
212
+ - lib/flowcommerce_spree/test_support.rb
193
213
  - lib/flowcommerce_spree/version.rb
194
214
  - lib/flowcommerce_spree/webhook_service.rb
195
215
  - lib/simple_csv_writer.rb
@@ -1,7 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FlowcommerceSpree
4
- ORGANIZATION = ENV.fetch('FLOW_ORGANIZATION', 'flow.io')
5
- BASE_COUNTRY = ENV.fetch('FLOW_BASE_COUNTRY', 'USA')
6
- API_KEY = ENV.fetch('FLOW_TOKEN', 'test_key')
7
- end