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.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.rspec +1 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +10 -0
  6. data/LICENSE +26 -0
  7. data/README.md +32 -0
  8. data/Rakefile +21 -0
  9. data/app/controllers/spree/api/spree_signifyd/orders_controller.rb +54 -0
  10. data/app/models/spree/order_decorator.rb +1 -0
  11. data/app/models/spree/signifyd_configuration.rb +6 -0
  12. data/app/models/spree_signifyd/order_concerns.rb +23 -0
  13. data/app/models/spree_signifyd/order_score.rb +9 -0
  14. data/app/models/spree_signifyd/shipment_decorator.rb +11 -0
  15. data/app/overrides/admin_order_signifyd_risk_analysis.rb +6 -0
  16. data/app/serializers/spree_signifyd/address_serializer.rb +20 -0
  17. data/app/serializers/spree_signifyd/billing_address_serializer.rb +13 -0
  18. data/app/serializers/spree_signifyd/credit_card_serializer.rb +25 -0
  19. data/app/serializers/spree_signifyd/delivery_address_serializer.rb +14 -0
  20. data/app/serializers/spree_signifyd/line_item_serializer.rb +25 -0
  21. data/app/serializers/spree_signifyd/order_serializer.rb +59 -0
  22. data/app/serializers/spree_signifyd/user_serializer.rb +44 -0
  23. data/app/views/spree/admin/orders/_signifyd_score.html.erb +10 -0
  24. data/bin/rails +7 -0
  25. data/circle.yml +6 -0
  26. data/config/locales/en.yml +7 -0
  27. data/config/routes.rb +7 -0
  28. data/db/migrate/20140819203000_add_signifyd_score_to_orders.rb +5 -0
  29. data/db/migrate/20140826202644_set_considered_risky_default_value.rb +5 -0
  30. data/db/migrate/20150206151312_create_spree_signifyd_order_scores.rb +11 -0
  31. data/db/migrate/20150206193231_transfer_spree_orders_signifyd_score_data.rb +15 -0
  32. data/db/migrate/20150211202803_remove_spree_orders_signifyd_score_column.rb +5 -0
  33. data/lib/generators/solidus_signifyd/install/install_generator.rb +26 -0
  34. data/lib/generators/solidus_signifyd/install/templates/solidus_signifyd.rb +3 -0
  35. data/lib/solidus_signifyd.rb +1 -0
  36. data/lib/spree_signifyd.rb +37 -0
  37. data/lib/spree_signifyd/create_signifyd_case.rb +13 -0
  38. data/lib/spree_signifyd/engine.rb +24 -0
  39. data/lib/spree_signifyd/request_verifier.rb +14 -0
  40. data/solidus_signifyd.gemspec +36 -0
  41. data/spec/controllers/spree/api/spree_signifyd/orders_controller_spec.rb +202 -0
  42. data/spec/lib/spree_signifyd/create_signifyd_case_spec.rb +20 -0
  43. data/spec/lib/spree_signifyd/request_verifier_spec.rb +27 -0
  44. data/spec/lib/spree_signifyd_spec.rb +66 -0
  45. data/spec/models/spree/order_spec.rb +51 -0
  46. data/spec/models/spree/shipment_spec.rb +59 -0
  47. data/spec/serializers/spree_signifyd/address_serializer_spec.rb +42 -0
  48. data/spec/serializers/spree_signifyd/billing_address_serializer.rb +12 -0
  49. data/spec/serializers/spree_signifyd/credit_card_serializer_spec.rb +26 -0
  50. data/spec/serializers/spree_signifyd/delivery_address_serializer_spec.rb +13 -0
  51. data/spec/serializers/spree_signifyd/line_item_serializer_spec.rb +26 -0
  52. data/spec/serializers/spree_signifyd/order_serializer_spec.rb +72 -0
  53. data/spec/serializers/spree_signifyd/user_serializer_spec.rb +53 -0
  54. data/spec/spec_helper.rb +63 -0
  55. 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
@@ -0,0 +1,15 @@
1
+ *.gem
2
+ \#*
3
+ *~
4
+ .#*
5
+ .DS_Store
6
+ .idea
7
+ .project
8
+ .sass-cache
9
+ coverage
10
+ Gemfile.lock
11
+ tmp
12
+ nbproject
13
+ pkg
14
+ *.swp
15
+ spec/dummy
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.1.7
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem "solidus", git: "git@github.com:solidusio/solidus.git", branch: "v1.0"
4
+ gem "solidus_auth_devise", "~> 1.0"
5
+
6
+ group :development, :test do
7
+ gem "pry-rails"
8
+ end
9
+
10
+ gemspec
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,6 @@
1
+ module Spree
2
+ class SignifydConfiguration < Preferences::Configuration
3
+ preference :api_key, :string
4
+ preference :signifyd_score_threshold, :integer, default: 500 # Signifyd's recommended threshold
5
+ end
6
+ end
@@ -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,9 @@
1
+ module SpreeSignifyd
2
+ class OrderScore < ActiveRecord::Base
3
+ self.table_name = :spree_signifyd_order_scores
4
+
5
+ belongs_to :order, class_name: "Spree::Order"
6
+ validates :score, numericality: true
7
+ validates :order, presence: true
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ module SpreeSignifyd
2
+ module ShipmentDecorator
3
+
4
+ def determine_state(order)
5
+ return 'pending' if (pending? || canceled?) && !order.approved?
6
+ super(order)
7
+ end
8
+ end
9
+ end
10
+
11
+ Spree::Shipment.prepend SpreeSignifyd::ShipmentDecorator
@@ -0,0 +1,6 @@
1
+ Deface::Override.new(
2
+ virtual_path: "spree/admin/orders/_risk_analysis",
3
+ name: "admin_order_signifyd_risk_analysis",
4
+ insert_bottom: "[data-hook='order_details_adjustments']",
5
+ partial: "spree/admin/orders/signifyd_score",
6
+ )
@@ -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,13 @@
1
+ require 'active_model/serializer'
2
+
3
+ module SpreeSignifyd
4
+ class BillingAddressSerializer < AddressSerializer
5
+ self.root = false
6
+
7
+ def attributes
8
+ hash = {}
9
+ hash['billingAddress'] = address
10
+ hash
11
+ end
12
+ end
13
+ 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