flowcommerce_spree 0.0.3 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlowcommerceSpree
4
+ class OrderUpdater
5
+ def initialize(order:)
6
+ unless order&.zone&.flow_io_active_or_archiving_experience?
7
+ raise(ArgumentError, 'Experience not defined or not active')
8
+ end
9
+
10
+ @experience = order.flow_io_experience_key
11
+ @order = order
12
+ @client = FlowcommerceSpree.client
13
+ end
14
+
15
+ def upsert_data(flow_io_order = nil)
16
+ return if @order.state == 'complete'
17
+
18
+ flow_io_order ||= @client.orders.get_by_number(FlowcommerceSpree::ORGANIZATION, @order.number).to_hash
19
+
20
+ @order.flow_data['order'] = flow_io_order
21
+ return if @order.flow_data.dig('order', 'submitted_at').blank?
22
+
23
+ attrs_to_update = { meta: @order.meta.to_json, email: @order.flow_customer_email, payment_state: 'pending' }
24
+ attrs_to_update.merge!(@order.prepare_flow_addresses)
25
+ @order.update_columns(attrs_to_update)
26
+ @order.state = 'delivery'
27
+ @order.save!
28
+ @order.create_proposed_shipments
29
+ @order.shipment.update_amounts
30
+ @order.line_items.each(&:store_ets)
31
+ @order.charge_taxes
32
+
33
+ @order.state = 'payment'
34
+ @order.save!
35
+ end
36
+
37
+ def finalize_order
38
+ @order.reload
39
+ @order.finalize!
40
+ @order.update_totals
41
+ @order.save
42
+ @order.after_completed_order
43
+ end
44
+
45
+ def complete_checkout
46
+ upsert_data
47
+ map_payments_to_spree
48
+ finalize_order if @order.state == 'complete'
49
+ end
50
+
51
+ def map_payments_to_spree
52
+ @order.flow_io_payments&.each do |p|
53
+ payment =
54
+ @order.payments.find_or_initialize_by(response_code: p['reference'], payment_method_id: payment_method_id)
55
+ next unless payment.new_record?
56
+
57
+ payment.amount = p.dig('total', 'amount')
58
+ payment.pend
59
+
60
+ # For now this additional update is overwriting the generated identifier with flow.io payment identifier.
61
+ # TODO: Check and possibly refactor in Spree 3.0, where the `before_create :set_unique_identifier`
62
+ # has been removed.
63
+ payment.update_column(:identifier, p['id'])
64
+ end
65
+
66
+ return if @order.payments.sum(:amount) < @order.amount || @order.state == 'complete'
67
+
68
+ @order.state = 'confirm'
69
+ @order.save!
70
+ @order.state = 'complete'
71
+ @order.save!
72
+ end
73
+
74
+ def payment_method_id
75
+ @payment_method_id ||= Spree::PaymentMethod.find_by(active: true, type: 'Spree::Gateway::FlowIo').id
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlowcommerceSpree
4
+ module Webhooks
5
+ class CaptureUpsertedV2
6
+ attr_reader :errors
7
+ alias full_messages errors
8
+
9
+ def self.process(data)
10
+ new(data).process
11
+ end
12
+
13
+ def initialize(data)
14
+ @data = data
15
+ @errors = []
16
+ end
17
+
18
+ def process
19
+ errors << { message: 'Capture param missing' } && (return self) unless (capture = @data['capture']&.to_hash)
20
+
21
+ order_number = capture.dig('authorization', 'order', 'number')
22
+ errors << { message: 'Order number param missing' } && (return self) unless order_number
23
+
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
29
+ else
30
+ errors << { message: "Order #{order_number} not found" }
31
+ self
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def upsert_order_captures(order, capture)
38
+ order.flow_data ||= {}
39
+ order.flow_data['captures'] ||= []
40
+ order_captures = order.flow_data['captures']
41
+ order_captures.delete_if { |c| c['id'] == capture['id'] }
42
+ order_captures << capture
43
+ order.update_column(:meta, order.meta.to_json)
44
+ end
45
+
46
+ def map_payment_captures_to_spree(order, payments)
47
+ order.flow_data['captures']&.each do |c|
48
+ next unless (payment = captured_payment(payments, c))
49
+
50
+ payment.capture_events.create!(amount: c['amount'], meta: { 'flow_data' => { 'id' => c['id'] } })
51
+ return if payment.completed? || payment.capture_events.sum(:amount) < payment.amount
52
+
53
+ payment.complete
54
+ end
55
+
56
+ return if order.completed?
57
+ return unless order.flow_io_captures_sum >= order.flow_io_total_amount && order.flow_io_balance_amount <= 0
58
+
59
+ FlowcommerceSpree::OrderUpdater.new(order: order).finalize_order
60
+ end
61
+
62
+ def captured_payment(flow_order_payments, capture)
63
+ return unless capture['status'] == 'succeeded'
64
+
65
+ auth = capture.dig('authorization', 'id')
66
+ return unless flow_order_payments&.find { |p| p['reference'] == auth }
67
+
68
+ return unless (payment = Spree::Payment.find_by(response_code: auth))
69
+
70
+ return if Spree::PaymentCaptureEvent.where("meta -> 'flow_data' ->> 'id' = ?", capture['id']).exists?
71
+
72
+ payment
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlowcommerceSpree
4
+ module Webhooks
5
+ class CardAuthorizationUpsertedV2
6
+ attr_reader :errors
7
+ alias full_messages errors
8
+
9
+ def self.process(data)
10
+ new(data).process
11
+ end
12
+
13
+ def initialize(data)
14
+ @data = data['authorization']&.to_hash
15
+ @data&.[]('method')&.delete('images')
16
+ @errors = []
17
+ end
18
+
19
+ def process
20
+ errors << { message: 'Authorization param missing' } && (return self) unless @data
21
+
22
+ errors << { message: 'Card param missing' } && (return self) unless (flow_io_card = @data.delete('card'))
23
+
24
+ if (order_number = @data.dig('order', 'number'))
25
+ if (order = Spree::Order.find_by(number: order_number))
26
+ card = upsert_card(flow_io_card, order)
27
+
28
+ order.payments.where(response_code: @data['id'])
29
+ .update_all(source_id: card.id, source_type: 'Spree::CreditCard')
30
+
31
+ return card
32
+ else
33
+ errors << { message: "Order #{order_number} not found" }
34
+ end
35
+ else
36
+ errors << { message: 'Order number param missing' }
37
+ end
38
+
39
+ self
40
+ end
41
+
42
+ private
43
+
44
+ def upsert_card(flow_io_card, order)
45
+ flow_io_card_expiration = flow_io_card.delete('expiration')
46
+
47
+ card = Spree::CreditCard.find_or_initialize_by(month: flow_io_card_expiration['month'].to_s,
48
+ year: flow_io_card_expiration['year'].to_s,
49
+ cc_type: flow_io_card.delete('type'),
50
+ last_digits: flow_io_card.delete('last4'),
51
+ name: flow_io_card.delete('name'),
52
+ user_id: order.user&.id)
53
+ card.flow_data ||= {}
54
+ if card.new_record?
55
+ card.flow_data.merge!(flow_io_card.except('discriminator'))
56
+ card.imported = true
57
+ end
58
+
59
+ card.push_authorization(@data.except('discriminator'))
60
+ card.new_record? ? card.save : card.update_column(:meta, card.meta.to_json)
61
+
62
+ card
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlowcommerceSpree
4
+ module Webhooks
5
+ class ExperienceUpsertedV2
6
+ attr_accessor :errors
7
+ alias full_messages errors
8
+
9
+ def self.process(data, opts = {})
10
+ new(data, opts).process
11
+ end
12
+
13
+ def initialize(data, opts = {})
14
+ @data = data
15
+ @opts = opts
16
+ @errors = []
17
+ end
18
+
19
+ def process
20
+ experience = @data['experience']
21
+ Spree::Zones::Product.find_or_initialize_by(name: experience['key'].titleize).store_flow_io_data(experience)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlowcommerceSpree
4
+ module Webhooks
5
+ class FraudStatusChanged
6
+ attr_accessor :errors
7
+ alias full_messages errors
8
+
9
+ def self.process(data, opts = {})
10
+ new(data, opts).process
11
+ end
12
+
13
+ def initialize(data, opts = {})
14
+ @data = data
15
+ @opts = opts
16
+ @errors = []
17
+ end
18
+
19
+ def process
20
+ order_number = @data.dig('order', 'number')
21
+ errors << { message: 'Order number param missing' } && (return self) unless order_number
22
+
23
+ order = Spree::Order.find_by(number: order_number)
24
+ errors << { message: "Order #{order_number} not found" } && (return self) unless order
25
+
26
+ if @data['status'] == 'declined'
27
+ order.update_columns(fraudulent: true)
28
+ order.cancel!
29
+ end
30
+
31
+ order
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlowcommerceSpree
4
+ module Webhooks
5
+ class LocalItemUpserted
6
+ attr_accessor :errors
7
+ alias full_messages errors
8
+
9
+ def self.process(data, opts = {})
10
+ new(data, opts).process
11
+ end
12
+
13
+ def initialize(data, opts = {})
14
+ @data = data
15
+ @opts = opts
16
+ @errors = []
17
+ end
18
+
19
+ def process
20
+ errors << { message: 'Local item param missing' } && (return self) unless (local_item = @data['local_item'])
21
+
22
+ errors << { message: 'SKU param missing' } && (return self) unless (flow_sku = local_item.dig('item', 'number'))
23
+
24
+ if (variant = Spree::Variant.find_by(sku: flow_sku))
25
+ variant.add_flow_io_experience_data(
26
+ local_item.dig('experience', 'key'),
27
+ 'prices' => [local_item.dig('pricing', 'price')], 'status' => local_item['status']
28
+ )
29
+
30
+ variant.update_column(:meta, variant.meta.to_json)
31
+ return variant
32
+ else
33
+ errors << { message: "Variant with sku [#{flow_sku}] not found!" }
34
+ end
35
+
36
+ self
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,21 @@
1
+ <fieldset data-hook="credit_card">
2
+ <legend align="center"><%= Spree.t(:credit_card) %></legend>
3
+
4
+ <div class="row">
5
+ <div class="alpha six columns">
6
+ <dl>
7
+ <dt><%= Spree.t(:name_on_card) %>:</dt>
8
+ <dd><%= payment.source&.name %></dd>
9
+
10
+ <dt><%= Spree.t(:card_type) %>:</dt>
11
+ <dd><%= payment.source&.cc_type %></dd>
12
+
13
+ <dt><%= Spree.t(:card_number) %>:</dt>
14
+ <dd><%= payment.source&.display_number %></dd>
15
+
16
+ <dt><%= Spree.t(:expiration) %>:</dt>
17
+ <dd><%= payment.source&.month %>/<%= payment.source&.year %></dd>
18
+ </dl>
19
+ </div>
20
+ </div>
21
+ </fieldset>
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlowcommerceSpree
4
+ class ImportItemWorker
5
+ include Sidekiq::Worker
6
+ sidekiq_options retry: 3, queue: :flow_io
7
+
8
+ sidekiq_retries_exhausted do |message, exception|
9
+ Rails.logger.warn("[!] FlowcommerceSpree::ImportItemWorker max attempts reached: #{message} - #{exception}")
10
+ notification_setting = FlowcommerceSpree::Config.notification_setting
11
+ return unless notification_setting[:slack].present?
12
+
13
+ slack_message = "[#{Rails.env}] #{message}"
14
+ Slack_client.chat_postMessage(channel: notification_setting[:slack][:channel], text: slack_message)
15
+ end
16
+
17
+ def perform(variant_sku)
18
+ variant = Spree::Variant.find_by sku: variant_sku
19
+ return unless variant
20
+
21
+ FlowcommerceSpree::ImportItem.run(variant)
22
+ end
23
+ end
24
+ end
data/config/routes.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  FlowcommerceSpree::Engine.routes.draw do
4
- post '/event-target', to: 'webhooks#handle_flow_web_hook_event'
4
+ post '/event-target', to: 'webhooks#handle_flow_io_event'
5
+ get '/order-completed', to: 'orders#order_completed'
6
+ post '/online-stock-availability', to: 'inventory#online_stock_availability'
5
7
  end
