workarea-forter 1.2.4 → 1.3.0

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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -0
  3. data/Gemfile +1 -1
  4. data/app/models/search/admin/order.decorator +1 -1
  5. data/app/models/workarea/checkout.decorator +24 -0
  6. data/app/models/workarea/checkout/collect_payment.decorator +7 -43
  7. data/app/models/workarea/checkout/fraud/forter_analyzer.rb +57 -0
  8. data/app/models/workarea/order.decorator +0 -7
  9. data/app/models/workarea/order/fraud_decision.decorator +13 -0
  10. data/app/models/workarea/order/status/suspected_fraud.rb +3 -9
  11. data/app/models/workarea/payment.decorator +1 -1
  12. data/app/models/workarea/payment/status/suspected_fraud.rb +1 -1
  13. data/app/services/workarea/forter/order.rb +3 -3
  14. data/app/views/workarea/admin/orders/{forter.html.haml → fraud.html.haml} +4 -7
  15. data/app/workers/forter/update_status.rb +1 -1
  16. data/config/initializers/appends.rb +0 -5
  17. data/config/initializers/workarea.rb +2 -0
  18. data/config/routes.rb +1 -6
  19. data/lib/workarea/forter/version.rb +1 -1
  20. data/test/integration/workarea/admin/forter_order_integration_test.rb +5 -5
  21. data/test/integration/workarea/storefront/checkouts_integration_test.decorator +69 -0
  22. data/test/integration/workarea/storefront/forter_integration_test.rb +15 -12
  23. data/test/lib/workarea/forter/gateway_test.rb +1 -2
  24. data/test/models/workarea/checkout/forter_collect_payment_test.rb +29 -38
  25. data/test/models/workarea/checkout_test.decorator +16 -0
  26. data/test/system/workarea/admin/orders_system_test.decorator +12 -0
  27. data/test/vcr_cassettes/forter/update_status.yml +31 -26
  28. data/test/workers/forter/update_status_test.rb +4 -4
  29. data/workarea-forter.gemspec +1 -1
  30. metadata +11 -11
  31. data/app/controllers/workarea/admin/orders_controller.decorator +0 -6
  32. data/app/models/workarea/forter/decision.rb +0 -18
  33. data/app/view_models/workarea/admin/order_view_model.decorator +0 -7
  34. data/app/views/workarea/admin/orders/_forter.html.haml +0 -18
  35. data/test/system/workarea/admin/forter_system_test.rb +0 -31
  36. data/test/view_models/workarea/storefront/order_view_model_test.decorator +0 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e6c4f679107573bc38a3e4a2ec421d83e53a85907082c678ff47553c4b3f9785
4
- data.tar.gz: 1aa0edd84330d5b6fee488117362f134f38b2e2763951585cfc36ce1de1c810d
3
+ metadata.gz: c5b05cf277bf2f9053f124dcf7c7cb83096aa2aaf4def84bc5a600f50364ae6e
4
+ data.tar.gz: 626cdd36a1900d1372572f04be318ae2e57b4be60a8627427d6b0f02a047d97c
5
5
  SHA512:
6
- metadata.gz: 2813c8e8aad7976352238666d8aeccb569e443f946f726ae0d2281c3346602940449613ac1b311da9fd3d8ac86e2420eaaf488c67fdee5ffecfcdc376ad88835
7
- data.tar.gz: 877214170590717f90ac8ada8d3db7f57c84be2a62842f156cdfcbb2d7fcc26a6ca1e767a6adddaf72e85f2872c8cff9b5afa799923cc8b9b853d501628fa377
6
+ metadata.gz: 88d605bb65ff10b3764ffd4ab113c066c1534fc968bd3d57fbdd9469d31fcb1f2a92c62fdd0d6f57c86a55c383b2717ce3abb031bd2e4ae309b71ce3c4605ef2
7
+ data.tar.gz: bd6607c5a93f02a6cd60de6bc36f8000c948c59237e6acfbbf30a739e8f58b05896eeeb5b59be07efc295db3c55a6a0511b582952bfe23b3ec5de4f9832ffb78
data/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ Workarea Forter 1.3.0 (2019-11-26)
2
+ --------------------------------------------------------------------------------
3
+
4
+ * V3.5 compatability updates
5
+
6
+ Update to make the plugin compatible with the 3.5 fraud
7
+ framework. Moves the decision to post auth.
8
+ Jeff Yucis
9
+
10
+
11
+
1
12
  Workarea Forter 1.2.4 (2019-10-16)
2
13
  --------------------------------------------------------------------------------
3
14
 
data/Gemfile CHANGED
@@ -3,7 +3,7 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
3
3
 
4
4
  gemspec
5
5
 
6
- gem 'workarea', github: 'workarea-commerce/workarea', branch: 'v3.4-stable'
6
+ gem 'workarea', github: 'workarea-commerce/workarea'
7
7
 
8
8
  gem 'byebug'
9
9
  group :test do
@@ -1,7 +1,7 @@
1
1
  module Workarea
2
2
  decorate Search::Admin::Order, with: :forter do
3
3
  def should_be_indexed?
4
- model.placed? || model.flagged_for_fraud?
4
+ model.placed? || model.fraud_suspected?
5
5
  end
6
6
  end
7
7
  end
@@ -0,0 +1,24 @@
1
+ module Workarea
2
+ decorate Checkout, with: :forter do
3
+
4
+ # Forter has a post auth model of fraud analysis
5
+ # This decorator removes the check for fraud.
6
+ def place_order
7
+ return false unless complete?
8
+ return false unless shippable?
9
+ return false unless payable?
10
+
11
+ inventory.purchase
12
+ return false unless inventory.captured?
13
+
14
+ unless payment_collection.purchase
15
+ inventory.rollback
16
+ return false
17
+ end
18
+
19
+ result = order.place
20
+ place_order_side_effects if result
21
+ result
22
+ end
23
+ end
24
+ end
@@ -4,23 +4,20 @@ module Workarea
4
4
  collect_result = super
5
5
 
6
6
  begin
