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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/Gemfile +1 -1
- data/app/models/search/admin/order.decorator +1 -1
- data/app/models/workarea/checkout.decorator +24 -0
- data/app/models/workarea/checkout/collect_payment.decorator +7 -43
- data/app/models/workarea/checkout/fraud/forter_analyzer.rb +57 -0
- data/app/models/workarea/order.decorator +0 -7
- data/app/models/workarea/order/fraud_decision.decorator +13 -0
- data/app/models/workarea/order/status/suspected_fraud.rb +3 -9
- data/app/models/workarea/payment.decorator +1 -1
- data/app/models/workarea/payment/status/suspected_fraud.rb +1 -1
- data/app/services/workarea/forter/order.rb +3 -3
- data/app/views/workarea/admin/orders/{forter.html.haml → fraud.html.haml} +4 -7
- data/app/workers/forter/update_status.rb +1 -1
- data/config/initializers/appends.rb +0 -5
- data/config/initializers/workarea.rb +2 -0
- data/config/routes.rb +1 -6
- data/lib/workarea/forter/version.rb +1 -1
- data/test/integration/workarea/admin/forter_order_integration_test.rb +5 -5
- data/test/integration/workarea/storefront/checkouts_integration_test.decorator +69 -0
- data/test/integration/workarea/storefront/forter_integration_test.rb +15 -12
- data/test/lib/workarea/forter/gateway_test.rb +1 -2
- data/test/models/workarea/checkout/forter_collect_payment_test.rb +29 -38
- data/test/models/workarea/checkout_test.decorator +16 -0
- data/test/system/workarea/admin/orders_system_test.decorator +12 -0
- data/test/vcr_cassettes/forter/update_status.yml +31 -26
- data/test/workers/forter/update_status_test.rb +4 -4
- data/workarea-forter.gemspec +1 -1
- metadata +11 -11
- data/app/controllers/workarea/admin/orders_controller.decorator +0 -6
- data/app/models/workarea/forter/decision.rb +0 -18
- data/app/view_models/workarea/admin/order_view_model.decorator +0 -7
- data/app/views/workarea/admin/orders/_forter.html.haml +0 -18
- data/test/system/workarea/admin/forter_system_test.rb +0 -31
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c5b05cf277bf2f9053f124dcf7c7cb83096aa2aaf4def84bc5a600f50364ae6e
|
4
|
+
data.tar.gz: 626cdd36a1900d1372572f04be318ae2e57b4be60a8627427d6b0f02a047d97c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
@@ -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
|
-
|
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 &&
|
11
|
+
if collect_result && @order.fraud_suspected?
|
15
12
|
payment.rollback!
|
16
|
-
payment.
|
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(:
|
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
|
-
|
41
|
-
|
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
|
-
|
3
|
-
|
4
|
-
|
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
|
@@ -57,8 +57,8 @@ module Workarea
|
|
57
57
|
end
|
58
58
|
|
59
59
|
def delivery_type
|
60
|
-
return "DIGITAL" if order.items.
|
61
|
-
return "HYBRID" if order.items.any?
|
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.
|
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.
|
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, :
|
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.
|
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.
|
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::
|
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,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
@@ -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.
|
19
|
-
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
|
23
|
+
def test_returns_forter_order_details
|
24
24
|
order = create_placed_forter_order(email: 'approve@workarea.com')
|
25
|
-
decision =
|
26
|
-
get admin.
|
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
|
-
|
12
|
+
order.reload
|
13
|
+
decision = order.fraud_decision
|
13
14
|
|
14
15
|
refute(decision.response.suspected_fraud?)
|
15
16
|
|
16
|
-
order.
|
17
|
-
refute(
|
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
|
-
|
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
|
-
|
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.
|
51
|
+
assert(order.fraud_suspected?)
|
50
52
|
assert_equal(:suspected_fraud, order.status)
|
51
53
|
|
52
|
-
assert(payment.
|
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
|
-
|
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 =
|
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
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
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
|
-
|
36
|
-
|
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
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
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":
|
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":"
|
14
|
-
|
15
|
-
|
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.
|
23
|
+
- '2.3'
|
21
24
|
User-Agent:
|
22
25
|
- Faraday v0.15.4
|
23
26
|
Authorization:
|
24
|
-
- Basic
|
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:
|
32
|
-
message:
|
34
|
+
code: 200
|
35
|
+
message: OK
|
33
36
|
headers:
|
34
37
|
Date:
|
35
|
-
-
|
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
|
-
- '
|
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/"
|
58
|
+
- W/"ba-Ef/isk7mEOWPRXN9i4trCjru7vk"
|
56
59
|
Vary:
|
57
60
|
- Accept-Encoding
|
58
61
|
body:
|
59
62
|
encoding: UTF-8
|
60
|
-
string: '{"status":"
|
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:
|
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":
|
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":"
|
74
|
-
|
75
|
-
|
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
|
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
|
-
-
|
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:
|
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":
|
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
|
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
|
-
-
|
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:
|
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
|
-
|
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(
|
14
|
+
Workarea::Forter::UpdateStatus.new.perform(order.id, details_hash)
|
15
15
|
|
16
|
-
|
16
|
+
order.reload
|
17
17
|
|
18
|
-
assert_equal("CANCELED_BY_MERCHANT",
|
18
|
+
assert_equal("CANCELED_BY_MERCHANT", order.fraud_decision.external_order_status)
|
19
19
|
end
|
20
20
|
end
|
21
21
|
end
|
data/workarea-forter.gemspec
CHANGED
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.
|
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-
|
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.
|
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.
|
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/
|
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/
|
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/
|
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,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,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
|