solidus_signifyd 0.1.1
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 +7 -0
- data/.gitignore +15 -0
- data/.rspec +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +10 -0
- data/LICENSE +26 -0
- data/README.md +32 -0
- data/Rakefile +21 -0
- data/app/controllers/spree/api/spree_signifyd/orders_controller.rb +54 -0
- data/app/models/spree/order_decorator.rb +1 -0
- data/app/models/spree/signifyd_configuration.rb +6 -0
- data/app/models/spree_signifyd/order_concerns.rb +23 -0
- data/app/models/spree_signifyd/order_score.rb +9 -0
- data/app/models/spree_signifyd/shipment_decorator.rb +11 -0
- data/app/overrides/admin_order_signifyd_risk_analysis.rb +6 -0
- data/app/serializers/spree_signifyd/address_serializer.rb +20 -0
- data/app/serializers/spree_signifyd/billing_address_serializer.rb +13 -0
- data/app/serializers/spree_signifyd/credit_card_serializer.rb +25 -0
- data/app/serializers/spree_signifyd/delivery_address_serializer.rb +14 -0
- data/app/serializers/spree_signifyd/line_item_serializer.rb +25 -0
- data/app/serializers/spree_signifyd/order_serializer.rb +59 -0
- data/app/serializers/spree_signifyd/user_serializer.rb +44 -0
- data/app/views/spree/admin/orders/_signifyd_score.html.erb +10 -0
- data/bin/rails +7 -0
- data/circle.yml +6 -0
- data/config/locales/en.yml +7 -0
- data/config/routes.rb +7 -0
- data/db/migrate/20140819203000_add_signifyd_score_to_orders.rb +5 -0
- data/db/migrate/20140826202644_set_considered_risky_default_value.rb +5 -0
- data/db/migrate/20150206151312_create_spree_signifyd_order_scores.rb +11 -0
- data/db/migrate/20150206193231_transfer_spree_orders_signifyd_score_data.rb +15 -0
- data/db/migrate/20150211202803_remove_spree_orders_signifyd_score_column.rb +5 -0
- data/lib/generators/solidus_signifyd/install/install_generator.rb +26 -0
- data/lib/generators/solidus_signifyd/install/templates/solidus_signifyd.rb +3 -0
- data/lib/solidus_signifyd.rb +1 -0
- data/lib/spree_signifyd.rb +37 -0
- data/lib/spree_signifyd/create_signifyd_case.rb +13 -0
- data/lib/spree_signifyd/engine.rb +24 -0
- data/lib/spree_signifyd/request_verifier.rb +14 -0
- data/solidus_signifyd.gemspec +36 -0
- data/spec/controllers/spree/api/spree_signifyd/orders_controller_spec.rb +202 -0
- data/spec/lib/spree_signifyd/create_signifyd_case_spec.rb +20 -0
- data/spec/lib/spree_signifyd/request_verifier_spec.rb +27 -0
- data/spec/lib/spree_signifyd_spec.rb +66 -0
- data/spec/models/spree/order_spec.rb +51 -0
- data/spec/models/spree/shipment_spec.rb +59 -0
- data/spec/serializers/spree_signifyd/address_serializer_spec.rb +42 -0
- data/spec/serializers/spree_signifyd/billing_address_serializer.rb +12 -0
- data/spec/serializers/spree_signifyd/credit_card_serializer_spec.rb +26 -0
- data/spec/serializers/spree_signifyd/delivery_address_serializer_spec.rb +13 -0
- data/spec/serializers/spree_signifyd/line_item_serializer_spec.rb +26 -0
- data/spec/serializers/spree_signifyd/order_serializer_spec.rb +72 -0
- data/spec/serializers/spree_signifyd/user_serializer_spec.rb +53 -0
- data/spec/spec_helper.rb +63 -0
- metadata +294 -0
@@ -0,0 +1,10 @@
|
|
1
|
+
<tr>
|
2
|
+
<td><strong><%= Spree.t(:"risk.signifyd_score") %>:</strong></td>
|
3
|
+
<td class="align-center">
|
4
|
+
<% if @order.signifyd_order_score.nil? %>
|
5
|
+
<span class="state pending"><%= Spree.t(:"risk.awaiting_score") %></span>
|
6
|
+
<% else %>
|
7
|
+
<span class="state void"><%= @order.signifyd_order_score.score %></span>
|
8
|
+
<% end %>
|
9
|
+
</td>
|
10
|
+
</tr>
|
data/bin/rails
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
|
2
|
+
|
3
|
+
ENGINE_ROOT = File.expand_path('../..', __FILE__)
|
4
|
+
ENGINE_PATH = File.expand_path('../../lib/spree_signifyd/engine', __FILE__)
|
5
|
+
|
6
|
+
require 'rails/all'
|
7
|
+
require 'rails/engine/commands'
|
data/circle.yml
ADDED
data/config/routes.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
class CreateSpreeSignifydOrderScores < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :spree_signifyd_order_scores do |t|
|
4
|
+
t.integer :order_id
|
5
|
+
t.integer :score
|
6
|
+
t.timestamps null: true
|
7
|
+
end
|
8
|
+
|
9
|
+
add_index :spree_signifyd_order_scores, :order_id, unique: true
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class TransferSpreeOrdersSignifydScoreData < ActiveRecord::Migration
|
2
|
+
disable_ddl_transaction!
|
3
|
+
|
4
|
+
def up
|
5
|
+
Spree::Order.connection.execute(<<-SQL)
|
6
|
+
insert into spree_signifyd_order_scores (order_id, score, created_at, updated_at)
|
7
|
+
select o.id, o.signifyd_score, '#{Time.now.to_s(:db)}', '#{Time.now.to_s(:db)}'
|
8
|
+
from spree_orders o
|
9
|
+
left join spree_signifyd_order_scores
|
10
|
+
on o.id = spree_signifyd_order_scores.order_id
|
11
|
+
where o.signifyd_score is not null -- where the order has a score...
|
12
|
+
and spree_signifyd_order_scores.id is null -- ...but the new table does not
|
13
|
+
SQL
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module SolidusSignifyd
|
2
|
+
module Generators
|
3
|
+
class InstallGenerator < Rails::Generators::Base
|
4
|
+
class_option :auto_run_migrations, type: :boolean, default: false
|
5
|
+
|
6
|
+
source_root File.expand_path("../templates", __FILE__)
|
7
|
+
|
8
|
+
def add_initializer
|
9
|
+
copy_file "solidus_signifyd.rb", "config/initializers/solidus_signifyd.rb"
|
10
|
+
end
|
11
|
+
|
12
|
+
def add_migrations
|
13
|
+
run 'bundle exec rake railties:install:migrations FROM=solidus_signifyd'
|
14
|
+
end
|
15
|
+
|
16
|
+
def run_migrations
|
17
|
+
run_migrations = options[:auto_run_migrations] || ['', 'y', 'Y'].include?(ask 'Would you like to run the migrations now? [Y/n]')
|
18
|
+
if run_migrations
|
19
|
+
run 'bundle exec rake db:migrate'
|
20
|
+
else
|
21
|
+
puts 'Skipping rake db:migrate, don\'t forget to run it!'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require "spree_signifyd"
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spree_core'
|
2
|
+
require 'signifyd'
|
3
|
+
require 'spree_signifyd/create_signifyd_case'
|
4
|
+
require 'spree_signifyd/engine'
|
5
|
+
require 'spree_signifyd/request_verifier'
|
6
|
+
require 'resque'
|
7
|
+
require 'devise'
|
8
|
+
|
9
|
+
module SpreeSignifyd
|
10
|
+
|
11
|
+
module_function
|
12
|
+
|
13
|
+
def set_score(order:, score:)
|
14
|
+
if order.signifyd_order_score
|
15
|
+
order.signifyd_order_score.update!(score: score)
|
16
|
+
else
|
17
|
+
order.create_signifyd_order_score!(score: score)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def approve(order:)
|
22
|
+
order.contents.approve(name: self.name)
|
23
|
+
order.shipments.each { |shipment| shipment.ready! unless shipment.ready? }
|
24
|
+
order.updater.update_shipment_state
|
25
|
+
order.save!
|
26
|
+
end
|
27
|
+
|
28
|
+
def create_case(order_number:)
|
29
|
+
Rails.logger.info "Queuing Signifyd case creation event: #{order_number}"
|
30
|
+
Resque.enqueue(SpreeSignifyd::CreateSignifydCase, order_number)
|
31
|
+
end
|
32
|
+
|
33
|
+
def score_above_threshold?(score)
|
34
|
+
score > SpreeSignifyd::Config[:signifyd_score_threshold]
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module SpreeSignifyd
|
2
|
+
class CreateSignifydCase
|
3
|
+
@queue = :spree_backend_high
|
4
|
+
|
5
|
+
def self.perform(order_number_or_id)
|
6
|
+
Rails.logger.info "Processing Signifyd case creation event: #{order_number_or_id}"
|
7
|
+
order = Spree::Order.find_by(number: order_number_or_id) || Spree::Order.find(order_number_or_id)
|
8
|
+
order_data = JSON.parse(OrderSerializer.new(order).to_json)
|
9
|
+
Signifyd::Case.create(order_data, SpreeSignifyd::Config[:api_key])
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module SpreeSignifyd
|
2
|
+
class Engine < Rails::Engine
|
3
|
+
require "spree/core"
|
4
|
+
isolate_namespace Spree
|
5
|
+
engine_name "solidus_signifyd"
|
6
|
+
|
7
|
+
# use rspec for tests
|
8
|
+
config.generators do |g|
|
9
|
+
g.test_framework :rspec
|
10
|
+
end
|
11
|
+
|
12
|
+
initializer "spree.signifyd.environment", before: :load_config_initializers do |app|
|
13
|
+
SpreeSignifyd::Config = Spree::SignifydConfiguration.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.activate
|
17
|
+
Dir.glob(File.join(File.dirname(__FILE__), "../../app/**/*_decorator*.rb")) do |c|
|
18
|
+
Rails.configuration.cache_classes ? require(c) : load(c)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
config.to_prepare &method(:activate).to_proc
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module SpreeSignifyd
|
2
|
+
module RequestVerifier
|
3
|
+
|
4
|
+
def encode_request(request_body)
|
5
|
+
request_body.force_encoding('ISO-8859-1').encode('UTF-8')
|
6
|
+
end
|
7
|
+
|
8
|
+
def build_sha(key, message)
|
9
|
+
sha256 = OpenSSL::Digest::SHA256.new
|
10
|
+
digest = OpenSSL::HMAC.digest(sha256, key, message)
|
11
|
+
Base64.encode64(digest).strip
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.platform = Gem::Platform::RUBY
|
5
|
+
s.name = "solidus_signifyd"
|
6
|
+
s.version = "0.1.1"
|
7
|
+
s.summary = "Solidus extension for communicating with Signifyd to check orders for fraud."
|
8
|
+
s.description = s.summary
|
9
|
+
|
10
|
+
s.author = "Bonobos"
|
11
|
+
s.email = "engineering@bonobos.com"
|
12
|
+
s.homepage = "http://www.bonobos.com"
|
13
|
+
|
14
|
+
s.required_ruby_version = ">= 2.1"
|
15
|
+
s.license = %q{BSD-3}
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.require_path = "lib"
|
20
|
+
s.requirements << "none"
|
21
|
+
|
22
|
+
s.add_dependency "active_model_serializers", "0.9.3"
|
23
|
+
s.add_dependency "resque", "~> 1.25.1"
|
24
|
+
s.add_dependency "signifyd", "~> 0.1.5"
|
25
|
+
s.add_dependency "solidus", "~> 1.0.0"
|
26
|
+
s.add_dependency "devise"
|
27
|
+
|
28
|
+
s.add_development_dependency "rspec-rails", "~> 2.13"
|
29
|
+
s.add_development_dependency "simplecov"
|
30
|
+
s.add_development_dependency "sqlite3"
|
31
|
+
s.add_development_dependency "sass-rails"
|
32
|
+
s.add_development_dependency "coffee-rails"
|
33
|
+
s.add_development_dependency "database_cleaner"
|
34
|
+
s.add_development_dependency "factory_girl"
|
35
|
+
s.add_development_dependency "ffaker"
|
36
|
+
end
|
@@ -0,0 +1,202 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Spree::Api::SpreeSignifyd
|
4
|
+
|
5
|
+
describe OrdersController do
|
6
|
+
describe 'POST #update' do
|
7
|
+
|
8
|
+
let(:order_number) { "19418" }
|
9
|
+
let!(:order) { create(:completed_order_with_totals, number: order_number) }
|
10
|
+
let!(:user) { create(:user) }
|
11
|
+
let(:signifyd_sha) { 'sdGXFLSPZi5hTt8ZCVR9FeNMrsfmOblEIkpV2cCVLxM=' }
|
12
|
+
|
13
|
+
let(:body) {
|
14
|
+
{
|
15
|
+
"analysisUrl" => "https://signifyd.com/v2/cases/1/analysis",
|
16
|
+
"entriesUrl" => "https://signifyd.com/v2/cases/1/entries",
|
17
|
+
"notesUrl" => "https://signifyd.com/v2/cases/1/notes",
|
18
|
+
"orderUrl" => "https://signifyd.com/v2/cases/1/order",
|
19
|
+
"status" => "DISMISSED",
|
20
|
+
"uuid" => "709b9107-eda0-4cdd-bdac-a82f51a8a3f3",
|
21
|
+
"headline" => "John Smith",
|
22
|
+
"reviewDisposition" => nil,
|
23
|
+
"associatedTeam" => {
|
24
|
+
"teamName" => "anyTeam",
|
25
|
+
"teamId" => 26,
|
26
|
+
"getAutoDismiss" => true,
|
27
|
+
"getTeamDismissalDays" => 2
|
28
|
+
},
|
29
|
+
"orderId" => order_number,
|
30
|
+
"orderDate" => "2013-06-17T06:20:47-0700",
|
31
|
+
"orderAmount" => 365.99,
|
32
|
+
"createdAt" => "2013-11-05T14:23:26-0800",
|
33
|
+
"updatedAt" => "2013-11-05T14:23:26-0800",
|
34
|
+
"adjustedScore" => 262.6666666666667,
|
35
|
+
"investigationId" => 1,
|
36
|
+
"score" => 262.6666666666667,
|
37
|
+
"caseId" => 1
|
38
|
+
}
|
39
|
+
}
|
40
|
+
|
41
|
+
before { request.headers['HTTP_HTTP_X_SIGNIFYD_HMAC_SHA256'] = signifyd_sha }
|
42
|
+
|
43
|
+
around do |example|
|
44
|
+
previous_api_key = SpreeSignifyd::Config[:api_key]
|
45
|
+
SpreeSignifyd::Config[:api_key] = 'ABCDE'
|
46
|
+
example.run
|
47
|
+
SpreeSignifyd::Config[:api_key] = previous_api_key
|
48
|
+
end
|
49
|
+
|
50
|
+
routes { Spree::Core::Engine.routes }
|
51
|
+
|
52
|
+
subject { post :update, body.to_json }
|
53
|
+
|
54
|
+
context "invalid sha" do
|
55
|
+
let(:signifyd_sha) { "INVALID" }
|
56
|
+
|
57
|
+
it "does not set signifyd_score" do
|
58
|
+
subject
|
59
|
+
order.reload
|
60
|
+
expect(order.signifyd_order_score).to eq nil
|
61
|
+
end
|
62
|
+
|
63
|
+
it "responds with 401" do
|
64
|
+
subject
|
65
|
+
expect(response.code.to_i).to eq 401
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context "valid sha" do
|
70
|
+
context "invalid order number" do
|
71
|
+
before(:each) { order.destroy! }
|
72
|
+
|
73
|
+
it "responds with a 404" do
|
74
|
+
subject
|
75
|
+
expect(response.code.to_i).to eq 404
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context "the order has been shipped" do
|
80
|
+
|
81
|
+
it "returns without trying to act on the order" do
|
82
|
+
Spree::Order.any_instance.stub(:shipped?).and_return(true)
|
83
|
+
expect(SpreeSignifyd).not_to receive(:approve)
|
84
|
+
expect(Spree::Order.any_instance).not_to receive(:cancel!)
|
85
|
+
expect { subject }.not_to raise_error
|
86
|
+
expect(response.status).to eq(200)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context "the order has been canceled" do
|
91
|
+
before(:each) { order.cancel! }
|
92
|
+
|
93
|
+
it "returns without trying to act on the order" do
|
94
|
+
expect(SpreeSignifyd).not_to receive(:approve)
|
95
|
+
expect(Spree::Order.any_instance).not_to receive(:cancel!)
|
96
|
+
expect { subject }.not_to raise_error
|
97
|
+
expect(response.status).to eq(200)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context "valid order number" do
|
102
|
+
it "sets the order's signifyd_score" do
|
103
|
+
subject
|
104
|
+
order.reload
|
105
|
+
expect(order.signifyd_order_score.score).to eq 262
|
106
|
+
end
|
107
|
+
|
108
|
+
it "responds with 200" do
|
109
|
+
subject
|
110
|
+
expect(response.code.to_i).to eq 200
|
111
|
+
end
|
112
|
+
|
113
|
+
context "reviewDisposition is FRAUDULENT" do
|
114
|
+
let(:signifyd_sha) { "ulHF48lbFO3M6UBMSi1tAroJWADeSggrr6V7ND8hBx0=" }
|
115
|
+
|
116
|
+
before(:each) do
|
117
|
+
@original_review_disposition = body['reviewDiposition']
|
118
|
+
body['reviewDisposition'] = 'FRAUDULENT'
|
119
|
+
end
|
120
|
+
|
121
|
+
after(:each) { body['reviewDiposition'] = @original_review_disposition }
|
122
|
+
|
123
|
+
it 'cancels the order' do
|
124
|
+
Spree::Order.any_instance.should_receive(:cancel!)
|
125
|
+
subject
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
context "reviewDisposition is not FRAUDULENT" do
|
130
|
+
context "the order has already been approved" do
|
131
|
+
|
132
|
+
before(:each) { order.update_attribute(:approved_at, Time.now) }
|
133
|
+
|
134
|
+
it "does not call approve" do
|
135
|
+
expect(SpreeSignifyd).not_to receive(:approve)
|
136
|
+
subject
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
context "the order has not yet been approved" do
|
141
|
+
context "the reviewDisposition is GOOD" do
|
142
|
+
let(:signifyd_sha) { "wZIjgRQoDMWe0W4VoE5TJEoHf8ZcY9UeXY1lnGP+pfg=" }
|
143
|
+
|
144
|
+
before(:each) do
|
145
|
+
@original_review_disposition = body['reviewDisposition']
|
146
|
+
body['reviewDisposition'] = 'GOOD'
|
147
|
+
end
|
148
|
+
|
149
|
+
after(:each) { body['reviewDisposition'] = @original_review_disposition }
|
150
|
+
|
151
|
+
it "calls approve" do
|
152
|
+
expect(SpreeSignifyd).to receive(:approve).with(order: order)
|
153
|
+
subject
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
context "the reviewDisposition is not GOOD" do
|
158
|
+
it "does not call approve" do
|
159
|
+
expect(SpreeSignifyd).not_to receive(:approve)
|
160
|
+
subject
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
context "the order is not risky" do
|
165
|
+
let(:signifyd_sha) { "ZI7bSCavfy6pWogJZ7nq2LbLLojcfcy9kjF02WHO4nM=" }
|
166
|
+
|
167
|
+
before(:each) do
|
168
|
+
@original_score = body['adjustedScore']
|
169
|
+
body['adjustedScore'] = SpreeSignifyd::Config[:signifyd_score_threshold] + 1
|
170
|
+
end
|
171
|
+
|
172
|
+
after(:each) { body['adjustedScore'] = @original_score }
|
173
|
+
|
174
|
+
it "approves the order" do
|
175
|
+
expect(SpreeSignifyd).to receive(:approve).with(order: order)
|
176
|
+
subject
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
context "the order is risky" do
|
181
|
+
|
182
|
+
let(:signifyd_sha) { "YcEDVtPBAXcgQ9fJgBMSoBWy9CVpc6pnN6YzCbtD85E=" }
|
183
|
+
|
184
|
+
before(:each) do
|
185
|
+
@original_score = body['adjustedScore']
|
186
|
+
body['adjustedScore'] = SpreeSignifyd::Config[:signifyd_score_threshold] - 1
|
187
|
+
end
|
188
|
+
|
189
|
+
after(:each) { body['adjustedScore'] = @original_score }
|
190
|
+
|
191
|
+
it "does not approve the order" do
|
192
|
+
expect(SpreeSignifyd).not_to receive(:approve)
|
193
|
+
subject
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|