solidus_signifyd 1.0.1 → 1.1.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/.travis.yml +21 -0
- data/Gemfile +10 -3
- data/README.md +58 -1
- data/app/controllers/spree/api/spree_signifyd/orders_controller.rb +1 -1
- data/app/models/spree/signifyd_configuration.rb +1 -0
- data/app/models/spree_signifyd/order_concerns.rb +18 -1
- data/app/models/spree_signifyd/shipment_decorator.rb +1 -1
- data/app/serializers/spree_signifyd/credit_card_serializer.rb +9 -9
- data/app/serializers/spree_signifyd/order_serializer.rb +22 -10
- data/app/serializers/spree_signifyd/user_serializer.rb +13 -2
- data/lib/spree_signifyd.rb +2 -3
- data/lib/spree_signifyd/create_signifyd_case.rb +3 -4
- data/lib/spree_signifyd/engine.rb +1 -0
- data/solidus_signifyd.gemspec +4 -4
- data/spec/controllers/spree/api/spree_signifyd/orders_controller_spec.rb +6 -10
- data/spec/lib/spree_signifyd/create_signifyd_case_spec.rb +4 -4
- data/spec/lib/spree_signifyd_spec.rb +14 -3
- data/spec/models/spree/order_spec.rb +38 -11
- data/spec/models/spree/shipment_spec.rb +33 -35
- data/spec/serializers/spree_signifyd/billing_address_serializer.rb +1 -1
- data/spec/serializers/spree_signifyd/credit_card_serializer_spec.rb +2 -2
- data/spec/serializers/spree_signifyd/delivery_address_serializer_spec.rb +2 -2
- data/spec/serializers/spree_signifyd/order_serializer_spec.rb +58 -17
- data/spec/serializers/spree_signifyd/user_serializer_spec.rb +9 -1
- data/spec/spec_helper.rb +7 -0
- data/spec/support/api_schema_matcher.rb +9 -0
- data/spec/support/schemas/v2/case.json +305 -0
- metadata +46 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5c7e1bb9ec394555d95c0bd04879ed53f60f36a4
|
4
|
+
data.tar.gz: 1ea89e5ec06118d867ba7fd1582785b64caaf129
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 601b96f317fb0f2b439f4550c910c1bcbfafe6cd0e1419f3a651c2aa2b7cb9ae956fa717b1abcc828628f11a63c45f976d7c84cf682b5f764fe1fe0772974885
|
7
|
+
data.tar.gz: fb0afbf0e113611b9f45e9e0011909acfa441590681910aead26d0a01784e646bb7752b71022bdfe57c96147095bf1c2775368f319a0ce5aa75388b989495804
|
data/.travis.yml
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
sudo: false
|
2
|
+
cache: bundler
|
3
|
+
language: ruby
|
4
|
+
rvm:
|
5
|
+
- 2.3.1
|
6
|
+
env:
|
7
|
+
matrix:
|
8
|
+
- SOLIDUS_BRANCH=v1.0 DB=postgres
|
9
|
+
- SOLIDUS_BRANCH=v1.1 DB=postgres
|
10
|
+
- SOLIDUS_BRANCH=v1.2 DB=postgres
|
11
|
+
- SOLIDUS_BRANCH=v1.3 DB=postgres
|
12
|
+
- SOLIDUS_BRANCH=v1.4 DB=postgres
|
13
|
+
- SOLIDUS_BRANCH=v2.0 DB=postgres
|
14
|
+
- SOLIDUS_BRANCH=master DB=postgres
|
15
|
+
- SOLIDUS_BRANCH=v1.0 DB=mysql
|
16
|
+
- SOLIDUS_BRANCH=v1.1 DB=mysql
|
17
|
+
- SOLIDUS_BRANCH=v1.2 DB=mysql
|
18
|
+
- SOLIDUS_BRANCH=v1.3 DB=mysql
|
19
|
+
- SOLIDUS_BRANCH=v1.4 DB=mysql
|
20
|
+
- SOLIDUS_BRANCH=v2.0 DB=mysql
|
21
|
+
- SOLIDUS_BRANCH=master DB=mysql
|
data/Gemfile
CHANGED
@@ -1,7 +1,14 @@
|
|
1
|
-
source
|
1
|
+
source "https://rubygems.org"
|
2
2
|
|
3
|
-
|
4
|
-
gem "
|
3
|
+
branch = ENV.fetch('SOLIDUS_BRANCH', 'master')
|
4
|
+
gem "solidus", github: "solidusio/solidus", branch: branch
|
5
|
+
|
6
|
+
if branch == 'master' || branch >= "v2.0"
|
7
|
+
gem "rails-controller-testing", group: :test
|
8
|
+
end
|
9
|
+
|
10
|
+
gem 'pg'
|
11
|
+
gem 'mysql2'
|
5
12
|
|
6
13
|
group :development, :test do
|
7
14
|
gem "pry-rails"
|
data/README.md
CHANGED
@@ -4,7 +4,15 @@ Solidus Signifyd
|
|
4
4
|
Integration with Signifyd that implements a fraud check prior to marking a
|
5
5
|
shipment as ready to be shipped.
|
6
6
|
|
7
|
-
[](https://travis-ci.org/solidusio/solidus_signifyd)
|
8
|
+
|
9
|
+
* All orders are sent to SIGNIFYD for scoring when they transition to complete.
|
10
|
+
* Risk analysis is returned from SIGNIFYD via a webhook and added to order.
|
11
|
+
* Orders with a risk score >= 500 (default review disposition threshhold)
|
12
|
+
- Paid orders are marked ready to ship.
|
13
|
+
* Orders with a risk score < 500
|
14
|
+
- Are cancelled.
|
15
|
+
- Risk analysis is displayed in admin.
|
8
16
|
|
9
17
|
Installation
|
10
18
|
------------
|
@@ -22,6 +30,53 @@ bundle
|
|
22
30
|
bundle exec rails g solidus_signifyd:install
|
23
31
|
```
|
24
32
|
|
33
|
+
Create a SIGNIFYD test team within the SIGNIFYD account. The API key is listed on the Teams page after a team has been created.
|
34
|
+
|
35
|
+
Create SIGNIFYD notifications for each event type and provide your
|
36
|
+
`api_spree_signifyd_orders_path`. To work with external webhook in local
|
37
|
+
development you may need to change the rails server [default host] and enable
|
38
|
+
port forwarding or setup a reverse SSH tunnel.
|
39
|
+
|
40
|
+
```
|
41
|
+
http://www.example.com/api/spree_signifyd/orders
|
42
|
+
```
|
43
|
+
|
44
|
+
Cases can be inspected in the SIGNIFYD web console.
|
45
|
+
|
46
|
+
Configuration
|
47
|
+
-------------
|
48
|
+
|
49
|
+
### api_key
|
50
|
+
|
51
|
+
Type: `string`
|
52
|
+
|
53
|
+
SIGNIFYD team API key.
|
54
|
+
|
55
|
+
### exclude_store_credit_orders
|
56
|
+
|
57
|
+
Type: `boolean`
|
58
|
+
Default: `false`
|
59
|
+
|
60
|
+
By default, even orders which are fully paid with store credit are sent to
|
61
|
+
SIGNIFYD. Since this could result in unnecessary charges to a user who is on a
|
62
|
+
"flat rate" plan, we provide the option to skip these orders.
|
63
|
+
|
64
|
+
### signifyd_score_threshold
|
65
|
+
|
66
|
+
Type: `integer`
|
67
|
+
Default: `500`
|
68
|
+
|
69
|
+
Automatic approval is granted to orders which have a good "reviewDisposition" or
|
70
|
+
have a score greater than the `signifyd_score_threshold`.
|
71
|
+
|
72
|
+
Risky Orders
|
73
|
+
------------
|
74
|
+
|
75
|
+
Flagging a case as bad in the SIGNIFYD web console will associate
|
76
|
+
a fraudulent case with the order's email. This will cause future orders to drop
|
77
|
+
below the `reviewDisposition` threshhold of 500 and allow you to inspect a
|
78
|
+
risky order.
|
79
|
+
|
25
80
|
Testing
|
26
81
|
-------
|
27
82
|
|
@@ -32,3 +87,5 @@ app can be regenerated by using `rake test_app`.
|
|
32
87
|
```shell
|
33
88
|
bundle exec rake
|
34
89
|
```
|
90
|
+
|
91
|
+
[default host]: http://guides.rubyonrails.org/4_2_release_notes.html#default-host-for-rails-server
|
@@ -21,7 +21,7 @@ module Spree::Api::SpreeSignifyd
|
|
21
21
|
private
|
22
22
|
|
23
23
|
def authorize
|
24
|
-
request_sha = request.headers['
|
24
|
+
request_sha = request.headers['HTTP_X_SIGNIFYD_SEC_HMAC_SHA256']
|
25
25
|
computed_sha = build_sha(SpreeSignifyd::Config[:api_key], request.raw_post)
|
26
26
|
|
27
27
|
if !Devise.secure_compare(request_sha, computed_sha)
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module Spree
|
2
2
|
class SignifydConfiguration < Preferences::Configuration
|
3
3
|
preference :api_key, :string
|
4
|
+
preference :exclude_store_credit_orders, :boolean, default: false
|
4
5
|
preference :signifyd_score_threshold, :integer, default: 500 # Signifyd's recommended threshold
|
5
6
|
end
|
6
7
|
end
|
@@ -3,7 +3,11 @@ module SpreeSignifyd::OrderConcerns
|
|
3
3
|
|
4
4
|
included do
|
5
5
|
Spree::Order.state_machine.after_transition to: :complete, unless: :approved? do |order, transition|
|
6
|
-
|
6
|
+
if order.send_to_signifyd?
|
7
|
+
SpreeSignifyd.create_case(order_number: order.number)
|
8
|
+
else
|
9
|
+
SpreeSignifyd.approve(order: order)
|
10
|
+
end
|
7
11
|
end
|
8
12
|
|
9
13
|
has_one :signifyd_order_score, class_name: "SpreeSignifyd::OrderScore"
|
@@ -19,5 +23,18 @@ module SpreeSignifyd::OrderConcerns
|
|
19
23
|
def awaiting_approval?
|
20
24
|
!signifyd_order_score
|
21
25
|
end
|
26
|
+
|
27
|
+
def send_to_signifyd?
|
28
|
+
!approved? &&
|
29
|
+
!(SpreeSignifyd::Config[:exclude_store_credit_orders] && paid_completely_with_store_credit?)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def paid_completely_with_store_credit?
|
35
|
+
payments.all? do |payment|
|
36
|
+
payment.payment_method.is_a?(Spree::PaymentMethod::StoreCredit)
|
37
|
+
end
|
38
|
+
end
|
22
39
|
end
|
23
40
|
end
|
@@ -4,7 +4,15 @@ module SpreeSignifyd
|
|
4
4
|
class CreditCardSerializer < ActiveModel::Serializer
|
5
5
|
self.root = false
|
6
6
|
|
7
|
-
attributes :cardHolderName, :last4
|
7
|
+
attributes :cardHolderName, :last4
|
8
|
+
|
9
|
+
# this is how to conditionally include attributes in AMS
|
10
|
+
def attributes(*args)
|
11
|
+
hash = super
|
12
|
+
hash[:expiryMonth] = object.month.to_i if object.month
|
13
|
+
hash[:expiryYear] = object.year.to_i if object.year
|
14
|
+
hash
|
15
|
+
end
|
8
16
|
|
9
17
|
def cardHolderName
|
10
18
|
"#{object.first_name} #{object.last_name}"
|
@@ -13,13 +21,5 @@ module SpreeSignifyd
|
|
13
21
|
def last4
|
14
22
|
object.last_digits
|
15
23
|
end
|
16
|
-
|
17
|
-
def expiryMonth
|
18
|
-
object.month
|
19
|
-
end
|
20
|
-
|
21
|
-
def expiryYear
|
22
|
-
object.year
|
23
|
-
end
|
24
24
|
end
|
25
25
|
end
|
@@ -8,16 +8,11 @@ module SpreeSignifyd
|
|
8
8
|
has_one :user, serializer: SpreeSignifyd::UserSerializer, root: "userAccount"
|
9
9
|
|
10
10
|
def purchase
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
'totalPrice' => object.total,
|
17
|
-
'products' => products,
|
18
|
-
'avsResponseCode' => latest_payment.try(:avs_response),
|
19
|
-
'cvvResponseCode' => latest_payment.try(:cvv_response_code)
|
20
|
-
}
|
11
|
+
build_purchase_information.tap do |purchase_info|
|
12
|
+
if paid_by_paypal?
|
13
|
+
purchase_info["paymentGateway"] = "paypal_account"
|
14
|
+
end
|
15
|
+
end
|
21
16
|
end
|
22
17
|
|
23
18
|
def recipient
|
@@ -41,6 +36,23 @@ module SpreeSignifyd
|
|
41
36
|
|
42
37
|
private
|
43
38
|
|
39
|
+
def paid_by_paypal?
|
40
|
+
latest_payment.try!(:source).try(:cc_type) == "paypal"
|
41
|
+
end
|
42
|
+
|
43
|
+
def build_purchase_information
|
44
|
+
{
|
45
|
+
'browserIpAddress' => object.last_ip_address || "",
|
46
|
+
'orderId' => object.number,
|
47
|
+
'createdAt' => object.completed_at.utc.iso8601,
|
48
|
+
'currency' => object.currency,
|
49
|
+
'totalPrice' => object.total.to_f,
|
50
|
+
'products' => products,
|
51
|
+
'avsResponseCode' => latest_payment.try!(:avs_response) || "",
|
52
|
+
'cvvResponseCode' => latest_payment.try!(:cvv_response_code) || ""
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
44
56
|
def products
|
45
57
|
order_products = []
|
46
58
|
|
@@ -4,7 +4,14 @@ module SpreeSignifyd
|
|
4
4
|
class UserSerializer < ActiveModel::Serializer
|
5
5
|
self.root = false
|
6
6
|
|
7
|
-
attributes :emailAddress, :username, :createdDate, :lastUpdateDate, :
|
7
|
+
attributes :emailAddress, :username, :createdDate, :lastUpdateDate, :aggregateOrderCount, :aggregateOrderDollars, :phone
|
8
|
+
|
9
|
+
# this is how to conditionally include attributes in AMS
|
10
|
+
def attributes(*args)
|
11
|
+
hash = super
|
12
|
+
hash[:lastOrderId] = lastOrderId if lastOrderId.present?
|
13
|
+
hash
|
14
|
+
end
|
8
15
|
|
9
16
|
def emailAddress
|
10
17
|
object.email
|
@@ -31,7 +38,11 @@ module SpreeSignifyd
|
|
31
38
|
end
|
32
39
|
|
33
40
|
def aggregateOrderDollars
|
34
|
-
completed_orders.sum(:total)
|
41
|
+
completed_orders.sum(:total).to_f
|
42
|
+
end
|
43
|
+
|
44
|
+
def phone
|
45
|
+
object.orders.order("created_at DESC").first.try!(:ship_address).try!(:phone)
|
35
46
|
end
|
36
47
|
|
37
48
|
private
|
data/lib/spree_signifyd.rb
CHANGED
@@ -3,7 +3,6 @@ require 'signifyd'
|
|
3
3
|
require 'spree_signifyd/create_signifyd_case'
|
4
4
|
require 'spree_signifyd/engine'
|
5
5
|
require 'spree_signifyd/request_verifier'
|
6
|
-
require 'resque'
|
7
6
|
require 'devise'
|
8
7
|
|
9
8
|
module SpreeSignifyd
|
@@ -20,14 +19,14 @@ module SpreeSignifyd
|
|
20
19
|
|
21
20
|
def approve(order:)
|
22
21
|
order.contents.approve(name: self.name)
|
23
|
-
order.shipments.each { |shipment| shipment.ready! if shipment.
|
22
|
+
order.shipments.each { |shipment| shipment.ready! if shipment.can_ready? }
|
24
23
|
order.updater.update_shipment_state
|
25
24
|
order.save!
|
26
25
|
end
|
27
26
|
|
28
27
|
def create_case(order_number:)
|
29
28
|
Rails.logger.info "Queuing Signifyd case creation event: #{order_number}"
|
30
|
-
|
29
|
+
SpreeSignifyd::CreateSignifydCase.perform_later(order_number)
|
31
30
|
end
|
32
31
|
|
33
32
|
def score_above_threshold?(score)
|
@@ -1,13 +1,12 @@
|
|
1
1
|
module SpreeSignifyd
|
2
|
-
class CreateSignifydCase
|
3
|
-
|
2
|
+
class CreateSignifydCase < ActiveJob::Base
|
3
|
+
queue_as :default
|
4
4
|
|
5
|
-
def
|
5
|
+
def perform(order_number_or_id)
|
6
6
|
Rails.logger.info "Processing Signifyd case creation event: #{order_number_or_id}"
|
7
7
|
order = Spree::Order.find_by(number: order_number_or_id) || Spree::Order.find(order_number_or_id)
|
8
8
|
order_data = JSON.parse(OrderSerializer.new(order).to_json)
|
9
9
|
Signifyd::Case.create(order_data, SpreeSignifyd::Config[:api_key])
|
10
10
|
end
|
11
|
-
|
12
11
|
end
|
13
12
|
end
|
data/solidus_signifyd.gemspec
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.platform = Gem::Platform::RUBY
|
5
5
|
s.name = "solidus_signifyd"
|
6
|
-
s.version = "1.0
|
6
|
+
s.version = "1.1.0"
|
7
7
|
s.summary = "Solidus extension for communicating with Signifyd to check orders for fraud."
|
8
8
|
s.description = s.summary
|
9
9
|
|
@@ -20,12 +20,12 @@ Gem::Specification.new do |s|
|
|
20
20
|
s.requirements << "none"
|
21
21
|
|
22
22
|
s.add_dependency "active_model_serializers", "0.9.3"
|
23
|
-
s.add_dependency "resque", "~> 1.25.1"
|
24
23
|
s.add_dependency "signifyd", "~> 0.1.5"
|
25
|
-
s.add_dependency "solidus", "
|
24
|
+
s.add_dependency "solidus", [">= 1.0", "< 3"]
|
26
25
|
s.add_dependency "devise"
|
27
26
|
|
28
|
-
s.add_development_dependency "rspec-rails", "~>
|
27
|
+
s.add_development_dependency "rspec-rails", "~> 3.4"
|
28
|
+
s.add_development_dependency "json-schema"
|
29
29
|
s.add_development_dependency "simplecov"
|
30
30
|
s.add_development_dependency "sqlite3"
|
31
31
|
s.add_development_dependency "sass-rails"
|
@@ -38,13 +38,9 @@ module Spree::Api::SpreeSignifyd
|
|
38
38
|
}
|
39
39
|
}
|
40
40
|
|
41
|
-
before
|
42
|
-
|
43
|
-
around do |example|
|
44
|
-
previous_api_key = SpreeSignifyd::Config[:api_key]
|
41
|
+
before do
|
42
|
+
request.headers['HTTP_X_SIGNIFYD_SEC_HMAC_SHA256'] = signifyd_sha
|
45
43
|
SpreeSignifyd::Config[:api_key] = 'ABCDE'
|
46
|
-
example.run
|
47
|
-
SpreeSignifyd::Config[:api_key] = previous_api_key
|
48
44
|
end
|
49
45
|
|
50
46
|
routes { Spree::Core::Engine.routes }
|
@@ -79,9 +75,9 @@ module Spree::Api::SpreeSignifyd
|
|
79
75
|
context "the order has been shipped" do
|
80
76
|
|
81
77
|
it "returns without trying to act on the order" do
|
82
|
-
Spree::Order.
|
78
|
+
allow_any_instance_of(Spree::Order).to receive(:shipped?).and_return(true)
|
83
79
|
expect(SpreeSignifyd).not_to receive(:approve)
|
84
|
-
|
80
|
+
expect_any_instance_of(Spree::Order).not_to receive(:cancel!)
|
85
81
|
expect { subject }.not_to raise_error
|
86
82
|
expect(response.status).to eq(200)
|
87
83
|
end
|
@@ -92,7 +88,7 @@ module Spree::Api::SpreeSignifyd
|
|
92
88
|
|
93
89
|
it "returns without trying to act on the order" do
|
94
90
|
expect(SpreeSignifyd).not_to receive(:approve)
|
95
|
-
|
91
|
+
expect_any_instance_of(Spree::Order).not_to receive(:cancel!)
|
96
92
|
expect { subject }.not_to raise_error
|
97
93
|
expect(response.status).to eq(200)
|
98
94
|
end
|
@@ -121,7 +117,7 @@ module Spree::Api::SpreeSignifyd
|
|
121
117
|
after(:each) { body['reviewDiposition'] = @original_review_disposition }
|
122
118
|
|
123
119
|
it 'cancels the order' do
|
124
|
-
Spree::Order.
|
120
|
+
expect_any_instance_of(Spree::Order).to receive(:cancel!)
|
125
121
|
subject
|
126
122
|
end
|
127
123
|
end
|
@@ -7,13 +7,13 @@ module SpreeSignifyd
|
|
7
7
|
let(:json) { JSON.parse(OrderSerializer.new(order).to_json) }
|
8
8
|
|
9
9
|
it "calls Signifyd::Case#create with the correct params" do
|
10
|
-
Signifyd::Case.
|
11
|
-
CreateSignifydCase.
|
10
|
+
expect(Signifyd::Case).to receive(:create).with(json, SpreeSignifyd::Config[:api_key])
|
11
|
+
CreateSignifydCase.perform_now(order.id)
|
12
12
|
end
|
13
13
|
|
14
14
|
it "calls Signifyd::Case#create with the correct params" do
|
15
|
-
Signifyd::Case.
|
16
|
-
CreateSignifydCase.
|
15
|
+
expect(Signifyd::Case).to receive(:create).with(json, SpreeSignifyd::Config[:api_key])
|
16
|
+
CreateSignifydCase.perform_now(order.number)
|
17
17
|
end
|
18
18
|
end
|
19
19
|
end
|
@@ -50,7 +50,7 @@ module SpreeSignifyd
|
|
50
50
|
end
|
51
51
|
|
52
52
|
it 'readies all of the shipments' do
|
53
|
-
order.shipments.each { |shipment| shipment.
|
53
|
+
order.shipments.each { |shipment| expect(shipment).to receive(:ready!) }
|
54
54
|
approve
|
55
55
|
end
|
56
56
|
|
@@ -61,12 +61,23 @@ module SpreeSignifyd
|
|
61
61
|
expect { approve }.to change { order.approved_at }
|
62
62
|
end
|
63
63
|
end
|
64
|
+
|
65
|
+
context "with backordered stock" do
|
66
|
+
before do
|
67
|
+
order.inventory_units.first.update(state: 'backordered')
|
68
|
+
order.reload
|
69
|
+
end
|
70
|
+
|
71
|
+
it "does not attempt invalid state changes" do
|
72
|
+
approve
|
73
|
+
expect(order.reload.shipments.first).to be_pending
|
74
|
+
end
|
75
|
+
end
|
64
76
|
end
|
65
77
|
|
66
78
|
describe ".create_case" do
|
67
79
|
it 'enqueues in resque' do
|
68
|
-
expect(
|
69
|
-
SpreeSignifyd.create_case(order_number: 111)
|
80
|
+
expect { SpreeSignifyd.create_case(order_number: 111) }.to have_enqueued_job(SpreeSignifyd::CreateSignifydCase)
|
70
81
|
end
|
71
82
|
end
|
72
83
|
|