flowcommerce_spree 0.0.6 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ff1a7f69451b03b52d6422015450255988ac45f664a1bee62559e238e45f0a6b
4
- data.tar.gz: d9fab8b6b5091b70235cde18b88dd0dc0988ed0d38946571236d9a828b0e6750
3
+ metadata.gz: ad7616da6a9fb5e7fbffe0a8869730ffedacb91deec3a5d08f328d2b876338ad
4
+ data.tar.gz: 76ffd0d23d05028ba0d24df80fa5a092efe62f0b5f14ddde339e73ede5ee53bc
5
5
  SHA512:
6
- metadata.gz: 73f135866d3644fc4b9a92bc8003d88c28bdf220612e4823d2d1f04e190f3e0888f00c995e215b7efc9ebfa81bd9720b47b00e8f796bdad6b68391bb1852ec59
7
- data.tar.gz: 8b2c6fe4c1d2987f4c467fb2f33afee8d0b4e15b7b6a12dddb44b28ab57c538e8c1dd9608604deca5170d70adfc011b0299ba00c7ba33c5b966a959878355a79
6
+ metadata.gz: 1eb2b92c39dd528cc4990d9957ee5dba29fbdfa39d7f7a4b821d07a6f5cb366541821b67a6d23ccbc9854b3c3c0867e0cd3fff8b36f64cea627b2fa6d7da47df
7
+ data.tar.gz: 75175908d8ff3a85ca061ea82890af39544e66bf5898409e0b38d467bc40880eb6ac8e1773263cc5e933ef54d28bf00a2fb1a3f5cddbd935793b92c7fa0e89f2
@@ -31,16 +31,9 @@ CurrentZoneLoader.module_eval do
31
31
  .where("meta -> 'flow_data' ->> 'country' = ?",
32
32
  ISO3166::Country[request_iso_code]&.alpha3).exists?
33
33
 
34
- request_ip = if Rails.env.production?
35
- request.ip
36
- else
37
- Spree::Config[:debug_request_ip_address] || request.ip
38
- # Germany ip: 85.214.132.117, Sweden ip: 62.20.0.196, Moldova ip: 89.41.76.29
39
- end
40
-
41
34
  # This will issue a session creation request to flow.io. The response will contain the Flow Experience key and
42
35
  # the session_id
43
- flow_io_session = FlowcommerceSpree::Session.create(ip: request_ip, visitor: visitor_id_for_flow_io)
36
+ flow_io_session = FlowcommerceSpree::Session.create(country: request_iso_code, visitor: visitor_id_for_flow_io)
44
37
 
45
38
  if (zone = Spree::Zones::Product.active.find_by(name: flow_io_session.experience&.key&.titleize))
46
39
  session['flow_session_id'] = flow_io_session.id
@@ -4,5 +4,6 @@ module FlowcommerceSpree
4
4
  class Settings < Spree::Preferences::Configuration
5
5
  preference :additional_attributes, :hash, default: {}
6
6
  preference :product_catalog_upload, :hash, default: {}
7
+ preference :notification_setting, :hash, default: {}
7
8
  end
8
9
  end
@@ -11,8 +11,8 @@ module Spree
11
11
  order = item.order
12
12
 
13
13
  if can_calculate_tax?(order)
14
- flow_response = get_flow_tax_data(order)
15
- tax_for_item(item, flow_response)
14
+ get_flow_tax_data(order)
15
+ tax_for_item(item)
16
16
  else
17
17
  prev_tax_amount(item)
18
18
  end
@@ -20,6 +20,13 @@ module Spree
20
20
  alias compute_shipment compute_shipment_or_line_item
21
21
  alias compute_line_item compute_shipment_or_line_item
22
22
 
23
+ def get_tax_rate(taxable)
24
+ order = taxable.class.to_s == 'Spree::Order' ? taxable : taxable.order
25
+ get_flow_tax_data(order) if order.flow_allocations.empty?
26
+ response = order.flow_tax_for_item(taxable.adjustable, 'vat_item_price', rate.included_in_price)
27
+ response.nil? ? 0 : response['rate']&.to_f
28
+ end
29
+
23
30
  private
24
31
 
25
32
  def prev_tax_amount(item)
@@ -39,21 +46,26 @@ module Spree
39
46
 
40
47
  def get_flow_tax_data(order)
41
48
  flow_io_tax_response = Rails.cache.fetch(order.flow_tax_cache_key, time_to_idle: 5.minutes) do
42
- FlowcommerceSpree.client.orders.get_allocations_by_number(FlowcommerceSpree::ORGANIZATION, order.number)
49
+ response = FlowcommerceSpree.client.orders
50
+ .get_allocations_by_number(FlowcommerceSpree::ORGANIZATION, order.number)
51
+ return nil unless response.present?
52
+
53
+ order.flow_order['allocations'] = response.to_hash
54
+ order.update_column(:meta, order.meta.to_json)
55
+ response
43
56
  end
