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
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ module SpreeSignifyd
4
+ describe CreateSignifydCase do
5
+ describe "#perform" do
6
+ let(:order) { create(:order_ready_to_ship) }
7
+ let(:json) { JSON.parse(OrderSerializer.new(order).to_json) }
8
+
9
+ it "calls Signifyd::Case#create with the correct params" do
10
+ Signifyd::Case.should_receive(:create).with(json, SpreeSignifyd::Config[:api_key])
11
+ CreateSignifydCase.perform(order.id)
12
+ end
13
+
14
+ it "calls Signifyd::Case#create with the correct params" do
15
+ Signifyd::Case.should_receive(:create).with(json, SpreeSignifyd::Config[:api_key])
16
+ CreateSignifydCase.perform(order.number)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ module SpreeSignifyd
4
+ describe RequestVerifier do
5
+ include RequestVerifier
6
+
7
+ describe "#encode_request" do
8
+ context "request has special characters" do
9
+ it "returns an unescaped UTF-8 string" do
10
+ expect(encode_request("R\xE9n\xE9 Pe\xF1a")).to eq "Réné Peña"
11
+ end
12
+ end
13
+
14
+ context "request doesn't contain special characters" do
15
+ it "returns the original string" do
16
+ expect(encode_request("John Doe")).to eq "John Doe"
17
+ end
18
+ end
19
+ end
20
+
21
+ describe "#build_sha" do
22
+ it "returns an HMAC SHA256 encoded message" do
23
+ expect(build_sha('ABCDE', 'test')).to eq "K0y2rIeTA77lBEHP8cRPk64fVRbhMrZqEk7la39EjEM="
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+
3
+ module SpreeSignifyd
4
+ describe SpreeSignifyd do
5
+
6
+ describe ".set_score" do
7
+
8
+ let(:order) { FactoryGirl.create(:order) }
9
+
10
+ def set_score(score)
11
+ SpreeSignifyd.set_score(order: order, score: score)
12
+ end
13
+
14
+ it 'creates or updates the score' do
15
+ expect {
16
+ set_score(100)
17
+ }.to change { SpreeSignifyd::OrderScore.count }.by(1)
18
+
19
+ expect(order.signifyd_order_score.score).to eq 100
20
+
21
+ expect {
22
+ set_score(200)
23
+ }.not_to change { SpreeSignifyd::OrderScore.count }
24
+
25
+ expect(order.signifyd_order_score.score).to eq 200
26
+ end
27
+
28
+ end
29
+
30
+ describe ".approve" do
31
+
32
+ let(:order) { FactoryGirl.create(:order_ready_to_ship, line_items_count: 1) }
33
+
34
+ before do
35
+ order.shipments.each { |shipment| shipment.update_attributes!(state: 'pending') }
36
+ order.updater.update_shipment_state
37
+ end
38
+
39
+ def approve
40
+ SpreeSignifyd.approve(order: order)
41
+ end
42
+
43
+ context "updates the order" do
44
+ it { expect { approve }.to change { order.approver_name }.to "SpreeSignifyd" }
45
+ it { expect { approve }.to change { order.shipment_state }.to 'ready' }
46
+ it do
47
+ expect(order.approved_at).to eq nil
48
+ expect { approve }.to change { order.approved_at }
49
+ end
50
+ end
51
+
52
+ it 'readies all of the shipments' do
53
+ order.shipments.each { |shipment| shipment.should_receive(:ready!) }
54
+ approve
55
+ end
56
+ end
57
+
58
+ describe ".create_case" do
59
+ it 'enqueues in resque' do
60
+ expect(Resque).to receive(:enqueue).with(SpreeSignifyd::CreateSignifydCase, 111)
61
+ SpreeSignifyd.create_case(order_number: 111)
62
+ end
63
+ end
64
+
65
+ end
66
+ end
@@ -0,0 +1,51 @@
1
+ require 'spec_helper'
2
+
3
+ describe Spree::Order, :type => :model do
4
+
5
+ let!(:order) { create(:order_ready_to_ship, line_items_count: 1) }
6
+
7
+ before do
8
+ order.shipments.each { |shipment| shipment.update_attributes!(state: 'pending') }
9
+ order.updater.update_shipment_state
10
+ end
11
+
12
+ describe "#is_risky?" do
13
+ subject { order.is_risky? }
14
+
15
+ context "no signifyd_score" do
16
+ it { should eq false }
17
+ end
18
+
19
+ context "signifyd_score present" do
20
+ before { SpreeSignifyd.set_score(score: 500, order: order) }
21
+
22
+ context "approved" do
23
+ before { SpreeSignifyd.approve(order: order) }
24
+ it { should eq false }
25
+ end
26
+
27
+ context "not approved" do
28
+ it { should eq true }
29
+ end
30
+ end
31
+ end
32
+
33
+ describe "transition to complete" do
34
+ let(:order) { create(:order_with_line_items, state: 'confirm') }
35
+ let!(:payment) { create(:payment, amount: order.total, order: order ) }
36
+
37
+ it "calls #create_signifyd_case" do
38
+ expect(SpreeSignifyd).to receive(:create_case).with(order_number: order.number)
39
+ order.complete!
40
+ end
41
+
42
+ context "the order is already approved" do # e.g. unreturned exchanges are automatically approved
43
+ it "does not create a case" do
44
+ order.contents.approve(user: Spree.user_class.first)
45
+ expect(SpreeSignifyd).not_to receive(:create_case)
46
+ order.complete!
47
+ end
48
+ end
49
+ end
50
+
51
+ end
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ describe Spree::Shipment, :type => :model do
4
+
5
+ let(:shipment) { create(:shipment) }
6
+ subject { shipment.determine_state(shipment.order) }
7
+
8
+ describe "#determine_state_with_signifyd" do
9
+
10
+ context "with a risky order" do
11
+ before { shipment.order.stub(:is_risky?).and_return(true) }
12
+
13
+ context "the order is not approved" do
14
+ it "returns pending" do
15
+ shipment.order.stub(:approved?).and_return(false)
16
+ subject.should eq "pending"
17
+ end
18
+ end
19
+
20
+ context "the order is approved" do
21
+ it "defaults to existing behavior" do
22
+ shipment.order.stub(:approved?).and_return(true)
23
+ shipment.should_receive(:determine_state).with(shipment.order)
24
+ subject
25
+ end
26
+ end
27
+ end
28
+
29
+ context "without a risky order" do
30
+ before { shipment.order.stub(:is_risky?).and_return(false) }
31
+
32
+ it "defaults to existing behavior" do
33
+ shipment.should_receive(:determine_state).with(shipment.order)
34
+ subject
35
+ end
36
+ end
37
+
38
+ context "shipment state" do
39
+ [:shipped, :ready].each do |state|
40
+ context "the shipment is #{state}" do
41
+ before { shipment.update_columns(state: state) }
42
+ it "defaults to existing behavior" do
43
+ shipment.should_receive(:determine_state).with(shipment.order)
44
+ subject
45
+ end
46
+ end
47
+ end
48
+
49
+ [:pending, :canceled].each do |state|
50
+ context "the shipment is #{state}" do
51
+ before { shipment.update_columns(state: state) }
52
+ it "is pending" do
53
+ subject.should eq "pending"
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ module SpreeSignifyd
4
+ describe AddressSerializer do
5
+ let(:ship_address) { create(:address) }
6
+ let(:serialized_address) { JSON.parse(AddressSerializer.new(ship_address).to_json) }
7
+
8
+ context "node values" do
9
+ let(:address) { serialized_address['address'] }
10
+
11
+ it "streetAddress" do
12
+ expect(address['streetAddress']).to eq ship_address.address1
13
+ end
14
+
15
+ it "unit" do
16
+ expect(address['unit']).to eq ship_address.address2
17
+ end
18
+
19
+ it "city" do
20
+ expect(address['city']).to eq ship_address.city
21
+ end
22
+
23
+ describe "provinceCode" do
24
+ it "with a state entity associated" do
25
+ expect(address['provinceCode']).to eq ship_address.state.abbr
26
+ end
27
+ it "with a state_name and no state entity" do
28
+ ship_address.update_attributes!(state_name: ship_address.state.name, state_id: nil)
29
+ expect(address['provinceCode']).to eq ship_address.state_name
30
+ end
31
+ end
32
+
33
+ it "postalCode" do
34
+ expect(address['postalCode']).to eq ship_address.zipcode
35
+ end
36
+
37
+ it "countryCode" do
38
+ expect(address['countryCode']).to eq ship_address.country.iso
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+
3
+ module SpreeSignifyd
4
+ describe BillingAddressSerializer do
5
+ let(:bill_address) { create(:address) }
6
+ let(:serialized_address) { JSON.parse(BillingAddressSerializer.new(bill_address).to_json) }
7
+
8
+ context "node values" do
9
+ it { serialized_address.should include 'billingAddress' }
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ module SpreeSignifyd
4
+ describe CreditCardSerializer do
5
+ let(:credit_card) { create(:credit_card) }
6
+ let(:serialized_credit_card) { JSON.parse(CreditCardSerializer.new(credit_card).to_json) }
7
+
8
+ context "node values" do
9
+ it "cardHolderName" do
10
+ expect(serialized_credit_card['cardHolderName']).to eq "#{credit_card.first_name} #{credit_card.last_name}"
11
+ end
12
+
13
+ it "last4" do
14
+ expect(serialized_credit_card['last4']).to eq credit_card.last_digits
15
+ end
16
+
17
+ it "expiryMonth" do
18
+ expect(serialized_credit_card['expiryMonth']).to eq credit_card.month
19
+ end
20
+
21
+ it "expiryYear" do
22
+ expect(serialized_credit_card['expiryYear']).to eq credit_card.year
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ module SpreeSignifyd
4
+ describe DeliveryAddressSerializer do
5
+ let(:delivery_address) { create(:address) }
6
+ let(:serialized_address) { JSON.parse(DeliveryAddressSerializer.new(delivery_address).to_json) }
7
+
8
+ context "node values" do
9
+ it { serialized_address.should include 'deliveryAddress' }
10
+ it { serialized_address['fullName'].should eq delivery_address.full_name }
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ module SpreeSignifyd
4
+ describe LineItemSerializer do
5
+ let(:line_item) { create(:line_item) }
6
+ let(:serialized_line_item) { JSON.parse(LineItemSerializer.new(line_item).to_json) }
7
+
8
+ context "node values" do
9
+ it "itemId" do
10
+ expect(serialized_line_item['itemId']).to eq line_item.variant_id
11
+ end
12
+
13
+ it "itemName" do
14
+ expect(serialized_line_item['itemName']).to eq line_item.name
15
+ end
16
+
17
+ it "itemQuantity" do
18
+ expect(serialized_line_item['itemQuantity']).to eq line_item.quantity
19
+ end
20
+
21
+ it "itemPrice" do
22
+ expect(serialized_line_item['itemPrice']).to eq line_item.price.to_s
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+
3
+ module SpreeSignifyd
4
+ describe OrderSerializer do
5
+ let(:order) { create(:shipped_order, line_items_count: 1) }
6
+ let(:line_item) { order.line_items.first }
7
+ let(:serialized_order) { JSON.parse(OrderSerializer.new(order).to_json) }
8
+
9
+ describe "node values" do
10
+ context "purchase" do
11
+
12
+ let(:purchase) { serialized_order['purchase'] }
13
+
14
+ it { purchase['browserIpAddress'].should eq order.last_ip_address }
15
+ it { purchase['orderId'].should eq order.number }
16
+ it { purchase['createdAt'].should eq order.completed_at.utc.iso8601 }
17
+ it { purchase['currency'].should eq order.currency }
18
+ it { purchase['totalPrice'].should eq order.total.to_s }
19
+
20
+ context "with a payment" do
21
+ it { purchase['avsResponseCode'].should eq order.payments.last.avs_response }
22
+ it { purchase['cvvResponseCode'].should eq order.payments.last.cvv_response_code }
23
+ end
24
+
25
+ context "without a payment" do
26
+ let(:order) { create(:completed_order_with_totals) }
27
+
28
+ it { purchase['avsResponseCode'].should be nil }
29
+ it { purchase['cvvResponseCode'].should be nil }
30
+ end
31
+
32
+ it "contains a products node" do
33
+ purchase['products'].should eq [ JSON.parse(SpreeSignifyd::LineItemSerializer.new(line_item).to_json) ]
34
+ end
35
+ end
36
+
37
+ context "userAccount" do
38
+ it { serialized_order.should include 'userAccount' }
39
+ end
40
+
41
+ context "recipient" do
42
+ it { serialized_order.should include 'recipient' }
43
+ it { serialized_order["recipient"]["confirmationEmail"].should eq order.email }
44
+ end
45
+
46
+ context "card" do
47
+ it { serialized_order.should include 'card' }
48
+
49
+ context "credit card payment" do
50
+ let!(:payment) { create(:payment, order: order) }
51
+
52
+ it { serialized_order["card"].should include 'billingAddress'}
53
+ end
54
+
55
+ context "no payment source" do
56
+ let(:order) { create(:completed_order_with_totals) }
57
+
58
+ it "contains no data" do
59
+ expect(serialized_order["card"]).to eq({})
60
+ end
61
+ end
62
+
63
+ context "non credit card payment" do
64
+ it "contains no data" do
65
+ Spree::CreditCard.any_instance.stub(:instance_of?).and_return(false)
66
+ expect(serialized_order["card"]).to eq({})
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+
3
+ module SpreeSignifyd
4
+ describe UserSerializer do
5
+ let(:user) { create(:user) }
6
+ let!(:incomplete_order) { create(:order, user: user) }
7
+ let!(:old_complete_order) { create(:shipped_order, user: user, created_at: 30.days.ago) }
8
+ let!(:new_complete_order) { create(:shipped_order, user: user) }
9
+
10
+ let(:serialized_user) { JSON.parse(UserSerializer.new(user).to_json) }
11
+
12
+ context "node values" do
13
+ it "emailAddress" do
14
+ expect(serialized_user['emailAddress']).to eq user.email
15
+ end
16
+
17
+ it "username" do
18
+ expect(serialized_user['username']).to eq user.email
19
+ end
20
+
21
+ it "createdDate" do
22
+ expect(serialized_user['createdDate']).to eq user.created_at.utc.iso8601
23
+ end
24
+
25
+ it "lastUpdateDate" do
26
+ expect(serialized_user['lastUpdateDate']).to eq user.updated_at.utc.iso8601
27
+ end
28
+
29
+ context "with at least 2 orders" do
30
+ it "lastOrderId" do
31
+ expect(serialized_user['lastOrderId']).to eq old_complete_order.number
32
+ end
33
+ end
34
+
35
+ context "without prior orders" do
36
+ let(:user_without_orders) { create(:user) }
37
+ let(:serialized_user) { JSON.parse(UserSerializer.new(user_without_orders).to_json) }
38
+
39
+ it "lastOrderId" do
40
+ expect(serialized_user['lastOrderId']).to eq nil
41
+ end
42
+ end
43
+
44
+ it "aggregateOrderCount" do
45
+ expect(serialized_user['aggregateOrderCount']).to eq 2
46
+ end
47
+
48
+ it "aggregateOrderDollars" do
49
+ expect(serialized_user['aggregateOrderDollars']).to eq (old_complete_order.total + new_complete_order.total).to_s
50
+ end
51
+ end
52
+ end
53
+ end