flowcommerce_spree 0.0.2 → 0.0.3

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