7
- decision = Forter::Decision.find_or_create_by(id: @order.id)
8
- order_hash = Forter::Order.new(@order).to_h
9
-
10
- response = get_decision(@order_id, order_hash, decision)
7
+ fraud_analyzer.decide!
11
8
 
12
9
  # Rollback all transactions if the original collection
13
10
  # was successful but the forter decision was a decline.
14
- if collect_result && response.suspected_fraud?
11
+ if collect_result && @order.fraud_suspected?
15
12
  payment.rollback!
16
- payment.flagged_for_fraud = true
13
+ payment.fraud_suspected = true
17
14
 
18
15
  payment.save!
19
16
  false
20
17
  else
21
18
  # need to re-add errors indicating the payment operation failed
22
19
  error_messages = payment.errors.messages.clone
23
- payment.update_attribute(:flagged_for_fraud, false)
20
+ payment.update_attribute(:fraud_suspected, false)
24
21
  error_messages.each do |attribute, message|
25
22
  payment.errors.add(attribute, message)
26
23
  end
@@ -30,46 +27,13 @@ module Workarea
30
27
  rescue => e
31
28
  Forter.log_error(e)
32
29
  return collect_result
33
- ensure
34
- decision.save!
35
30
  end
36
31
  end
37
32
 
38
33
  private
39
34
 
40
- ERROR_STATUSES = 500..599
41
- MAX_RETRIES = 2
42
-
43
- # Gets forter decision and builds decision responses on the Forter Decision
44
- # model
45
- #
46
- # @raises Error
47
- #
48
- # @return [Workarea::Forter::DecisionResponse] response
49
- #
50
- def get_decision(order_id, order_hash, decision)
51
- count = 1
52
- begin
53
- fraud_response = Forter.gateway.create_decision(@order.id, order_hash)
54
- body = JSON.parse(fraud_response.body)
55
- response = Forter::DecisionResponse.new(body)
56
-
57
- decision.responses.build(decision_response: response)
58
- raise if ERROR_STATUSES.include? fraud_response.status
59
-
60
- response
61
- rescue => error
62
- decision.responses.build(
63
- timed_out: error.is_a?(Faraday::Error::TimeoutError),
64
- error: error.message
65
- )
66
- if count < MAX_RETRIES
67
- count += 1
68
- retry
69
- else
70
- raise error
71
- end
72
- end
73
- end
35
+ def fraud_analyzer
36
+ @fraud_analyzer ||= Workarea.config.fraud_analyzer.constantize.new(@checkout)
37
+ end
74
38
  end
75
39
  end
@@ -0,0 +1,57 @@
1
+ module Workarea
2
+ class Checkout
3
+ module Fraud
4
+ class ForterAnalyzer < Analyzer
5
+ ERROR_STATUSES = 500..599
6
+ MAX_RETRIES = 2
7
+
8
+ def make_decision
9
+ decision = Workarea::Order::FraudDecision.new
10
+ order_hash = Forter::Order.new(order).to_h
11
+ count = 1
12
+
13
+ begin
14
+ fraud_response = Forter.gateway.create_decision(order.id, order_hash)
15
+ body = JSON.parse(fraud_response.body)
16
+
17
+ forter_decision_action = if body["action"] == "decline"
18
+ :declined
19
+ else
20
+ body["action"].optionize.to_sym
21
+ end
22
+
23
+ raise if ERROR_STATUSES.include? fraud_response.status
24
+
25
+ decision.message = body["message"]
26
+ decision.decision = forter_decision_action
27
+ decision.response = body
28
+
29
+ response = Forter::DecisionResponse.new(body)
30
+ decision.responses.build(decision_response: response)
31
+ decision
32
+
33
+ rescue => error
34
+ forter_error_decision.responses.build(
35
+ timed_out: error.is_a?(Faraday::Error::TimeoutError),
36
+ error: error.message
37
+ )
38
+ forter_error_decision
39
+
40
+ if count < MAX_RETRIES
41
+ count += 1
42
+ retry
43
+ else
44
+ raise error
45
+ end
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def forter_error_decision
52
+ @forter_error_decision ||= error_decision(nil)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -3,12 +3,5 @@ module Workarea
3
3
  decorated do
4
4
  field :forter_tracking_code, type: String
5
5
  end
6
-
7
- def flagged_for_fraud?
8
- decision = Workarea::Forter::Decision.find(id) rescue nil
9
- return false unless decision.present? && decision.response.present?
10
-
11
- decision.response.action == 'decline'
12
- end
13
6
  end
14
7
  end
@@ -0,0 +1,13 @@
1
+ module Workarea
2
+ decorate Order::FraudDecision, with: :forter do
3
+ decorated do
4
+ embeds_many :responses, class_name: 'Workarea::Forter::Response'
5
+ field :external_order_status, type: String, default: "PROCESSING"
6
+ end
7
+
8
+ def response
9
+ return if responses.empty?
10
+ responses.sort_by { |r| r.created_at.to_i }.last.decision_response
11
+ end
12
+ end
13
+ end
@@ -1,13 +1,7 @@
1
1
  module Workarea
2
- class Order
3
- module Status
4
- class SuspectedFraud
5
- include StatusCalculator::Status
6
-
7
- def in_status?
8
- !order.placed? && order.flagged_for_fraud? && !order.canceled?
9
- end
10
- end
2
+ decorate Order::Status::SuspectedFraud, with: :forter do
3
+ def in_status?
4
+ super && !order.placed? && !order.canceled?
11
5
  end
12
6
  end
13
7
  end
@@ -1,7 +1,7 @@
1
1
  module Workarea
2
2
  decorate Payment, with: :forter do
3
3
  decorated do
4
- field :flagged_for_fraud, type: Boolean, default: false
4
+ field :fraud_suspected, type: Boolean, default: false
5
5
  end
6
6
 
7
7
  def rollback!(options = {})
@@ -5,7 +5,7 @@ module Workarea
5
5
  include Workarea::StatusCalculator::Status
6
6
 
7
7
  def in_status?
8
- order.flagged_for_fraud?
8
+ order.fraud_suspected?
9
9
  end
10
10
  end
11
11
  end