44
57
  flow_io_tax_response
45
58
  end
46
59
 
47
- def tax_for_item(item, flow_response)
60
+ def tax_for_item(item)
61
+ order = item.order
48
62
  prev_tax_amount = prev_tax_amount(item)
49
- return prev_tax_amount if flow_response.nil?
50
-
51
- item_details = flow_response.details&.find do |el|
52
- item.is_a?(Spree::LineItem) ? el.number == item.variant.sku : el.key.value == 'shipping'
53
- end
54
- price_components = rate.included_in_price ? item_details.included : item_details.not_included
63
+ tax_data = order.flow_tax_for_item(item, 'vat_item_price', rate.included_in_price)
64
+ return prev_tax_amount if tax_data.blank?
55
65
 
56
- amount = price_components&.find { |el| el.key.value == 'vat_item_price' }&.total&.amount
66
+ subsidy_data = order.flow_tax_for_item(item, 'vat_subsidy', rate.included_in_price)
67
+ amount = tax_data.dig('total', 'amount')
68
+ amount += subsidy_data.dig('total', 'amount') if subsidy_data.present?
57
69
  amount.present? && amount > 0 ? amount : prev_tax_amount
58
70
  end
59
71
  end
@@ -158,6 +158,24 @@ module Spree
158
158
  address_attributes
159
159
  end
160
160
 
161
+ def flow_allocations
162
+ return @flow_allocations if @flow_allocations
163
+
164
+ @flow_allocations = flow_order&.[]('allocations')
165
+ end
166
+
167
+ def flow_tax_for_item(item, tax_key, included_in_price = true)
168
+ return {} if flow_allocations.blank?
169
+
170
+ item_details = flow_allocations['details']&.find do |el|
171
+ item.is_a?(Spree::LineItem) ? el['number'] == item.variant.sku : el['key'] == 'shipping'
172
+ end
173
+ return {} if item_details.blank?
174
+
175
+ price_components = included_in_price ? item_details['included'] : item_details['not_included']
176
+ price_components&.find { |el| el['key'] == tax_key }
177
+ end
178
+
161
179
  Spree::Order.include(self) if Spree::Order.included_modules.exclude?(self)
162
180
  end
163
181
  end
@@ -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,17 +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)
56
-
57
- return if FlowcommerceSpree::API_KEY.blank? || FlowcommerceSpree::API_KEY == 'test_key'
62
+ error = sync_flow_info?
63
+ return error if error.present?
58
64
 
59
- return { error: 'Price is 0' } if price == 0
60
-
61
- return unless country_of_origin
65
+ update_flow_data
66
+ end
62
67
 
68
+ def update_flow_data
63
69
  additional_attrs = {}
64
70
  attr_name = nil
65
71
  export_required = false
@@ -84,7 +90,7 @@ module Spree
84
90
  flow_item_sh1 = Digest::SHA1.hexdigest(flow_item.to_json)
85
91
 
86
92
  # skip if sync not needed
87
- 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
88
94
 
89
95
  response = FlowcommerceSpree.client.items.put_by_number(FlowcommerceSpree::ORGANIZATION, sku, flow_item)
90
96
  self.flow_data ||= {}
@@ -93,6 +99,8 @@ module Spree
93
99
  # after successful put, write cache
94
100
  update_column(:meta, meta.to_json)
95
101
 
102
+ FlowcommerceSpree::ImportItemWorker.perform_async(sku)
103
+
96
104
  response
97
105
  rescue Net::OpenTimeout => e
98
106
  { error: e.message }
@@ -21,7 +21,7 @@ module Spree
21
21
  end
22
22
 
23
23
  def payment_profiles_supported?
24
- true
24
+ false
25
25
  end
26
26
 
27
27
  def method_type
@@ -43,14 +43,18 @@ module Spree
43
43
  end
44
44
 
45
45
  def refund(payment, amount, _options = {})
46
- request_refund_store_result(payment.order, amount)
46
+ response = request_refund_store_result(payment.order, amount)
47
+ map_refund_to_payment(response, payment.order) if response.success?
48
+ response
47
49
  rescue StandardError => e
48
50
  ActiveMerchant::Billing::Response.new(false, e.to_s, {}, {})
49
51
  end
50
52
 
51
53
  def cancel(authorization)
52
54
  original_payment = Spree::Payment.find_by(response_code: authorization)
