solidus_signifyd 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e7c186c4c9b430760dabce1a20c7b0d6619241de
|
4
|
+
data.tar.gz: fa6036cd2c018e312763b158926f201f26973adf
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 49806f09e4991a97f8ac9605c8b9ecbac09384477f9391649a9f79e97c553baeee02e676cb223ed62dc16264d9992b1e45ec11f236b23ae6e68dfa52d83df917
|
7
|
+
data.tar.gz: 7cdc4844d2b1649be005514ad4cc213316faabda99675bfa24e614b9a32ff1d5554248cb71ea7e208cc63adbdf94216aa0d40696933a469bbb6999ca1f3a862c
|
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.1.7
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
Copyright (c) 2014 Bonobos, Inc.
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without modification,
|
5
|
+
are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
* Redistributions of source code must retain the above copyright notice,
|
8
|
+
this list of conditions and the following disclaimer.
|
9
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
10
|
+
this list of conditions and the following disclaimer in the documentation
|
11
|
+
and/or other materials provided with the distribution.
|
12
|
+
* Neither the name Bonobos nor the names of its contributors may be used to
|
13
|
+
endorse or promote products derived from this software without specific
|
14
|
+
prior written permission.
|
15
|
+
|
16
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
17
|
+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
18
|
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
19
|
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
20
|
+
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
21
|
+
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
22
|
+
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
23
|
+
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
24
|
+
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
25
|
+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
26
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
Solidus Signifyd
|
2
|
+
================
|
3
|
+
|
4
|
+
Integration with Signifyd that implements a fraud check prior to marking a
|
5
|
+
shipment as ready to be shipped.
|
6
|
+
|
7
|
+
Installation
|
8
|
+
------------
|
9
|
+
|
10
|
+
In your Gemfile:
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
gem "solidus_signifyd"
|
14
|
+
```
|
15
|
+
|
16
|
+
Bundle your dependencies and run the installation generator:
|
17
|
+
|
18
|
+
```shell
|
19
|
+
bundle
|
20
|
+
bundle exec rails g solidus_signifyd:install
|
21
|
+
```
|
22
|
+
|
23
|
+
Testing
|
24
|
+
-------
|
25
|
+
|
26
|
+
First bundle your dependencies, then run `rake`. `rake` will default to
|
27
|
+
building the dummy app if it does not exist, then it will run specs. The dummy
|
28
|
+
app can be regenerated by using `rake test_app`.
|
29
|
+
|
30
|
+
```shell
|
31
|
+
bundle exec rake
|
32
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
require 'spree/testing_support/extension_rake'
|
6
|
+
|
7
|
+
RSpec::Core::RakeTask.new
|
8
|
+
|
9
|
+
task :default do
|
10
|
+
if Dir["spec/dummy"].empty?
|
11
|
+
Rake::Task[:test_app].invoke
|
12
|
+
Dir.chdir("../../")
|
13
|
+
end
|
14
|
+
Rake::Task[:spec].invoke
|
15
|
+
end
|
16
|
+
|
17
|
+
desc 'Generates a dummy app for testing'
|
18
|
+
task :test_app do
|
19
|
+
ENV['LIB_NAME'] = 'solidus_signifyd'
|
20
|
+
Rake::Task['extension:test_app'].invoke
|
21
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Spree::Api::SpreeSignifyd
|
2
|
+
class OrdersController < ActionController::Base
|
3
|
+
include SpreeSignifyd::RequestVerifier
|
4
|
+
|
5
|
+
respond_to :json
|
6
|
+
|
7
|
+
before_filter :authorize, :load_order, :order_canceled_or_shipped
|
8
|
+
|
9
|
+
def update
|
10
|
+
SpreeSignifyd.set_score(order: @order, score: score)
|
11
|
+
|
12
|
+
if is_fraudulent?
|
13
|
+
@order.cancel!
|
14
|
+
elsif should_approve?
|
15
|
+
SpreeSignifyd.approve(order: @order)
|
16
|
+
end
|
17
|
+
|
18
|
+
render nothing: true, status: 200
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def authorize
|
24
|
+
request_sha = request.headers['HTTP_HTTP_X_SIGNIFYD_HMAC_SHA256']
|
25
|
+
computed_sha = build_sha(SpreeSignifyd::Config[:api_key], encode_request(request.raw_post))
|
26
|
+
|
27
|
+
head 401 unless Devise.secure_compare(request_sha, computed_sha)
|
28
|
+
end
|
29
|
+
|
30
|
+
def load_order
|
31
|
+
head 404 unless @order = Spree::Order.find_by(number: body['orderId'])
|
32
|
+
end
|
33
|
+
|
34
|
+
def order_canceled_or_shipped
|
35
|
+
head 200 if @order.shipped? || @order.canceled?
|
36
|
+
end
|
37
|
+
|
38
|
+
def body
|
39
|
+
@body ||= JSON.parse(request.raw_post)
|
40
|
+
end
|
41
|
+
|
42
|
+
def is_fraudulent?
|
43
|
+
body['reviewDisposition'] == 'FRAUDULENT'
|
44
|
+
end
|
45
|
+
|
46
|
+
def should_approve?
|
47
|
+
body['reviewDisposition'] == 'GOOD' || SpreeSignifyd.score_above_threshold?(score)
|
48
|
+
end
|
49
|
+
|
50
|
+
def score
|
51
|
+
body['adjustedScore']
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
Spree::Order.include SpreeSignifyd::OrderConcerns
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module SpreeSignifyd::OrderConcerns
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
Spree::Order.state_machine.after_transition to: :complete, unless: :approved? do |order, transition|
|
6
|
+
SpreeSignifyd.create_case(order_number: order.number)
|
7
|
+
end
|
8
|
+
|
9
|
+
has_one :signifyd_order_score, class_name: "SpreeSignifyd::OrderScore"
|
10
|
+
|
11
|
+
prepend(InstanceMethods)
|
12
|
+
end
|
13
|
+
|
14
|
+
module InstanceMethods
|
15
|
+
def is_risky?
|
16
|
+
!(awaiting_approval? || approved?)
|
17
|
+
end
|
18
|
+
|
19
|
+
def awaiting_approval?
|
20
|
+
!signifyd_order_score
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'active_model/serializer'
|
2
|
+
|
3
|
+
module SpreeSignifyd
|
4
|
+
class AddressSerializer < ActiveModel::Serializer
|
5
|
+
self.root = false
|
6
|
+
|
7
|
+
attributes :address
|
8
|
+
|
9
|
+
def address
|
10
|
+
{
|
11
|
+
'streetAddress' => object.address1,
|
12
|
+
'unit' => object.address2,
|
13
|
+
'city' => object.city,
|
14
|
+
'provinceCode' => object.state_text,
|
15
|
+
'postalCode' => object.zipcode,
|
16
|
+
'countryCode' => object.country.iso
|
17
|
+
}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'active_model/serializer'
|
2
|
+
|
3
|
+
module SpreeSignifyd
|
4
|
+
class CreditCardSerializer < ActiveModel::Serializer
|
5
|
+
self.root = false
|
6
|
+
|
7
|
+
attributes :cardHolderName, :last4, :expiryMonth, :expiryYear
|
8
|
+
|
9
|
+
def cardHolderName
|
10
|
+
"#{object.first_name} #{object.last_name}"
|
11
|
+
end
|
12
|
+
|
13
|
+
def last4
|
14
|
+
object.last_digits
|
15
|
+
end
|
16
|
+
|
17
|
+
def expiryMonth
|
18
|
+
object.month
|
19
|
+
end
|
20
|
+
|
21
|
+
def expiryYear
|
22
|
+
object.year
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'active_model/serializer'
|
2
|
+
|
3
|
+
module SpreeSignifyd
|
4
|
+
class DeliveryAddressSerializer < AddressSerializer
|
5
|
+
self.root = false
|
6
|
+
|
7
|
+
def attributes
|
8
|
+
hash = {}
|
9
|
+
hash['deliveryAddress'] = address
|
10
|
+
hash['fullName'] = object.full_name
|
11
|
+
hash
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'active_model/serializer'
|
2
|
+
|
3
|
+
module SpreeSignifyd
|
4
|
+
class LineItemSerializer < ActiveModel::Serializer
|
5
|
+
self.root = false
|
6
|
+
|
7
|
+
attributes :itemId, :itemName, :itemQuantity, :itemPrice
|
8
|
+
|
9
|
+
def itemId
|
10
|
+
object.variant_id
|
11
|
+
end
|
12
|
+
|
13
|
+
def itemName
|
14
|
+
object.name
|
15
|
+
end
|
16
|
+
|
17
|
+
def itemQuantity
|
18
|
+
object.quantity
|
19
|
+
end
|
20
|
+
|
21
|
+
def itemPrice
|
22
|
+
object.price
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'active_model/serializer'
|
2
|
+
|
3
|
+
module SpreeSignifyd
|
4
|
+
class OrderSerializer < ActiveModel::Serializer
|
5
|
+
self.root = false
|
6
|
+
|
7
|
+
attributes :purchase, :recipient, :card
|
8
|
+
has_one :user, serializer: SpreeSignifyd::UserSerializer, root: "userAccount"
|
9
|
+
|
10
|
+
def purchase
|
11
|
+
{
|
12
|
+
'browserIpAddress' => object.last_ip_address,
|
13
|
+
'orderId' => object.number,
|
14
|
+
'createdAt' => object.completed_at.utc.iso8601,
|
15
|
+
'currency' => object.currency,
|
16
|
+
'totalPrice' => object.total,
|
17
|
+
'products' => products,
|
18
|
+
'avsResponseCode' => latest_payment.try(:avs_response),
|
19
|
+
'cvvResponseCode' => latest_payment.try(:cvv_response_code)
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def recipient
|
24
|
+
recipient = SpreeSignifyd::DeliveryAddressSerializer.new(object.ship_address).serializable_object
|
25
|
+
recipient[:confirmationEmail] = object.email
|
26
|
+
recipient[:fullName] = object.ship_address.full_name
|
27
|
+
recipient
|
28
|
+
end
|
29
|
+
|
30
|
+
def card
|
31
|
+
payment_source = latest_payment.try(:source)
|
32
|
+
card = {}
|
33
|
+
|
34
|
+
if payment_source.present? && payment_source.instance_of?(Spree::CreditCard)
|
35
|
+
card = CreditCardSerializer.new(payment_source).serializable_object
|
36
|
+
card.merge!(SpreeSignifyd::BillingAddressSerializer.new(object.bill_address).serializable_object)
|
37
|
+
end
|
38
|
+
|
39
|
+
card
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def products
|
45
|
+
order_products = []
|
46
|
+
|
47
|
+
object.line_items.each do |li|
|
48
|
+
serialized_line_item = SpreeSignifyd::LineItemSerializer.new(li).serializable_object
|
49
|
+
order_products << serialized_line_item
|
50
|
+
end
|
51
|
+
|
52
|
+
order_products
|
53
|
+
end
|
54
|
+
|
55
|
+
def latest_payment
|
56
|
+
object.payments.order(:created_at, :id).last
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'active_model/serializer'
|
2
|
+
|
3
|
+
module SpreeSignifyd
|
4
|
+
class UserSerializer < ActiveModel::Serializer
|
5
|
+
self.root = false
|
6
|
+
|
7
|
+
attributes :emailAddress, :username, :createdDate, :lastUpdateDate, :lastOrderId, :aggregateOrderCount, :aggregateOrderDollars
|
8
|
+
|
9
|
+
def emailAddress
|
10
|
+
object.email
|
11
|
+
end
|
12
|
+
|
13
|
+
def username
|
14
|
+
object.email
|
15
|
+
end
|
16
|
+
|
17
|
+
def createdDate
|
18
|
+
object.created_at.utc.iso8601
|
19
|
+
end
|
20
|
+
|
21
|
+
def lastUpdateDate
|
22
|
+
object.updated_at.utc.iso8601
|
23
|
+
end
|
24
|
+
|
25
|
+
def lastOrderId
|
26
|
+
completed_orders.order("completed_at DESC").second.try(:number)
|
27
|
+
end
|
28
|
+
|
29
|
+
def aggregateOrderCount
|
30
|
+
completed_orders.count
|
31
|
+
end
|
32
|
+
|
33
|
+
def aggregateOrderDollars
|
34
|
+
completed_orders.sum(:total)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def completed_orders
|
40
|
+
@order ||= object.orders.complete
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|