@@ -5,13 +5,15 @@ class AddMetaToSpreeTables < ActiveRecord::Migration
5
5
  add_column :spree_orders, :meta, :jsonb, default: '{}' unless column_exists?(:spree_orders, :meta)
6
6
  add_column :spree_promotions, :meta, :jsonb, default: '{}' unless column_exists?(:spree_promotions, :meta)
7
7
  add_column :spree_credit_cards, :meta, :jsonb, default: '{}' unless column_exists?(:spree_credit_cards, :meta)
8
+ add_column :spree_payment_capture_events, :meta, :jsonb, default: '{}' unless column_exists?(:spree_payment_capture_events, :meta)
8
9
  end
9
10
 
10
11
  def down
11
- remove_column :spree_products, :meta if column_exists?(:spree_products, :meta)
12
- remove_column :spree_variants, :meta if column_exists?(:spree_variants, :meta)
13
- remove_column :spree_orders, :meta if column_exists?(:spree_orders, :meta)
14
- remove_column :spree_promotions, :meta if column_exists?(:spree_promotions, :meta)
12
+ remove_column :spree_payment_capture_events, :meta if column_exists?(:spree_payment_capture_events, :meta)
15
13
  remove_column :spree_credit_cards, :meta if column_exists?(:spree_credit_cards, :meta)