53
- request_refund_store_result(original_payment.order, original_payment.amount)
55
+ response = request_refund_store_result(original_payment.order, original_payment.amount)
56
+ map_refund_to_payment(response, original_payment.order) if response.success?
57
+ response
54
58
  rescue StandardError => e
55
59
  ActiveMerchant::Billing::Response.new(false, e.to_s, {}, {})
56
60
  end
@@ -72,6 +76,12 @@ module Spree
72
76
  create_flow_cc_profile!
73
77
  end
74
78
 
79
+ def credit(payment, credit_amount)
80
+ request_refund_store_result(payment.order, credit_amount)
81
+ rescue StandardError => e
82
+ ActiveMerchant::Billing::Response.new(false, e.to_s, {}, {})
83
+ end
84
+
75
85
  private
76
86
 
77
87
  def request_refund_store_result(order, amount)
@@ -82,8 +92,10 @@ module Spree
82
92
  response_status = response.status.value
83
93
  if response_status == REFUND_SUCCESS
84
94
  add_refund_to_order(response, order)
85
- map_refund_to_payment(response, order)
86
- ActiveMerchant::Billing::Response.new(true, REFUND_SUCCESS, {}, {})
95
+ ActiveMerchant::Billing::Response.new(true,
96
+ REFUND_SUCCESS,
97
+ response.to_hash,
98
+ authorization: response.authorization.id)
87
99
  else
88
100
  msg = "Partial refund fail. Details: #{response_status}"
89
101
  ActiveMerchant::Billing::Response.new(false, msg, {}, {})
@@ -100,18 +112,17 @@ module Spree
100
112
  end
101
113
 
102
114
  def map_refund_to_payment(response, order)
103
- original_payment = Spree::Payment.find_by(response_code: response.authorization.id)
115
+ original_payment = Spree::Payment.find_by(response_code: response.authorization)
104
116
  payment = order.payments.create!(state: 'completed',
105
- response_code: response.authorization.id,
117
+ response_code: response.authorization,
106
118
  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)
119
+ amount: - response.params['amount'].to_f,
120
+ source: original_payment)
110
121
 
111
122
  # For now this additional update is overwriting the generated identifier with flow.io payment identifier.
112
123
  # TODO: Check and possibly refactor in Spree 3.0, where the `before_create :set_unique_identifier`
113
124
  # has been removed.
114
- payment.update_column(:identifier, response.id)
125
+ payment.update_column(:identifier, response.params['id'])
115
126
  end
116
127
 
117
128
  # hard inject Flow as payment method unless defined