@@ -57,8 +57,8 @@ module Workarea
57
57
  end
58
58
 
59
59
  def delivery_type
60
- return "DIGITAL" if order.items.all? { |oi| oi.digital? }
61
- return "HYBRID" if order.items.any? { |oi| oi.digital? }
60
+ return "DIGITAL" if order.items.none?(&:shipping?)
61
+ return "HYBRID" if order.items.any?(&:shipping?)
62
62
  "PHYSICAL"
63
63
  end
64
64
 
@@ -74,7 +74,7 @@ module Workarea
74
74
  basicItemData: {
75
75
  name: item.product.name,
76
76
  quantity: item.quantity,
77
- type: item.digital? ? "NON_TANGIBLE" : "TANGIBLE",
77
+ type: item.shipping? ? "TANGIBLE" : "NON_TANGIBLE",
78
78
  price: { amountUSD: item.total_value.to_s },
79
79
  productId: item.product.id
80
80
  }
@@ -1,4 +1,4 @@
1
- - @page_title = t('workarea.admin.orders.forter.page_title', id: @order.id)
1
+ - @page_title = t('workarea.admin.orders.fraud.title', id: @order.id)
2
2
 
3
3
  .view
4
4
  .view__header
@@ -11,7 +11,7 @@
11
11
  = render_aux_navigation_for(@order)
12
12
 
13
13
  .view__container
14
- = render_cards_for(@order, :forter)
14
+ = render_cards_for(@order, :fraud)
15
15
 
16
16
  .view__container.view__container--narrow
17
17
  .grid
@@ -21,7 +21,7 @@
21
21
 
22
22
  %li
23
23
  %strong= t('workarea.admin.orders.forter.status')
24
- = @order.decision.external_order_status
24
+ = @order.fraud_decision.external_order_status
25
25
  %li
26
26
  %br
27
27
  %strong= link_to t('workarea.admin.orders.forter.console'), "https://portal.forter.com/dashboard/#{@order.id}"
@@ -41,7 +41,7 @@
41
41
  %th= t('workarea.admin.orders.forter.error')
42
42
 
43
43
  %tbody
44
- - @order.decision.responses.each do |response|
44
+ - @order.fraud_decision.responses.each do |response|
45
45
  %tr
46
46
  %td= response.fraud_decision_action
47
47
  %td= response.decision_response&.body_message
@@ -52,6 +52,3 @@
52
52
  %li= e["message"]
53
53
  %td= response.error
54
54
 
55
-
56
-
57
-
@@ -4,7 +4,7 @@ module Workarea
4
4
  include Sidekiq::Worker
5
5
 
6
6
  def perform(id, status_hash)
7
- decision = Workarea::Forter::Decision.find(id) rescue nil
7
+ decision = Workarea::Order.find(id).fraud_decision rescue nil
8
8
 
9
9
  if decision.blank?
10
10
  Rails.logger.warn "No decision record found for #{id} during update status"
@@ -2,8 +2,3 @@ Workarea.append_partials(
2
2
  'storefront.javascript',
3
3
  'workarea/storefront/forter_tracking'
4
4
  )
5
-
6
- Workarea.append_partials(
7
- 'admin.order_cards',
8
- 'workarea/admin/orders/forter'
9
- )
@@ -2,6 +2,8 @@ Workarea.configure do |config|
2
2
  config.order_status_calculators.insert(0, 'Workarea::Order::Status::SuspectedFraud')
3
3
  config.payment_status_calculators.insert(0, 'Workarea::Payment::Status::SuspectedFraud')
4
4
 
5
+ config.fraud_analyzer = 'Workarea::Checkout::Fraud::ForterAnalyzer'
6
+
5
7
  config.forter = ActiveSupport::Configurable::Configuration.new
6
8
  config.forter.site_id = nil
7
9
 
data/config/routes.rb CHANGED
@@ -1,7 +1,2 @@
1
- Workarea::Admin::Engine.routes.draw do
2
- resources :orders, only: [] do
3
- member do
4
- get :forter
5
- end
6
- end
1
+ Rails.application.routes.draw do
7
2
  end
@@ -1,5 +1,5 @@
1
1
  module Workarea
2
2
  module Forter
3
- VERSION = "1.2.4"
3
+ VERSION = "1.3.0"
4
4
  end
5
5
  end
@@ -15,15 +15,15 @@ module Workarea
15
15
  def test_shows_error_decision
16
16
  order = create_placed_forter_order(email: 'error@workarea.com')
17
17
 
18
- get admin.forter_order_path(order)
19
- message = Workarea::Forter::Decision.first.response.errors.first["message"]
18
+ get admin.fraud_order_path(order)
19
+ message = order.fraud_decision.response.errors.first["message"]
20
20
  assert(response.body.include?(message))
21
21
  end
22
22
 
23
- def test_returns_flow_order_details
23
+ def test_returns_forter_order_details
24
24
  order = create_placed_forter_order(email: 'approve@workarea.com')
25
- decision = Workarea::Forter::Decision.find(order.id)
26
- get admin.forter_order_path(order)
25
+ decision = order.fraud_decision
26
+ get admin.fraud_order_path(order)
27
27
 
28
28
  assert(response.body.include?(decision.response.body_message))
29
29
  assert(response.body.include?(decision.response.action))
