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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 51a668ee3cc70d0bc9bd8c7662a972f086e06dcdcf4c68df2177e94818dfb820
4
- data.tar.gz: aa9ff17e57cd19452736af11d51bd1905c4dd7e66bb461b5dff017fc5dea0b0d
3
+ metadata.gz: c89d8869da7867a7ae0400dab1005c763c5c98f001336bf915f14743bd279c7a
4
+ data.tar.gz: f664b4f9f7380860b5c5eec7cf5aeda85d59c8b6d2753830f553ccc8734718c4
5
5
  SHA512:
6
- metadata.gz: 024a29a6fc47f932e432f2477f9a1f3b520af7cc10b7a85eb35613328276d5390a682187151217c0527b20435431f78e8cc55255d95298620bfb5077c7ed5011
7
- data.tar.gz: b6d9485612e360ed50caabfab537baf9583feec384607b4a890c1ab1fd9fc48cd9ea3954562aac828f4a858682ad1515ae7f2b9bf05a8a501ff8abf96c28f972
6
+ metadata.gz: f32478dce9ed19e62ddb9955a0e0d8f7b7388eb9c5079be107282306c85ab524b49f83b35cff0d5835fc79c485d0dd3fc15cb924969ba250dd80182dfb6242c4
7
+ data.tar.gz: b1e68c45f17888c1d717001a2a352957e04a4fb32c47d2864349299d868e01e0cbfd138f7ed599cd5c1a06e3847b4e4745d8630b0472d104c7eceabe6177f518
data/README.md CHANGED
@@ -20,8 +20,15 @@ All flowcommerce_spree code is located in the ./app and ./lib folders.
20
20
 
21
21
  - Run `bundle install`.
22
22
 
