diff --git a/app/controllers/solidus_paypal_braintree/transactions_controller.rb b/app/controllers/solidus_paypal_braintree/transactions_controller.rb
index 2423c7c7..9ed3ce93 100644
--- a/app/controllers/solidus_paypal_braintree/transactions_controller.rb
+++ b/app/controllers/solidus_paypal_braintree/transactions_controller.rb
@@ -1,4 +1,6 @@
class SolidusPaypalBraintree::TransactionsController < Spree::StoreController
+ class InvalidTransactionError < StandardError; end
+
PERMITTED_BRAINTREE_TRANSACTION_PARAMS = [
:nonce,
:payment_type,
@@ -23,7 +25,8 @@ def create
return redirect_to spree.checkout_state_path(import.order.state)
end
else
- render text: transaction.errors
+ raise InvalidTransactionError,
+ "Transaction invalid: #{transaction.errors.full_messages.join(', ')}"
end
end
diff --git a/spec/controllers/solidus_paypal_braintree/transactions_controller_spec.rb b/spec/controllers/solidus_paypal_braintree/transactions_controller_spec.rb
new file mode 100644
index 00000000..f7966c49
--- /dev/null
+++ b/spec/controllers/solidus_paypal_braintree/transactions_controller_spec.rb
@@ -0,0 +1,102 @@
+require 'spec_helper'
+
+RSpec.describe SolidusPaypalBraintree::TransactionsController, type: :controller do
+ include_context "order ready for payment"
+
+ let(:payment_method) { create_gateway }
+
+ before do
+ allow(controller).to receive(:spree_current_user) { user }
+ allow(controller).to receive(:current_order) { order }
+ end
+
+ cassette_options = { cassette_name: "transactions_controller/create" }
+ describe "POST create", vcr: cassette_options do
+ subject(:post_create) { post :create, params }
+
+ let(:params) do
+ {
+ transaction: {
+ nonce: "ABC123",
+ payment_type: "MonopolyMoney",
+ phone: "1112223333",
+ email: "batman@example.com",
+ address_attributes: {
+ first_name: "Wade",
+ last_name: "Wilson",
+ address_line_1: "123 Fake Street",
+ city: "Seattle",
+ zip: "98101",
+ state_code: "WA",
+ country_code: "US"
+ }
+ },
+ payment_method_id: payment_method.id
+ }
+ end
+
+ context "when the transaction is valid" do
+ it "imports the payment" do
+ expect { post_create }.to change { order.payments.count }.by(1)
+ expect(order.payments.first.amount).to eq 55
+ end
+
+ context "and a valid address is provided" do
+ it "creates a new address" do
+ # Creating the order also creates 3 addresses, we want to make sure
+ # the transaction import only creates 1 new one
+ order
+ expect { post_create }.to change { Spree::Address.count }.by(1)
+ expect(Spree::Address.last.full_name).to eq "Wade Wilson"
+ end
+ end
+
+ context "and an invalid address is provided" do
+ before { params[:transaction][:address_attributes][:city] = nil }
+
+ it "raises a validation error" do
+ expect { post_create }.to raise_error(
+ ActiveRecord::RecordInvalid,
+ "Validation failed: " \
+ "Billing address city can't be blank, " \
+ "Shipping address city can't be blank"
+ )
+ end
+ end
+
+ context "and the transaction does not have an address" do
+ before { params[:transaction].delete(:address_attributes) }
+
+ it "does not create a new address" do
+ order
+ expect { post_create }.to_not change { Spree::Address.count }
+ end
+ end
+
+ context "when import! leaves the order in confirm" do
+ it "redirects the user to the confirm page" do
+ expect(post_create).to redirect_to spree.checkout_state_path("confirm")
+ end
+ end
+
+ context "when import! completes the order" do
+ before { allow(order).to receive(:complete?).and_return(true) }
+
+ it "displays the order to the user" do
+ expect(post_create).to redirect_to spree.order_path(order)
+ end
+ end
+ end
+
+ context "when the transaction is invalid" do
+ before { params[:transaction].delete(:phone) }
+
+ it "raises an error" do
+ expect { post_create }.to raise_error(
+ SolidusPaypalBraintree::TransactionsController::InvalidTransactionError,
+ "Transaction invalid: Phone can't be blank"
+ )
+ end
+ end
+ end
+end
diff --git a/spec/fixtures/cassettes/transactions_controller/create.yml b/spec/fixtures/cassettes/transactions_controller/create.yml
new file mode 100644
index 00000000..449cab3f
--- /dev/null
+++ b/spec/fixtures/cassettes/transactions_controller/create.yml
@@ -0,0 +1,141 @@
+---
+http_interactions:
+- request:
+ method: post
+ uri: https://api.sandbox.braintreegateway.com/merchants/7rdg92j7bm7fk5h3/customers
+ body:
+ encoding: UTF-8
+ string: |
+
+
+
+ headers:
+ Accept-Encoding:
+ - gzip
+ Accept:
+ - application/xml
+ User-Agent:
+ - Braintree Ruby Gem 2.66.0
+ X-Apiversion:
+ - '4'
+ Authorization:
+ - Basic bXdqa2t4d2NwMzJja2huZjphOTI5OGY0M2IzMGM2OTlkYjMwNzJjYzRhMDBmN2Y0OQ==
+ Content-Type:
+ - application/xml
+ response:
+ status:
+ code: 201
+ message: Created
+ headers:
+ Date:
+ - Wed, 14 Sep 2016 15:56:51 GMT
+ Content-Type:
+ - application/xml; charset=utf-8
+ Transfer-Encoding:
+ - chunked
+ X-Frame-Options:
+ - SAMEORIGIN
+ - SAMEORIGIN
+ X-Xss-Protection:
+ - 1; mode=block
+ X-Content-Type-Options:
+ - nosniff
+ X-Authentication:
+ - basic_auth
+ X-User:
+ - 3v249hqtptsg744y
+ Vary:
+ - Accept-Encoding
+ Content-Encoding:
+ - gzip
+ Etag:
+ - W/"cf917cd8f592cf2b13a7c0842a0ccf71"
+ Cache-Control:
+ - max-age=0, private, must-revalidate
+ X-Request-Id:
+ - dec51a53-1dae-4d14-8769-2ddc9ba6a55a
+ X-Runtime:
+ - '0.146172'
+ Strict-Transport-Security:
+ - max-age=31536000; includeSubDomains
+ body:
+ encoding: ASCII-8BIT
+ string: !binary |-
+ H4sIAENz2VcAA5SRsXKDMBBEe3+FRr0CwsEEj8Cdv8Bp0p25wyhBgpGEY/4+
+ mHjszJAUKXef9m7npHYX07IzOa87W3D5FHNGtupQ21PBXw978cJ35UpVgw+d
+ IVeuGFMay2Sdx0meJSqaxNWbWNWADWLSmcNTnrxnR5PVH2mzVtFPen1da+eD
+ sGCIWd0WPLiBeDSjFv4iVWd6sOPCJwO6Xbh909nljBouC++Tjl6HX/Y5gkAo
+ ILAw9lRwnGTQhniZxHIj4lzI54NMt+lmm8o3FT0Cc37o8X/5R+B7/3xzUWtq
+ 0d8roQ6iAof+NhScg/HWGBAdeU8LNnW7f+AXAAAA//8DAPq+nqbzAQAA
+ http_version:
+ recorded_at: Wed, 14 Sep 2016 15:56:51 GMT
+- request:
+ method: post
+ uri: https://api.sandbox.braintreegateway.com/merchants/7rdg92j7bm7fk5h3/customers
+ body:
+ encoding: UTF-8
+ string: |
+
+
+
+ headers:
+ Accept-Encoding:
+ - gzip
+ Accept:
+ - application/xml
+ User-Agent:
+ - Braintree Ruby Gem 2.66.0
+ X-Apiversion:
+ - '4'
+ Authorization:
+ - Basic bXdqa2t4d2NwMzJja2huZjphOTI5OGY0M2IzMGM2OTlkYjMwNzJjYzRhMDBmN2Y0OQ==
+ Content-Type:
+ - application/xml
+ response:
+ status:
+ code: 201
+ message: Created
+ headers:
+ Date:
+ - Wed, 14 Sep 2016 15:56:51 GMT
+ Content-Type:
+ - application/xml; charset=utf-8
+ Transfer-Encoding:
+ - chunked
+ X-Frame-Options:
+ - SAMEORIGIN
+ - SAMEORIGIN
+ X-Xss-Protection:
+ - 1; mode=block
+ X-Content-Type-Options:
+ - nosniff
+ X-Authentication:
+ - basic_auth
+ X-User:
+ - 3v249hqtptsg744y
+ Vary:
+ - Accept-Encoding
+ Content-Encoding:
+ - gzip
+ Etag:
+ - W/"78438d21ad2d3cac1e4b98726448afec"
+ Cache-Control:
+ - max-age=0, private, must-revalidate
+ X-Request-Id:
+ - 55d2c0e5-3748-4001-a629-5ca96af6a894
+ X-Runtime:
+ - '0.169527'
+ Strict-Transport-Security:
+ - max-age=31536000; includeSubDomains
+ body:
+ encoding: ASCII-8BIT
+ string: !binary |-
+ H4sIAENz2VcAA5SRzW6DMBCE73kKy3eXn4S/yJBbnyC99LZhl+AWG2SbNrx9
+ CY2SSrSHHmc+z+5oLQ8X3bEPsk71puTRU8gZmbpHZc4lfzk+i5wfqo2sR+d7
+ TbbaMCYVVukuz4ttHMpgFldvZnULxotZZxbPRfyWnXTWvCftVgY/6fV1o6zz
+ woAmZlRXcm9H4sGCOviL1L0ewEwrnzSobuUObW/WMxq4rLxPOjnlf9lnCTyh
+ AM/8NFDJcZZeaeJVHEapCAsR7Y5Rsk/SfRK9yuARWPLjgP/LPwLf+5ebi0ZR
+ h+5eCZUXNVh0t6FgLUy3xoBoyTlasbnb/QO/AAAA//8DAGrc9TDzAQAA
+ http_version:
+ recorded_at: Wed, 14 Sep 2016 15:56:51 GMT
+recorded_with: VCR 3.0.3