@@ -0,0 +1,69 @@
1
+ module Workarea
2
+ decorate Storefront::CheckoutsIntegrationTest, with: :forter do
3
+ def test_saving_fraud_decision
4
+ complete_checkout
5
+ order = Order.desc(:created_at).first
6
+
7
+ assert(order.fraud_decision.present?)
8
+ assert(order.placed?)
9
+ refute(order.fraud_suspected_at.present?)
10
+ assert(order.fraud_decided_at.present?)
11
+ assert_equal(:approve, order.fraud_decision.decision)
12
+
13
+ post storefront.cart_items_path,
14
+ params: {
15
+ product_id: product.id,
16
+ sku: product.skus.first,
17
+ quantity: 1
18
+ }
19
+
20
+ get storefront.checkout_addresses_path
21
+ patch storefront.checkout_addresses_path,
22
+ params: {
23
+ email: 'decline@workarea.com',
24
+ billing_address: {
25
+ first_name: 'Ben',
26
+ last_name: 'Crouse',
27
+ street: '12 N. 3rd St.',
28
+ city: 'Philadelphia',
29
+ region: 'PA',
30
+ postal_code: '19106',
31
+ country: 'US',
32
+ phone_number: '2159251800'
33
+ },
34
+ shipping_address: {
35
+ first_name: 'Ben',
36
+ last_name: 'Crouse',
37
+ street: '22 S. 3rd St.',
38
+ city: 'Philadelphia',
39
+ region: 'PA',
40
+ postal_code: '19106',
41
+ country: 'US',
42
+ phone_number: '2159251800'
43
+ }
44
+ }
45
+ get storefront.checkout_shipping_path
46
+ patch storefront.checkout_shipping_path
47
+
48
+ get storefront.checkout_payment_path
49
+
50
+ patch storefront.checkout_place_order_path,
51
+ params: {
52
+ payment: 'new_card',
53
+ credit_card: {
54
+ number: '1',
55
+ month: 1,
56
+ year: 2020,
57
+ cvv: '999'
58
+ }
59
+ }
60
+
61
+ order = Order.desc(:created_at).first
62
+
63
+ assert_equal(:declined, order.fraud_decision.decision)
64
+ assert(order.fraud_suspected_at.present?)
65
+ assert(order.fraud_decided_at.present?)
66
+ refute(order.placed?)
67
+ end
68
+ end
69
+ end
@@ -9,13 +9,13 @@ module Workarea
9
9
  order = Workarea::Order.last
10
10
  payment = Workarea::Payment.last
11
11
 
12
- decision = Workarea::Forter::Decision.find(order.id)
12
+ order.reload
13
+ decision = order.fraud_decision
13
14
 
14
15
  refute(decision.response.suspected_fraud?)
15
16
 
16
- order.reload
17
- refute(order.flagged_for_fraud?)
18
- refute(payment.flagged_for_fraud?)
17
+ refute(order.fraud_suspected?)
18
+ refute(payment.fraud_suspected?)
19
19
  end
20
20
 
21
21
  def test_decision_not_reviewed
@@ -24,9 +24,10 @@ module Workarea
24
24
 
25
25
  order = Workarea::Order.last
26
26
  payment = Workarea::Payment.last
27
- decision = Workarea::Forter::Decision.find(order.id)
28
27
 
29
- refute(payment.flagged_for_fraud?)
28
+ decision = order.fraud_decision
29
+
30
+ refute(payment.fraud_suspected?)
30
31
  refute(decision.response.suspected_fraud?)
31
32
  end
32
33
 
@@ -35,7 +36,9 @@ module Workarea
35
36
  complete_checkout("decline@workarea.com", 'W3blinc1')
36
37
 
37
38
  order = Workarea::Order.last
38
- decision = Workarea::Forter::Decision.find(order.id)
39
+
40
+ decision = order.fraud_decision
41
+
39
42
  assert(decision.response.suspected_fraud?)
40
43
 
41
44
  payment = Workarea::Payment.last
@@ -43,13 +46,12 @@ module Workarea
43
46
 
44
47
  assert(transaction.cancellation.present?)
45
48
 
46
- order.reload
47
49
  payment.reload
48
50
 
49
- assert(order.flagged_for_fraud?)
51
+ assert(order.fraud_suspected?)
50
52
  assert_equal(:suspected_fraud, order.status)
51
53
 
52
- assert(payment.flagged_for_fraud?)
54
+ assert(payment.fraud_suspected?)
53
55
  assert_equal(:suspected_fraud, payment.status)
54
56
  end
55
57
 
@@ -59,9 +61,10 @@ module Workarea
59
61
 
60
62
  order = Workarea::Order.last
61
63
  payment = Workarea::Payment.last
62
- decision = Workarea::Forter::Decision.find(order.id)
63
64
 
64
- refute(payment.flagged_for_fraud?)
65
+ decision = order.fraud_decision
66
+
67
+ refute(payment.fraud_suspected?)
65
68
  refute(decision.response.suspected_fraud?)
66
69
  assert(decision.response.errors.present?)
67
70
  end
@@ -16,7 +16,7 @@ module Workarea
16
16
  VCR.use_cassette("forter/get_decision", match_requests_on: [:method, :uri]) do
17
17
  order = create_placed_forter_order(id: "fortertest1234", email: "approve@forter.com")
18
18
 
19
- forter_decision = Forter::Decision.find order.id
19
+ forter_decision = order.fraud_decision
20
20
  response = forter_decision.responses.first
21
21
  assert response.decision_response.success?
22
22
  end
@@ -31,7 +31,6 @@ module Workarea
31
31
  response = gateway.create_decision(order.id, hsh)
32
32
  assert(response.success?)
33
33
 