23
- - Define this additional ENV variables. You will find all of them, except FLOW_MOUNT_PATH in
24
- [Flow console](https://console.flow.io/org_account_name/organization/integrations):
23
+ - Define these additional ENV variables.
24
+ - You will find FLOW_TOKEN, FLOW_ORGANIZATION and FLOW_BASE_COUNTRY in [Flow
25
+ console](https://console.flow.io/org_account_name/organization/integrations)
26
+ - To enable HTTP Basic authentication for securing the FlowcommerceSpree::WebhooksController, prepend
27
+ username:password@ to the hostname in your webhook URL.
28
+ By doing so, the credentials needed for authentication will be sent in the HTTP header.
29
+ For example: https://username:password@www.mywebhookurl.com
30
+ On the main app's backend side, the `username` and `password` values should be defined in the
31
+ FLOW_IO_WEBHOOK_USER and FLOW_IO_WEBHOOK_PASSWORD environment variables
25
32
 
26
33
  ```
27
34
  FLOW_TOKEN='SUPERsecretTOKEN' # API_KEY
@@ -29,7 +36,10 @@ All flowcommerce_spree code is located in the ./app and ./lib folders.
29
36
  FLOW_BASE_COUNTRY='usa'
30
37
  # The path to which the FlowcommerceSpree engine will be mounted (default, if this variable is missing, will be the
31
38
  # '/flow' path)
32
- FLOW_MOUNT_PATH='/flow'
39
+ FLOW_MOUNT_PATH='/flow'
40
+ # The following variables should be set for securing the FlowcommerceSpree::WebhooksControler
41
+ FLOW_IO_WEBHOOK_USER
42
+ FLOW_IO_WEBHOOK_PASSWORD
33
43
  ```
34
44
 
35
45
  - To enable payments with the FlowCommerce engine, the payment method `flow.io` with `Spree::Gateway::FlowIo` should be
@@ -86,7 +96,9 @@ being used, depending on the level of modification.
86
96
 
87
97
  ### Spree::Gateway::FlowIo
88
98
 
89
- Adapter for Spree, that allows using [Flow.io](https://www.flow.io) as payment gateway. Flow is PCI compliant payment processor.
99
+ Adapter for Spree, that allows using [Flow.io](https://www.flow.io) as payment gateway.
100
+ Flow is PCI compliant payment processor.
101
+
90
102
 
91
103
  ## Gem Maintenance
92
104
 
@@ -129,11 +141,28 @@ by the following command:
129
141
  gem build flowcommerce_spree.gemspec
130
142
  ```
131
143
 
132
- Asuming the version was set to `0.0.1`, a `flowcommerce_spree-0.0.1.gem` will be generated at the root of the app
133
- (repo).
144
+ Assuming the version was set to `0.0.1`,
145
+ a `flowcommerce_spree-0.0.1.gem` binary file will be generated at the root of the app (repo).
146
+
147
+ - The binary file shouldn't be added into the `git` tree, it will be pushed into the RubyGems and to the GitHub releases
134
148
 
135
149
  ### Pushing a new gem release to RubyGems
136
150
 
137
151
  ```
138
152
  gem push flowcommerce_spree-0.0.1.gem # don't forget to specify the correct version number
139
153
  ```
154
+
155
+ ### Crafting the new release on GitHub
156
+
157
+ On the [Releases page](https://github.com/mejuri-inc/flowcommerce_spree/releases) push the `Draft a new release` button.
158
+
159
+ The new release editing page opens, on which the following actions could be taken:
160
+
161
+ - Choose the repo branch (default is `main`)
162
+ - Insert a tag version (usually, the tag should correspond to the gem's new version, v0.0.1, for example)
163
+ - the tag will be created by GitHub on the last commit into the chosen branch
164
+ - Fill the release Title and Description
165
+ - Attach the binary file with the generated gem version
166
+ - If the release is not yet ready for production, mark the `This is a pre-release` checkbox
167
+ - Press either the `Publish release`, or the `Save draft button` if you want to publish it later
168
+ - After publishing the release, the the binary gem file will be available on GitHub and could be removed locally
@@ -7,12 +7,14 @@ CurrentZoneLoader.module_eval do
7
7
  return @current_zone if defined?(@current_zone)
8
8
 
9
9
  @current_zone = if (session_region_name = session['region']&.[]('name'))
10
- Spree::Zones::Product.find_by(name: session_region_name)
11
- elsif request_iso_code.present?
12
- @current_zone = flow_zone
13
- @current_zone ||= Spree::Country.find_by(iso: request_iso_code)&.product_zones&.active&.first
10
+ Spree::Zones::Product.find_by(name: session_region_name, status: 'active')
14
11
  end
15
12
 
13
+ @current_zone ||= if request_iso_code.present?
14
+ @current_zone = flow_zone
15
+ @current_zone ||= Spree::Country.find_by(iso: request_iso_code)&.product_zones&.active&.first
16
+ end
17
+
16
18
  @current_zone ||= Spree::Zones::Product.find_by(name: 'International') ||
17
19
  Spree::Zones::Product.new(name: 'International', taxon_ids: [], currencies: %w[USD CAD])
18
20
 
@@ -20,29 +22,21 @@ CurrentZoneLoader.module_eval do
20
22
  session['region'] = { name: current_zone_name, available_currencies: @current_zone.available_currencies,
21
23
  request_iso_code: request_iso_code }
22
24
 
23
- RequestStore.store[:session] = session
24
25
  Rails.logger.debug("Using product zone: #{current_zone_name}")
25
26
  @current_zone
26
27
  end
27
28
 
28
- def flow_zone # rubocop:disable Metrics/AbcSize
29
+ def flow_zone
29
30
  return unless Spree::Zones::Product.active
30
31
  .where("meta -> 'flow_data' ->> 'country' = ?",
31
32
  ISO3166::Country[request_iso_code]&.alpha3).exists?
32
33
 
33
- request_ip = if Rails.env.production?
34
- request.ip
35
- else
36
- Spree::Config[:debug_request_ip_address] || request.ip
37
- # Germany ip: 85.214.132.117, Sweden ip: 62.20.0.196, Moldova ip: 89.41.76.29
38
- end
39
- flow_io_session = FlowcommerceSpree::Session.create(ip: request_ip, visitor: visitor_id_for_flow_io)
40
- # :create method will issue a request to flow.io. The experience, contained in the
41
- # response, will be available in the session object - flow_io_session.experience
34
+ # This will issue a session creation request to flow.io. The response will contain the Flow Experience key and
35
+ # the session_id
36
+ flow_io_session = FlowcommerceSpree::Session.create(country: request_iso_code, visitor: visitor_id_for_flow_io)
42
37
 
43
38
  if (zone = Spree::Zones::Product.active.find_by(name: flow_io_session.experience&.key&.titleize))
44
- session['_f60_session'] = flow_io_session.id
45
- session['_f60_expires_at'] = flow_io_session.expires_at.to_s
39
+ session['flow_session_id'] = flow_io_session.id
46
40
  end
47
41
 
48
42
  zone
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlowcommerceSpree
4
+ class InventoryController < ActionController::Base
5
+ def online_stock_availability
6
+ items = params['items'] || []
7
+ response = items.inject([]) { |result, item| result << check_stock(item[:id], item[:qty].to_i) }
8
+ render json: { items: response }, status: :ok
9
+ end
10
+
11
+ private
12
+
13
+ def check_stock(flow_number, quantity)
14
+ variant = Spree::Variant.find_by(sku: flow_number)
15
+ return { id: flow_number, has_inventory: false } unless variant
16
+
17
+ { id: flow_number, has_inventory: variant.available_online?(quantity) }
18
+ rescue StandardError
19
+ Rails.logger.error "[!] FlowcommerceSpree::InventoryController#stock unexpected Error: #{$ERROR_INFO}"
20
+ { id: flow_number, has_inventory: false }
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlowcommerceSpree
4
+ class OrdersController < ApplicationController
5
+ wrap_parameters false
6
+
7
+ skip_before_action :setup_tracking, only: :order_completed
8
+
9
+ # proxy enpoint between flow and thankyou page.
10
+ # /flow/order_completed endpoint
11
+ def order_completed
12
+ order = Spree::Order.find_by number: params[:order], guest_token: params[:t]
13
+
14
+ flow_updater = FlowcommerceSpree::OrderUpdater.new(order: order)
15
+ flow_updater.complete_checkout
16
+
17
+ redirect_to "/thankyou?order=#{params[:order]}&t=#{params[:t]}"
18
+ end
19
+ end
20
+ end
@@ -4,34 +4,32 @@ module FlowcommerceSpree
4
4
  class WebhooksController < ActionController::Base
5
5
  wrap_parameters false
6
6
  respond_to :json
7
+ http_basic_authenticate_with name: FLOW_IO_WEBHOOK_USER, password: FLOW_IO_WEBHOOK_PASSWORD
7
8
 
8
- # forward all incoming requests to Flow WebhookService object
9
+ # forward incoming requests to respective Flow Webhooks Service objects
9
10
  # /flow/event-target endpoint
10
- def handle_flow_web_hook_event
11
- result = check_organization
12
- if result.blank?
13
- webhook_result = WebhookService.process(params)
14
- result[:error] = webhook_result.full_messages.join("\n") if webhook_result.errors.any?
15
- end
11
+ def handle_flow_io_event
12
+ %i[event_id organization discriminator].each_with_object(params) { |key, obj| obj.require(key) }
13
+ return unless organization_valid?
14
+
15
+ webhook_result = "FlowcommerceSpree::Webhooks::#{params['discriminator'].classify}".constantize.process(params)
16
+ @result = {}
17
+ @result[:error] = webhook_result.full_messages.join("\n") if webhook_result.errors.any?
16
18
  rescue StandardError => e
17
- result = { error: e.class.to_s, message: e.message, backtrace: e.backtrace }
19
+ @result = { error: e.class.to_s, message: e.message, backtrace: e.backtrace }
18
20
  ensure
19
- response_status = if result[:error]
20
- logger.info(result)
21
- :unprocessable_entity
22
- else
23
- :ok
24
- end
25
- render json: result.except(:backtrace), status: response_status
21
+ logger.info(@result) if (error = @result[:error])
22
+ render json: @result.except(:backtrace), status: error ? :unprocessable_entity : :ok
26
23
  end
27
24
 
28
25
  private
29
26
 
30
- def check_organization
27
+ def organization_valid?
31
28
  org = params[:organization]
32
- return {} if org == FlowcommerceSpree::ORGANIZATION
29
+ return true if org == FlowcommerceSpree::ORGANIZATION
33
30
 
34
- { error: 'InvalidParam', message: "Organization '#{org}' is invalid!" }
31
+ @result = { error: 'InvalidParam', message: "Organization '#{org}' is invalid!" }
32
+ false
35
33
  end
36
34
  end
37
35
  end
@@ -2,10 +2,27 @@
2
2
 
3
3
  module Users
4
4
  SessionsController.class_eval do
5
+ # This endpoint is for returning to the FrontEnd the dynamic url to an external checkout, a flow.io url.
6
+ def checkout_url
7
+ flow_session_id = request.headers['flow-session-id']
8
+ return render json: { error: :session_id_missing }, status: 422 if flow_session_id.blank?
9
+
10
+ checkout_token =
11
+ FlowcommerceSpree::OrderSync.new(order: current_order, flow_session_id: flow_session_id).synchronize!
12
+ return render json: { error: :checkout_token_missing }, status: 422 if checkout_token.blank?
13
+
14
+ render json: { checkout_url: "https://checkout.flow.io/tokens/#{checkout_token}" }, status: 200
15
+ end
16
+
5
17
  private
6
18
 
7
- def external_checkout?
8
- current_zone.flow_io_active_experience? ? 'true' : 'false'
19
+ def add_optional_attrs(session_current)
20
+ session_current['user'] = current_user_attrs if current_user&.spree_api_key?
21
+ session_current['region'] = zone_attrs
22
+
23
+ external_checkout = current_zone.flow_io_active_experience?
24
+ session_current['external_checkout'] = external_checkout
25
+ session_current['flow_session_id'] = session['flow_session_id'] if external_checkout
9
26
  end
10
27
  end
11
28
  end
@@ -17,22 +17,6 @@ module Spree
17
17
  @current_order.flow_io_experience_from_zone
18
18
  update_meta ||= true
19
19
  end
20
- order_flow_session_id = @current_order.flow_data['session_id']
21
- order_session_expired = @current_order.flow_data['session_expires_at']
22
- flow_io_session_id = session['_f60_session']
23
- flow_io_session_expires = session['_f60_expires_at']
24
- if flow_io_session_id.present?
25
- if order_flow_session_id != flow_io_session_id &&
26
- order_session_expired&.to_datetime.to_i < flow_io_session_expires&.to_datetime.to_i
27
- @current_order.flow_data['session_id'] = flow_io_session_id
28
- @current_order.flow_data['session_expires_at'] = flow_io_session_expires
29
- @current_order.flow_data['checkout_token'] = nil
30
- update_meta ||= true
31
- end
32
- elsif order_flow_session_id.present?
33
- session['_f60_session'] = order_flow_session_id
34
- session['_f60_expires_at'] = order_session_expired
35
- end
36
20
  end
37
21
 
38
22
  if @current_order.new_record?
@@ -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,7 +11,7 @@ module Spree
11
11
  address2: address_data['streets'][1],
12
12
  zipcode: address_data['postal'],
13
13
  city: address_data['city'],
14
- state_name: address_data['province'] || 'something',
14
+ state_name: address_data['province'],
15
15
  country: Spree::Country.find_by(iso3: address_data['country'])
16
16
  }
17
17
  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)
@@ -32,28 +39,33 @@ module Spree
32
39
 
33
40
  def can_calculate_tax?(order)
34
41
  return false if order.flow_data.blank?
35
- return false if %w[cart address delivery].include?(order.state)
42
+ return false if %w[cart address].include?(order.state)
36
43
 
37
44
  true
38
45
  end
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
@@ -4,6 +4,9 @@ module Spree
4
4
  class Calculator
5
5
  module Shipping
6
6
  class FlowIo < ShippingCalculator
7
+ preference :lower_boundary, :decimal, default: 100
8
+ preference :charge_default, :decimal, default: 15
9
+
7
10
  def self.description
8
11
  'FlowIO Calculator'
9
12
  end
@@ -16,11 +19,11 @@ module Spree
16
19
  end
17
20
 
18
21
  def default_charge(_country)
19
- 0
22
+ preferred_charge_default
20
23
  end
21
24
 
22
25
  def threshold
23
- 0
26
+ preferred_lower_boundary
24
27
  end
25
28
 
26
29
  private
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ module FlowIoCreditCardDecorator
5
+ def self.prepended(base)
6
+ base.serialize :meta, ActiveRecord::Coders::JSON.new(symbolize_keys: true)
7
+
8
+ base.store_accessor :meta, :flow_data
9
+ end
10
+
11
+ def push_authorization(auth_hash)
12
+ self.flow_data ||= {}
13
+ flow_data['authorizations'] ||= []
14
+ card_authorizations = flow_data['authorizations']
15
+ card_authorizations.delete_if { |ca| ca['id'] == auth_hash['id'] }
16
+ card_authorizations << auth_hash
17
+ end
18
+
19
+ Spree::CreditCard.prepend(self) if Spree::CreditCard.included_modules.exclude?(self)
20
+ end
21
+ end
@@ -0,0 +1,181 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ # Added flow specific methods to Spree::Order
5
+ module FlowIoOrderDecorator
6
+ def self.included(base)
7
+ base.serialize :meta, ActiveRecord::Coders::JSON.new(symbolize_keys: true)
8
+
9
+ base.store_accessor :meta, :flow_data
10
+ end
11
+
12
+ def flow_tax_cache_key
13
+ [number, 'flowcommerce', 'allocation', line_items.sum(:quantity)].join('-')
14
+ end
15
+
16
+ def display_total
17
+ return unless flow_data&.[]('order')
18
+
19
+ Spree::Money.new(flow_io_total_amount, currency: currency)
20
+ end
21
+
22
+ def flow_order
23
+ flow_data&.[]('order')
24
+ end
25
+
26
+ def flow_order_with_payments?
27
+ payment = payments.completed.first
28
+
29
+ payment&.payment_method&.type == 'Spree::Gateway::FlowIo'
30
+ end
31
+
32
+ # accepts line item, usually called from views
33
+ def flow_line_item_price(line_item, total = false)
34
+ result = if (order = flow_order)
35
+ item = order['lines']&.find { |el| el['item_number'] == line_item.variant.sku }
36
+
37
+ return 'n/a' unless item
38
+
39
+ total ? item['total']['label'] : item['price']['label']
40
+ else
41
+ FlowcommerceSpree::Api.format_default_price(line_item.price * (total ? line_item.quantity : 1))
42
+ end
43
+
44
+ # add line item promo
45
+ # promo_total, adjustment_total
46
+ result += " (#{FlowcommerceSpree::Api.format_default_price(line_item.promo_total)})" if line_item.promo_total > 0
47
+
48
+ result
49
+ end
50
+
51
+ # shows localized total, if possible. if not, fall back to Spree default
52
+ def flow_io_total_amount
53
+ flow_data&.dig('order', 'total', 'amount')&.to_d || 0
54
+ end
55
+
56
+ def flow_io_experience_key
57
+ flow_data&.[]('exp')
58
+ end
59
+
60
+ def flow_io_experience_from_zone
61
+ self.flow_data = (flow_data || {}).merge!('exp' => zone.flow_io_experience)
62
+ end
63
+
64
+ def flow_io_order_id
65
+ flow_data&.dig('order', 'id')
66
+ end
67
+
68
+ def flow_io_attributes
69
+ flow_data&.dig('order', 'attributes') || {}
70
+ end
71
+
72
+ def flow_io_attribute_add(attr_key, value)
73
+ self.flow_data['order'] ||= {}
74
+ self.flow_data['order']['attributes'] ||= {}
75
+ self.flow_data['order']['attributes'][attr_key] = value
76
+ end
77
+
78
+ def add_user_uuid_to_flow_data
79
+ self.flow_data['order'] ||= {}
80
+ self.flow_data['order']['attributes'] ||= {}
81
+ self.flow_data['order']['attributes']['user_uuid'] = user&.uuid || ''
82
+ end
83
+
84
+ def flow_io_attr_user_uuid
85
+ flow_data&.dig('order', 'attributes', 'user_uuid')
86
+ end
87
+
88
+ def flow_io_captures
89
+ flow_data&.[]('captures')
90
+ end
91
+
92
+ def flow_io_captures_sum
93
+ captures_sum = 0
94
+ flow_data&.[]('captures')&.each do |c|
95
+ next if c['status'] != 'succeeded'
96
+
97
+ amount = c['amount']
98
+ amount = amount.to_d if amount.is_a?(String)
99
+ captures_sum += amount
100
+ end
101
+ captures_sum.to_d
102
+ end
103
+
104
+ def flow_io_balance_amount
105
+ flow_data&.dig('order', 'balance', 'amount')&.to_d || 0
106
+ end
107
+
108
+ def flow_io_payments
109
+ flow_data.dig('order', 'payments')
110
+ end
111
+
112
+ def flow_customer_email
113
+ flow_data.dig('order', 'customer', 'email')
114
+ end
115
+
116
+ def flow_ship_address
117
+ flow_destination = flow_data.dig('order', 'destination')
118
+ return unless flow_destination.present?
119
+
120
+ flow_destination['first'] = flow_destination.dig('contact', 'name', 'first')
121
+ flow_destination['last'] = flow_destination.dig('contact', 'name', 'last')
122
+ flow_destination['phone'] = flow_destination.dig('contact', 'phone')
123
+
124
+ s_address = ship_address || build_ship_address
125
+ s_address.prepare_from_flow_attributes(flow_destination)
126
+ s_address
127
+ end
128
+
129
+ def flow_bill_address
130
+ flow_payment_address = flow_data.dig('order', 'payments')&.last&.[]('address')
131
+ return unless flow_payment_address
132
+
133
+ flow_payment_address['first'] = flow_payment_address.dig('name', 'first')
134
+ flow_payment_address['last'] = flow_payment_address.dig('name', 'last')
135
+ flow_payment_address['phone'] = ship_address['phone']
136
+
137
+ b_address = bill_address || build_bill_address
138
+ b_address.prepare_from_flow_attributes(flow_payment_address)
139
+ b_address
140
+ end
141
+
142
+ def prepare_flow_addresses
143
+ address_attributes = {}
144
+
145
+ s_address = flow_ship_address
146
+
147
+ if s_address&.changes&.any?
148
+ s_address.save!
149
+ address_attributes[:ship_address_id] = s_address.id unless ship_address_id
150
+ end
151
+
152
+ b_address = flow_bill_address
153
+ if b_address&.changes&.any?
154
+ b_address.save!
155
+ address_attributes[:bill_address_id] = b_address.id unless bill_address_id
156
+ end
157
+
158
+ address_attributes
159
+ end
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
+
179
+ Spree::Order.include(self) if Spree::Order.included_modules.exclude?(self)
180
+ end
181
+ end