@@ -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
@@ -124,7 +124,7 @@ module FlowcommerceSpree
124
124
  { center: FLOW_CENTER,
125
125
  number: variant.sku,
126
126
  quantity: line_item.quantity,
127
- price: { amount: price_root['amount'] || variant.cost_price,
127
+ price: { amount: price_root['amount'] || variant.price,
128
128
  currency: price_root['currency'] || variant.cost_currency } }
129
129
  end
130
130
 
@@ -22,16 +22,25 @@ module FlowcommerceSpree
22
22
  errors << { message: 'Order number param missing' } && (return self) unless order_number
23
23
 
24
24
  if (order = Spree::Order.find_by(number: order_number))
25
- upsert_order_captures(order, capture)
26
- payments = order.flow_io_payments
27
- map_payment_captures_to_spree(order, payments) if payments.present?
28
- order
25
+ if order.payments.any?
26
+ store_payment_capture(order, capture)
27
+ else
28
+ FlowcommerceSpree::UpdatePaymentCaptureWorker.perform_in(1.minute, order.number, capture)
29
+ order
30
+ end
29
31
  else
30
32
  errors << { message: "Order #{order_number} not found" }
31
33
  self
32
34
  end
33
35
  end
34
36
 
37
+ def store_payment_capture(order, capture)
38
+ upsert_order_captures(order, capture)
39
+ payments = order.flow_io_payments
40
+ map_payment_captures_to_spree(order, payments) if payments.present?
41
+ order
42
+ end
43
+
35
44
  private
36
45
 
37
46
  def upsert_order_captures(order, capture)
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlowcommerceSpree
4
+ class FlowIoWorker
5
+ include Sidekiq::Worker
6
+
7
+ sidekiq_retries_exhausted do |message, exception|
8
+ Rails.logger.warn("[!] #{self.class} max attempts reached: #{message} - #{exception}")
9
+ notification_setting = FlowcommerceSpree::Config.notification_setting
10
+ return unless notification_setting[:slack].present?
11
+
12
+ slack_message = "[#{Rails.env}] #{message}"
13
+ Slack_client.chat_postMessage(channel: notification_setting[:slack][:channel], text: slack_message)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlowcommerceSpree
4
+ class ImportItemWorker < FlowIoWorker
5
+ sidekiq_options retry: 3, queue: :flow_io
6
+
7
+ def perform(variant_sku)
8
+ variant = Spree::Variant.find_by sku: variant_sku
9
+ return unless variant
10
+
11
+ FlowcommerceSpree::ImportItem.run(variant)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlowcommerceSpree
4
+ class UpdatePaymentCaptureWorker < FlowIoWorker
5
+ sidekiq_options retry: 3, queue: :flow_io
6
+
7
+ def perform(order_number, capture = {})
8
+ order = Spree::Order.find_by number: order_number
9
+ raise 'Order has no payments' if order.payments.empty?
10
+
11
+ FlowcommerceSpree::Webhooks::CaptureUpsertedV2.new({ capture: capture }.as_json)
12
+ .store_payment_capture(order, capture)
13
+ end
14
+ end
15
+ end
@@ -5,16 +5,14 @@ module FlowcommerceSpree
5
5
  class Session
6
6
  attr_accessor :session, :localized, :visitor
7
7
 
8
- def self.create(ip:, visitor:, experience: nil)
9
- instance = new(ip: ip, visitor: visitor, experience: experience)
8
+ def self.create(country:, visitor:, experience: nil)
9
+ instance = new(country: country, visitor: visitor, experience: experience)
10
10
  instance.create
11
11
  instance
12
12
  end
13
13
 
14
- def initialize(ip:, visitor:, experience: nil)
15
- ip = '127.0.0.1' if ip == '::1'
16
-
17
- @ip = ip
14
+ def initialize(country:, visitor:, experience: nil)
15
+ @country = country
18
16
  @visitor = visitor
19
17
  @experience = experience
20
18
  end
@@ -22,7 +20,7 @@ module FlowcommerceSpree
22
20
  # create session without or with experience (the latter is useful for creating a new session with the order's
23
21
  # experience on refreshing the checkout_token)
24
22
  def create
25
- data = { ip: @ip,
23
+ data = { country: @country,
26
24
  visit: { id: @visitor,
27
25
  expires_at: (Time.now + 30.minutes).iso8601 } }
28
26
  data[:experience] = @experience if @experience
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FlowcommerceSpree
4
- VERSION = '0.0.6'
4
+ VERSION = '0.0.10'
5
5
  end
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.6
4
+ version: 0.0.10
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-04-13 00:00:00.000000000 Z
12
+ date: 2021-08-04 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: active_model_serializers
@@ -210,7 +210,6 @@ files:
210
210
  - app/helpers/spree/core/controller_helpers/flow_io_order_helper_decorator.rb
211
211
  - app/models/flowcommerce_spree/settings.rb
212
212
  - app/models/spree/address_decorator.rb
213
- - app/models/spree/app_configuration_decorator.rb
214
213
  - app/models/spree/calculator/flow_io.rb
215
214
  - app/models/spree/calculator/shipping/flow_io.rb
216
215
  - app/models/spree/flow_io_credit_card_decorator.rb
@@ -231,6 +230,7 @@ files:
231
230
  - app/serializers/api/v2/order_serializer_decorator.rb
232
231
  - app/services/flowcommerce_spree/import_experience_items.rb
233
232
  - app/services/flowcommerce_spree/import_experiences.rb
233
+ - app/services/flowcommerce_spree/import_item.rb
234
234
  - app/services/flowcommerce_spree/order_sync.rb
235
235
  - app/services/flowcommerce_spree/order_updater.rb
236
236
  - app/services/flowcommerce_spree/webhooks/capture_upserted_v2.rb
@@ -244,6 +244,9 @@ files:
244
244
  - app/views/spree/admin/promotions/edit.html.erb
245
245
  - app/views/spree/admin/shared/_order_summary.html.erb
246
246
  - app/views/spree/admin/shared/_order_summary_flow.html.erb
247
+ - app/workers/flowcommerce_spree/flow_io_worker.rb
248
+ - app/workers/flowcommerce_spree/import_item_worker.rb
249
+ - app/workers/flowcommerce_spree/update_payment_capture_worker.rb
247
250
  - config/rails_best_practices.yml
248
251
  - config/routes.rb
249
252
  - db/migrate/20201021160159_add_type_and_meta_to_spree_zone.rb
@@ -281,7 +284,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
281
284
  - !ruby/object:Gem::Version
282
285
  version: '0'
283
286
  requirements: []
284
- rubygems_version: 3.1.2
287
+ rubygems_version: 3.0.8
285
288
  signing_key:
286
289
  specification_version: 4
287
290
  summary: Integration of Spree with Flow API
@@ -1,7 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Spree
4
- AppConfiguration.class_eval do
5
- preference :debug_request_ip_address, :string
6
- end
7
- end