14
+ remove_column :spree_promotions, :meta if column_exists?(:spree_promotions, :meta)
15
+ remove_column :spree_orders, :meta if column_exists?(:spree_orders, :meta)
16
+ remove_column :spree_variants, :meta if column_exists?(:spree_variants, :meta)
17
+ remove_column :spree_products, :meta if column_exists?(:spree_products, :meta)
16
18
  end
17
19
  end
@@ -34,42 +34,6 @@ module Flow
34
34
  error_response(e)
35
35
  end
36
36
 
37
- # capture authorised funds
38
- def cc_capture
39
- # GET /:organization/authorizations, order_number: abc
40
- data = @order.flow_data['authorization']
41
-
42
- raise ArgumentError, 'No Authorization data, please authorize first' unless data
43
-
44
- capture_form = ::Io::Flow::V0::Models::CaptureForm.new(data)
45
- response = FlowcommerceSpree.client.captures.post(FlowcommerceSpree::ORGANIZATION, capture_form)
46
-
47
- return ActiveMerchant::Billing::Response.new false, 'error', response: response unless response.id
48
-
49
- @order.update_column :flow_data, @order.flow_data.merge('capture': response.to_hash)
50
- @order.flow_finalize!
51
-
52
- ActiveMerchant::Billing::Response.new true, 'success', response: response
53
- rescue StandardError => e
54
- error_response(e)
55
- end
56
-
57
- def cc_refund
58
- raise ArgumentError, 'capture info is not available' unless @order.flow_data['capture']
59
-
60
- # we allways have capture ID, so we use it
61
- refund_data = { capture_id: @order.flow_data['capture']['id'] }
62
- refund_form = ::Io::Flow::V0::Models::RefundForm.new(refund_data)
63
- response = FlowcommerceSpree.client.refunds.post(FlowcommerceSpree::ORGANIZATION, refund_form)
64
-
65
- return ActiveMerchant::Billing::Response.new false, 'error', response: response unless response.id
66
-
67
- @order.update_column :flow_data, @order.flow_data.merge('refund': response.to_hash)
68
- ActiveMerchant::Billing::Response.new true, 'success', response: response
69
- rescue StandardError => e
70
- error_response(e)
71
- end
72
-
73
37
  private
74
38
 
75
39
  # if order is not in flow, we use local Spree settings