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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c89d8869da7867a7ae0400dab1005c763c5c98f001336bf915f14743bd279c7a
|
4
|
+
data.tar.gz: f664b4f9f7380860b5c5eec7cf5aeda85d59c8b6d2753830f553ccc8734718c4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
24
|
-
[Flow
|
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.
|
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
|
-
|
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
|
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
|
-
|
34
|
-
|
35
|
-
|
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['
|
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
|
9
|
+
# forward incoming requests to respective Flow Webhooks Service objects
|
9
10
|
# /flow/event-target endpoint
|
10
|
-
def
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
20
|
-
|
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
|
27
|
+
def organization_valid?
|
31
28
|
org = params[:organization]
|
32
|
-
return
|
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
|
8
|
-
|
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?
|
@@ -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']
|
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
|
-
|
15
|
-
tax_for_item(item
|
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
|
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
|
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
|
60
|
+
def tax_for_item(item)
|
61
|
+
order = item.order
|
48
62
|
prev_tax_amount = prev_tax_amount(item)
|
49
|
-
|
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
|
-
|
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
|
-
|
22
|
+
preferred_charge_default
|
20
23
|
end
|
21
24
|
|
22
25
|
def threshold
|
23
|
-
|
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
|