workarea-forter 1.2.4 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
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