34
-
35
34
  hsh = {
36
35
  orderId: order.id,
37
36
  eventTime: Time.new.to_i * 1000,
@@ -5,56 +5,47 @@ module Workarea
5
5
  class ForterCollectPaymentTest < TestCase
6
6
  setup :create_models
7
7
 
8
- def test_rescuing_timeout_errors
9
- Workarea::Forter::BogusGateway
10
- .any_instance
11
- .stubs(:create_decision)
12
- .raises(Faraday::Error::TimeoutError)
13
- refute(@collect_payment.purchase)
14
-
15
- forter_decision = Forter::Decision.find(@order.id)
16
- assert_equal(2, forter_decision.responses.size)
17
- forter_decision.responses.each do |response|
18
- assert response.timed_out
19
- end
8
+ def test_purchase
9
+ @collect_payment.purchase
10
+ @order.reload
11
+ forter_decision = @order.fraud_decision
12
+ assert_equal(1, forter_decision.responses.size)
13
+ assert_equal(:approve, forter_decision.decision)
20
14
  end
21
15
 
22
- def test_with_one_timeout
23
- normal_response = Forter.gateway.create_decision(@order.id, Forter::Order.new(@order).to_h)
16
+ def test_rescuing_timeout_errors
24
17
  Workarea::Forter::BogusGateway
25
18
  .any_instance
26
19
  .stubs(:create_decision)
27
20
  .raises(Faraday::Error::TimeoutError)
28
- .then
29
- .returns(normal_response)
30
-
31
21
  refute(@collect_payment.purchase)
32
- forter_decision = Forter::Decision.find(@order.id)
33
- assert_equal(2, forter_decision.responses.size)
34
22
 
35
- assert(forter_decision.responses.first.timed_out)
36
- refute(forter_decision.responses.second.timed_out)
23
+ @order.reload
24
+ forter_decision = @order.fraud_decision
25
+ assert_equal(:no_decision, forter_decision.decision)
26
+
27
+ assert_equal("An error occured during the fraud check: timeout", forter_decision.message)
37
28
  end
38
29
 
39
30
  private
40
31
 
41
- def create_models
42
- @order = Order.create!(email: 'test@workarea.com', total_price: 5.to_m)
43
- @checkout = Checkout.new(@order)
44
- @payment = Payment.create!(
45
- id: @order.id,
46
- address: {
47
- first_name: "Ben",
48
- last_name: "Crouse",
49
- street: "22 S 3rd St",
50
- city: "Philadelphia",
51
- region: "PA",
52
- country: Country['US'],
53
- postal_code: 19106
54
- }
55
- )
56
- @collect_payment = CollectPayment.new(@checkout)
57
- end
32
+ def create_models
33
+ @order = Order.create!(email: 'test@workarea.com', total_price: 5.to_m)
34
+ @checkout = Checkout.new(@order)
35
+ @payment = Payment.create!(
36
+ id: @order.id,
37
+ address: {
38
+ first_name: "Ben",
39
+ last_name: "Crouse",
40
+ street: "22 S 3rd St",
41
+ city: "Philadelphia",
42
+ region: "PA",
43
+ country: Country['US'],
44
+ postal_code: 19106
45
+ }
46
+ )
47
+ @collect_payment = CollectPayment.new(@checkout)
48
+ end
58
49
  end
59
50
  end
60
51
  end
@@ -0,0 +1,16 @@
1
+ module Workarea
2
+ decorate CheckoutTest, with: :forter do
3
+ def test_place_order_fails_for_fraud
4
+ @order.email = 'decline@workarea.com'
5
+ checkout = Checkout.new(@order)
6
+
7
+ checkout.expects(:complete?).returns(true)
8
+ checkout.expects(:shippable?).returns(true)
9
+ checkout.expects(:payable?).returns(true)
10
+ checkout.inventory.expects(:purchase).once
11
+
12
+ refute(checkout.place_order)
13
+ refute(@order.reload.placed?)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,12 @@
1
+ module Workarea
2
+ decorate Admin::OrdersSystemTest, with: :forter do
3
+ def test_fraud
4
+ order = create_placed_forter_order(email: 'decline@workarea.com')
5
+ visit admin.order_path(order)
6
+ click_link t('workarea.admin.orders.attributes.fraud.title')
7
+
8
+ assert(page.has_content?('decline')) # decision
9
+ assert(page.has_content?(order.fraud_decision.message))
10
+ end
11
+ end
12
+ end
@@ -5,38 +5,41 @@ http_interactions:
5
5
  uri: https://api.forter-secure.com/v2/orders/statusfortertest12345
6
6
  body:
7
7
  encoding: UTF-8
8
- string: '{"orderId":"statusfortertest12345","orderType":"WEB","timeSentToForter":1546030401000,"checkoutTime":1546030397,"primaryRecipient":{"personalDetails":{"firstName":"Ben","lastName":"Crouse"},"address":{"address1":"22
8
+ string: '{"orderId":"statusfortertest12345","orderType":"WEB","timeSentToForter":1574090964000,"checkoutTime":1574090960,"primaryRecipient":{"personalDetails":{"firstName":"Ben","lastName":"Crouse"},"address":{"address1":"22
9
9
  S. 3rd St.","city":"Philadelphia","country":"US","address2":"Second Floor","zip":"19106","region":"PA"},"phone":[{"phone":""}]},"totalAmount":{"amountUSD":"11.00"},"connectionInformation":{"customerIP":"127.0.0.1","userAgent":"Mozilla/5.0
10
10
  (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0 Mozilla/5.0
11
- (Macintosh; Intel Mac OS X x.y; rv:42.0) Gecko/20100101 Firefox/42.0."},"primaryDeliveryDetails":{"deliveryType":"PHYSICAL","deliveryMethod":"Test
11
+ (Macintosh; Intel Mac OS X x.y; rv:42.0) Gecko/20100101 Firefox/42.0.","forterTokenCookie":"123ABC"},"primaryDeliveryDetails":{"deliveryType":"PHYSICAL","deliveryMethod":"Test
12
12
  0","deliveryPrice":{"amountUSD":"1.00"},"carrier":null},"cartItems":[{"basicItemData":{"name":"Test
13
- Product","quantity":2,"type":"TANGIBLE","price":{"amountUSD":"10.00"},"productId":"BCE0E155EF"}}],"payment":[{"billingDetails":{"personalDetails":{"firstName":"Ben","lastName":"Crouse"},"address":{"address1":"22
14
- S. 3rd St.","address2":"Second Floor","city":"Philadelphia","country":"US","zip":"19106","region":"PA"},"phone":[{"phone":""}]},"tokenizedCard":{"token":"1","lastFourDigits":"1111","expirationMonth":"01","expirationYear":"2019","cardBrand":"Visa","nameOnCard":"Ben
15
- Crouse"}}],"accountOwner":{"firstName":"Ben","lastName":"Crouse","email":"approve@forter.com"}}'
13
+ Product","quantity":2,"type":"TANGIBLE","price":{"amountUSD":"10.00"},"productId":"BE6FEF44CE"}}],"payment":[{"creditCard":{"bin":"411111","lastFourDigits":"1111","expirationMonth":"01","expirationYear":"2020","cardBrand":"Visa","nameOnCard":"Ben
14
+ Crouse","paymentGatewayData":{"gatewayName":"not specified","gatewayTransactionId":"53433"},"verificationResults":{"avsFullResult":"","cvvResult":"","authorizationCode":"53433","processorResponseText":"Bogus
15
+ Gateway: Forced success","processorResponseCode":""}},"billingDetails":{"personalDetails":{"firstName":"Ben","lastName":"Crouse"},"address":{"address1":"12
16
+ N. 3rd St.","address2":"thrid floor","city":"Philadelphia","country":"US","zip":"19106","region":"PA"},"phone":[{"phone":""}]},"amount":{"amountUSD":"11.00"}}],"accountOwner":{"firstName":"Ben","lastName":"Crouse","email":"approve@forter.com"},"totalDiscount":null}'
16
17
  headers:
17
18
  Content-Type:
18
19
  - application/json
20
+ X-Forter-Siteid:
21
+ - 4d12ac5d794c
19
22
  Api-Version:
20
- - '2.2'
23
+ - '2.3'
21
24
  User-Agent:
22
25
  - Faraday v0.15.4
23
26
  Authorization:
24
- - Basic a
27
+ - Basic ODZmMjFiZWU3ZTNiMmU1MWY2YTk5NmIxZTYxNTFmOTA4ZDY1ZWQ0Zjo=
25
28
  Accept-Encoding:
26
29
  - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
27
30
  Accept:
28
31
  - "*/*"
29
32
  response:
30
33
  status:
31
- code: 401
32
- message: Unauthorized
34
+ code: 200
35
+ message: OK
33
36
  headers:
34
37
  Date:
35
- - Fri, 28 Dec 2018 20:53:21 GMT
38
+ - Mon, 18 Nov 2019 15:29:28 GMT
36
39
  Content-Type:
37
40
  - application/json; charset=utf-8
38
41
  Content-Length:
39
- - '58'
42
+ - '186'
40
43
  Connection:
41
44
  - keep-alive
42
45
  Server:
@@ -52,27 +55,29 @@ http_interactions:
52
55
  Expires:
53
56
  - '0'
54
57
  Etag:
55
- - W/"3a-ZqDAtEqlVbaEf6c/ziI5qiLtZfk"
58
+ - W/"ba-Ef/isk7mEOWPRXN9i4trCjru7vk"
56
59
  Vary:
57
60
  - Accept-Encoding
58
61
  body:
59
62
  encoding: UTF-8
60
- string: '{"status":"failed","message":"unauthorized, invalid site"}'
63
+ string: '{"status":"success","transaction":"statusfortertest12345","action":"approve","message":"
64
+ | Link in portal: https://portal.forter.com/dashboard/statusfortertest12345","reasonCode":"Test"}'
61
65
  http_version:
62
- recorded_at: Fri, 28 Dec 2018 20:53:21 GMT
66
+ recorded_at: Mon, 18 Nov 2019 15:29:28 GMT
63
67
  - request:
64
68
  method: post
65
69
  uri: https://api.forter-secure.com/v2/orders/statusfortertest12345
66
70
  body:
67
71
  encoding: UTF-8
68
- string: '{"orderId":"statusfortertest12345","orderType":"WEB","timeSentToForter":1546030401000,"checkoutTime":1546030397,"primaryRecipient":{"personalDetails":{"firstName":"Ben","lastName":"Crouse"},"address":{"address1":"22
72
+ string: '{"orderId":"statusfortertest12345","orderType":"WEB","timeSentToForter":1574090968000,"checkoutTime":1574090968,"primaryRecipient":{"personalDetails":{"firstName":"Ben","lastName":"Crouse"},"address":{"address1":"22
69
73
  S. 3rd St.","city":"Philadelphia","country":"US","address2":"Second Floor","zip":"19106","region":"PA"},"phone":[{"phone":""}]},"totalAmount":{"amountUSD":"11.00"},"connectionInformation":{"customerIP":"127.0.0.1","userAgent":"Mozilla/5.0
70
74
  (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0 Mozilla/5.0
71
- (Macintosh; Intel Mac OS X x.y; rv:42.0) Gecko/20100101 Firefox/42.0."},"primaryDeliveryDetails":{"deliveryType":"PHYSICAL","deliveryMethod":"Test
75
+ (Macintosh; Intel Mac OS X x.y; rv:42.0) Gecko/20100101 Firefox/42.0.","forterTokenCookie":"123ABC"},"primaryDeliveryDetails":{"deliveryType":"PHYSICAL","deliveryMethod":"Test
72
76
  0","deliveryPrice":{"amountUSD":"1.00"},"carrier":null},"cartItems":[{"basicItemData":{"name":"Test
73
- Product","quantity":2,"type":"TANGIBLE","price":{"amountUSD":"10.00"},"productId":"BCE0E155EF"}}],"payment":[{"billingDetails":{"personalDetails":{"firstName":"Ben","lastName":"Crouse"},"address":{"address1":"22
74
- S. 3rd St.","address2":"Second Floor","city":"Philadelphia","country":"US","zip":"19106","region":"PA"},"phone":[{"phone":""}]},"tokenizedCard":{"token":"1","lastFourDigits":"1111","expirationMonth":"01","expirationYear":"2019","cardBrand":"Visa","nameOnCard":"Ben
75
- Crouse"}}],"accountOwner":{"firstName":"Ben","lastName":"Crouse","email":"approve@forter.com"}}'
77
+ Product","quantity":2,"type":"TANGIBLE","price":{"amountUSD":"10.00"},"productId":"BE6FEF44CE"}}],"payment":[{"creditCard":{"bin":"411111","lastFourDigits":"1111","expirationMonth":"01","expirationYear":"2020","cardBrand":"Visa","nameOnCard":"Ben
78
+ Crouse","paymentGatewayData":{"gatewayName":"not specified","gatewayTransactionId":"53433"},"verificationResults":{"avsFullResult":"","cvvResult":"","authorizationCode":"53433","processorResponseText":"Bogus
79
+ Gateway: Forced success","processorResponseCode":""}},"billingDetails":{"personalDetails":{"firstName":"Ben","lastName":"Crouse"},"address":{"address1":"12
80
+ N. 3rd St.","address2":"thrid floor","city":"Philadelphia","country":"US","zip":"19106","region":"PA"},"phone":[{"phone":""}]},"amount":{"amountUSD":"11.00"}}],"accountOwner":{"firstName":"Ben","lastName":"Crouse","email":"approve@forter.com"},"totalDiscount":null}'
76
81
  headers:
77
82
  Content-Type:
78
83
  - application/json
@@ -83,7 +88,7 @@ http_interactions:
83
88
  User-Agent:
84
89
  - Faraday v0.15.4
85
90
  Authorization:
86
- - Basic a
91
+ - Basic ODZmMjFiZWU3ZTNiMmU1MWY2YTk5NmIxZTYxNTFmOTA4ZDY1ZWQ0Zjo=
87
92
  Accept-Encoding:
88
93
  - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
89
94
  Accept:
@@ -94,7 +99,7 @@ http_interactions:
94
99
  message: OK
95
100
  headers:
96
101
  Date:
97
- - Fri, 28 Dec 2018 20:53:21 GMT
102
+ - Mon, 18 Nov 2019 15:29:37 GMT
98
103
  Content-Type:
99
104
  - application/json; charset=utf-8
100
105
  Content-Length:
@@ -122,13 +127,13 @@ http_interactions:
122
127
  string: '{"status":"success","transaction":"statusfortertest12345","action":"approve","message":"
123
128
  | Link in portal: https://portal.forter.com/dashboard/statusfortertest12345","reasonCode":"Test"}'
124
129
  http_version:
125
- recorded_at: Fri, 28 Dec 2018 20:53:21 GMT
130
+ recorded_at: Mon, 18 Nov 2019 15:29:37 GMT
126
131
  - request:
127
132
  method: put
128
133
  uri: https://api.forter-secure.com/v2/status/statusfortertest12345
129
134
  body:
130
135
  encoding: UTF-8
131
- string: '{"orderId":"statusfortertest12345","eventTime":1546030401000,"updatedStatus":"CANCELED_BY_MERCHANT"}'
136
+ string: '{"orderId":"statusfortertest12345","eventTime":1574090977000,"updatedStatus":"CANCELED_BY_MERCHANT"}'
132
137
  headers:
133
138
  Content-Type:
134
139
  - application/json
@@ -139,7 +144,7 @@ http_interactions:
139
144
  User-Agent:
140
145
  - Faraday v0.15.4
141
146
  Authorization:
142
- - Basic a
147
+ - Basic ODZmMjFiZWU3ZTNiMmU1MWY2YTk5NmIxZTYxNTFmOTA4ZDY1ZWQ0Zjo=
143
148
  Accept-Encoding:
144
149
  - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
145
150
  Accept:
@@ -150,7 +155,7 @@ http_interactions:
150
155
  message: OK
151
156
  headers:
152
157
  Date:
153
- - Fri, 28 Dec 2018 20:53:21 GMT
158
+ - Mon, 18 Nov 2019 15:29:38 GMT
154
159
  Content-Type:
155
160
  - application/json; charset=utf-8
156
161
  Content-Length:
@@ -178,5 +183,5 @@ http_interactions:
178
183
  string: '{"status":"success","message":"Transaction #statusfortertest12345 status
179
184
  recieved"}'
180
185
  http_version:
181
- recorded_at: Fri, 28 Dec 2018 20:53:21 GMT
186
+ recorded_at: Mon, 18 Nov 2019 15:29:38 GMT
182
187
  recorded_with: VCR 2.9.3
@@ -3,7 +3,7 @@ require 'test_helper'
3
3
  module Workarea
4
4
  class Forter::UpdateStatusTest < TestCase
5
5
  def test_decision_status_update
6
- decision = Workarea::Forter::Decision.create(id: '1234')
6
+ order = create_placed_order
7
7
 
8
8
  details_hash = {
9
9
  orderId: 1234,
@@ -11,11 +11,11 @@ module Workarea
11
11
  updatedStatus: "CANCELED_BY_MERCHANT"
12
12
  }
13
13
 
14
- Workarea::Forter::UpdateStatus.new.perform(decision.id, details_hash)
14
+ Workarea::Forter::UpdateStatus.new.perform(order.id, details_hash)
15
15
 
16
- decision.reload
16
+ order.reload
17
17
 
18
- assert_equal("CANCELED_BY_MERCHANT", decision.external_order_status)
18
+ assert_equal("CANCELED_BY_MERCHANT", order.fraud_decision.external_order_status)
19
19
  end
20
20
  end
21
21
  end
@@ -13,6 +13,6 @@ Gem::Specification.new do |s|
13
13
  s.files = `git ls-files`.split("\n")
14
14
  s.license = 'Business Software License'
15
15
 
16
- s.add_dependency 'workarea', '~> 3.x', '>= 3.4'
16
+ s.add_dependency 'workarea', '~> 3.x', '>= 3.5'
17
17
  s.add_dependency 'faraday'
18
18
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: workarea-forter
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.4
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeff Yucis
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-10-16 00:00:00.000000000 Z
11
+ date: 2019-11-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: workarea
@@ -19,7 +19,7 @@ dependencies:
19
19
  version: 3.x
20
20
  - - ">="
21
21
  - !ruby/object:Gem::Version
22
- version: '3.4'
22
+ version: '3.5'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -29,7 +29,7 @@ dependencies:
29
29
  version: 3.x
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
- version: '3.4'
32
+ version: '3.5'
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: faraday
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -67,14 +67,15 @@ files:
67
67
  - LICENSE
68
68
  - README.md
69
69
  - Rakefile
70
- - app/controllers/workarea/admin/orders_controller.decorator
71
70
  - app/controllers/workarea/storefront/application_controller.decorator
72
71
  - app/models/search/admin/order.decorator
72
+ - app/models/workarea/checkout.decorator
73
73
  - app/models/workarea/checkout/collect_payment.decorator
74
- - app/models/workarea/forter/decision.rb
74
+ - app/models/workarea/checkout/fraud/forter_analyzer.rb
75
75
  - app/models/workarea/forter/response.rb
76
76
  - app/models/workarea/fulfillment.decorator
77
77
  - app/models/workarea/order.decorator
78
+ - app/models/workarea/order/fraud_decision.decorator
78
79
  - app/models/workarea/order/status/suspected_fraud.rb
79
80
  - app/models/workarea/payment.decorator
80
81
  - app/models/workarea/payment/saved_credit_card.decorator
@@ -88,9 +89,7 @@ files:
88
89
  - app/services/workarea/forter/tender/gift_card.rb
89
90
  - app/services/workarea/forter/tender/paypal.rb
90
91
  - app/services/workarea/forter/tender/store_credit.rb
91
- - app/view_models/workarea/admin/order_view_model.decorator
92
- - app/views/workarea/admin/orders/_forter.html.haml
93
- - app/views/workarea/admin/orders/forter.html.haml
92
+ - app/views/workarea/admin/orders/fraud.html.haml
94
93
  - app/views/workarea/storefront/_forter_tracking.html.haml
95
94
  - app/workers/forter/update_status.rb
96
95
  - app/workers/workarea/save_user_order_details.decorator
@@ -144,15 +143,17 @@ files:
144
143
  - test/integration/workarea/forter_cybersource_response_code_integration_test.rb
145
144
  - test/integration/workarea/forter_moneris_response_code_integration_test.rb
146
145
  - test/integration/workarea/forter_payflow_pro_response_code_test.rb
146
+ - test/integration/workarea/storefront/checkouts_integration_test.decorator
147
147
  - test/integration/workarea/storefront/forter_integration_test.rb
148
148
  - test/lib/workarea/forter/gateway_test.rb
149
149
  - test/models/workarea/checkout/forter_collect_payment_test.rb
150
+ - test/models/workarea/checkout_test.decorator
150
151
  - test/models/workarea/forter_payment_test.rb
151
152
  - test/models/workarea/payment/forter_credit_card_test.rb
152
153
  - test/models/workarea/payment/forter_saved_credit_card_test.rb
153
154
  - test/services/workarea/forter/order_test.rb
154
155
  - test/support/workarea/forter_api_config.rb
155
- - test/system/workarea/admin/forter_system_test.rb
156
+ - test/system/workarea/admin/orders_system_test.decorator
156
157
  - test/system/workarea/storefront/forter_braintree_checkout_system_test.rb
157
158
  - test/system/workarea/storefront/forter_tracking_system_test.rb
158
159
  - test/system/workarea/storefront/guest_checkout_system_test.decorator
@@ -169,7 +170,6 @@ files:
169
170
  - test/vcr_cassettes/forter/system/braintree_declined.yml
170
171
  - test/vcr_cassettes/forter/system/braintree_not_reviewed.yml
171
172
  - test/vcr_cassettes/forter/update_status.yml
172
- - test/view_models/workarea/storefront/order_view_model_test.decorator
173
173
  - test/workers/forter/update_status_test.rb
174
174
  - workarea-forter.gemspec
175
175
  homepage: https://github.com/workarea-commerce/workarea-forter
@@ -1,6 +0,0 @@
1
- module Workarea
2
- decorate Admin::OrdersController, with: :forter do
3
- def fraud
4
- end
5
- end
6
- end
@@ -1,18 +0,0 @@
1
- module Workarea
2
- module Forter
3
- class Decision
4
- include ApplicationDocument
5
-
6
- embeds_many :responses, class_name: 'Workarea::Forter::Response'
7
- field :external_order_status, type: String, default: "PROCESSING"
8
-
9
- index(created_at: -1)
10
-
11
- # the last response returned from the forter service.
12
- def response
13
- return if responses.empty?
14
- responses.sort_by { |r| r.created_at.to_i }.last.decision_response
15
- end
16
- end
17
- end
18
- end
@@ -1,7 +0,0 @@
1
- module Workarea
2
- decorate Admin::OrderViewModel, with: :forter do
3
- def decision
4
- @decision = Workarea::Forter::Decision.find(model.id) rescue nil
5
- end
6
- end
7
- end
@@ -1,18 +0,0 @@
1
- - if order.decision.present?
2
- .grid__cell
3
- .card{ class: card_classes(:forter, local_assigns[:active]) }
4
- = link_to forter_order_path(order), class: 'card__header' do
5
- %span.card__header-text= t('workarea.admin.orders.cards.forter.title')
6
- = inline_svg 'workarea/admin/icons/link.svg', class: 'card__icon'
7
-
8
- - if local_assigns[:active].blank?
9
- .card__body
10
- %ul.list-reset
11
- %li
12
- %strong= t('workarea.admin.orders.cards.forter.decision')
13
- = order.decision.response&.action
14
-
15
- - if order.decision.response&.reason_code.present?
16
- %li
17
- %strong= t('workarea.admin.orders.cards.forter.reason_code')
18
- = order.decision.response.reason_code
@@ -1,31 +0,0 @@
1
- require 'test_helper'
2
-
3
- module Workarea
4
- module Adming
5
- class ForterSystemTest < Workarea::SystemTest
6
- include Admin::IntegrationTest
7
-
8
- def test_viewing_forter_order_admin
9
- checkout = create_purchasable_checkout
10
- order = checkout.order
11
-
12
- normal_response = Forter.gateway.create_decision(
13
- order.id,
14
- Forter::Order.new(order).to_h
15
- )
16
- Workarea::Forter::BogusGateway
17
- .any_instance
18
- .stubs(:create_decision)
19
- .raises(Faraday::Error::TimeoutError)
20
- .then
21
- .returns(normal_response)
22
-
23
- assert(checkout.place_order)
24
-
25
- visit admin.forter_order_path order
26
-
27
- assert page.has_content?("timeout")
28
- end
29
- end
30
- end
31
- end
@@ -1,10 +0,0 @@
1
- module Workarea
2
- decorate Storefront::OrderViewModelTest, with: :forter do
3
-
4
- def set_order
5
- @order = Order.new
6
- Workarea::Forter::Decision.create(id: @order.id)
7
-
8
- end
9
- end
10
- end