require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
require File.expand_path(File.dirname(__FILE__) + "/client_api/spec_helper")

describe Braintree::Transaction do
  describe "self.clone_transaction" do
    it "creates a new transaction from the card of the transaction to clone" do
      result = Braintree::Transaction.sale(
        :amount => "112.44",
        :customer => {
          :last_name => "Adama",
        },
        :credit_card => {
          :number => "5105105105105100",
          :expiration_date => "05/2012"
        },
        :billing => {
          :country_name => "Botswana",
          :country_code_alpha2 => "BW",
          :country_code_alpha3 => "BWA",
          :country_code_numeric => "072"
        },
        :shipping => {
          :country_name => "Bhutan",
          :country_code_alpha2 => "BT",
          :country_code_alpha3 => "BTN",
          :country_code_numeric => "064"
        },
      )
      result.success?.should == true

      clone_result = Braintree::Transaction.clone_transaction(
        result.transaction.id,
        :amount => "112.44",
        :channel => "MyShoppingCartProvider",
        :options => {
          :submit_for_settlement => false
        },
      )
      clone_result.success?.should == true

      transaction = clone_result.transaction

      transaction.id.should_not == result.transaction.id
      transaction.amount.should == BigDecimal("112.44")
      transaction.channel.should == "MyShoppingCartProvider"

      transaction.billing_details.country_name.should == "Botswana"
      transaction.billing_details.country_code_alpha2.should == "BW"
      transaction.billing_details.country_code_alpha3.should == "BWA"
      transaction.billing_details.country_code_numeric.should == "072"

      transaction.shipping_details.country_name.should == "Bhutan"
      transaction.shipping_details.country_code_alpha2.should == "BT"
      transaction.shipping_details.country_code_alpha3.should == "BTN"
      transaction.shipping_details.country_code_numeric.should == "064"

      transaction.credit_card_details.masked_number.should == "510510******5100"
      transaction.credit_card_details.expiration_date.should == "05/2012"

      transaction.customer_details.last_name.should == "Adama"
      transaction.status.should == Braintree::Transaction::Status::Authorized
    end

    it "submit for settlement option" do
      result = Braintree::Transaction.sale(
        :amount => "112.44",
        :credit_card => {
          :number => "5105105105105100",
          :expiration_date => "05/2012"
        },
      )

      result.success?.should be(true)

      clone_result = Braintree::Transaction.clone_transaction(result.transaction.id, :amount => "112.44", :options => {:submit_for_settlement => true})
      clone_result.success?.should == true

      clone_result.transaction.status.should == Braintree::Transaction::Status::SubmittedForSettlement
    end

    it "handles validation errors" do
      transaction = Braintree::Transaction.credit!(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
      )
      result = Braintree::Transaction.clone_transaction(transaction.id, :amount => "112.44")
      result.success?.should be(false)

      result.errors.for(:transaction).on(:base).first.code.should == Braintree::ErrorCodes::Transaction::CannotCloneCredit
    end
  end

  describe "self.clone_transaction!" do
    it "returns the transaction if valid" do
      transaction = Braintree::Transaction.sale!(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
      )
      clone_transaction = Braintree::Transaction.clone_transaction!(transaction.id, :amount => "112.44", :options => {:submit_for_settlement => false})
      clone_transaction.id.should_not == transaction.id
    end

    it "raises a validationsfailed if invalid" do
      transaction = Braintree::Transaction.sale!(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
      )
      expect do
        clone_transaction = Braintree::Transaction.clone_transaction!(transaction.id, :amount => "im not a number")
        clone_transaction.id.should_not == transaction.id
      end.to raise_error(Braintree::ValidationsFailed)
    end
  end

  describe "self.create" do
    describe "risk data" do
      it "returns decision, device_data_captured, id, transaction_risk_score, and decision_reasons" do
        with_fraud_protection_enterprise_merchant do
          result = Braintree::Transaction.create(
            :type => "sale",
            :amount => 1_00,
            :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::CardTypeIndicators::Prepaid,
              :expiration_date => "05/2009"
            },
          )
          expect(result.transaction.risk_data).to be_a(Braintree::RiskData)
          expect(result.transaction.risk_data.id).not_to be_nil
          expect(result.transaction.risk_data.decision).not_to be_nil
          expect(result.transaction.risk_data.decision_reasons).not_to be_nil
          expect(result.transaction.risk_data).to respond_to(:device_data_captured)
          expect(result.transaction.risk_data).to respond_to(:fraud_service_provider)
          expect(result.transaction.risk_data).to respond_to(:transaction_risk_score)
        end
      end

      it "returns decision, device_data_captured, id, liability_shift, and decision_reasons" do
        with_chargeback_protection_merchant do
          result = Braintree::Transaction.create(
            :type => "sale",
            :amount => 1_00,
            :credit_card => {
              :number => "4111111111111111",
              :expiration_date => "05/2009"
            },
          )
          expect(result.transaction.risk_data).to be_a(Braintree::RiskData)
          expect(result.transaction.risk_data.id).not_to be_nil
          expect(result.transaction.risk_data.decision).not_to be_nil
          expect(result.transaction.risk_data.decision_reasons).not_to be_nil
          expect(result.transaction.risk_data).to respond_to(:device_data_captured)
          expect(result.transaction.risk_data).to respond_to(:fraud_service_provider)
          expect(result.transaction.risk_data).to respond_to(:liability_shift)
        end
      end

      it "handles validation errors for invalid risk data attributes" do
        with_advanced_fraud_kount_integration_merchant do
          result = Braintree::Transaction.create(
            :type => "sale",
            :amount => Braintree::Test::TransactionAmounts::Authorize,
            :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::Visa,
              :expiration_date => "05/2009"
            },
            :risk_data => {
              :customer_browser => "#{"1" * 300}",
              :customer_device_id => "customer_device_id_0#{"1" * 300}",
              :customer_ip => "192.168.0.1",
              :customer_location_zip => "not-a-$#phone",
              :customer_tenure => "20#{"0" * 500}"
            },
          )
          expect(result.success?).to eq(false)
          result.errors.for(:transaction).for(:risk_data).on(:customer_device_id).map { |e| e.code }.should include Braintree::ErrorCodes::RiskData::CustomerDeviceIdIsTooLong
          result.errors.for(:transaction).for(:risk_data).on(:customer_location_zip).map { |e| e.code }.should include Braintree::ErrorCodes::RiskData::CustomerLocationZipInvalidCharacters
          result.errors.for(:transaction).for(:risk_data).on(:customer_tenure).map { |e| e.code }.should include Braintree::ErrorCodes::RiskData::CustomerTenureIsTooLong
        end
      end
    end

    describe "card type indicators" do
      it "sets the prepaid field if the card is prepaid" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => 1_00,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::CardTypeIndicators::Prepaid,
            :expiration_date => "05/2009"
          },
        )
        result.transaction.credit_card_details.prepaid.should == Braintree::CreditCard::Prepaid::Yes
        result.transaction.payment_instrument_type.should == Braintree::PaymentInstrumentType::CreditCard
      end
    end

    describe "sca_exemption" do
      context "with a valid request" do
        it "succeeds" do
          requested_exemption = "low_value"
          result = Braintree::Transaction.create(
            :type => "sale",
            :amount => Braintree::Test::TransactionAmounts::Authorize,
            :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::VisaCountryOfIssuanceIE,
              :expiration_date => "05/2009"
            },
            :sca_exemption => requested_exemption,
          )
          expect(result).to be_success
          expect(result.transaction.sca_exemption_requested).to eq(requested_exemption)
        end
      end

      context "with an invalid request" do
        it "returns an error" do
          result = Braintree::Transaction.create(
            :type => "sale",
            :amount => Braintree::Test::TransactionAmounts::Authorize,
            :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::Visa,
              :expiration_date => "05/2009"
            },
            :sca_exemption => "invalid_sca_exemption_value",
          )
          sca_exemption_invalid = Braintree::ErrorCodes::Transaction::ScaExemptionInvalid
          expect(result).not_to be_success
          expect(result.errors.for(:transaction).map(&:code)).to eq([sca_exemption_invalid])
        end
      end
    end

    describe "industry data" do
      context "for lodging" do
        it "accepts valid industry data" do
          result = Braintree::Transaction.create(
            :type => "sale",
            :amount => 1000_00,
            :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::CardTypeIndicators::Prepaid,
              :expiration_date => "05/2009"
            },
            :industry => {
              :industry_type => Braintree::Transaction::IndustryType::Lodging,
              :data => {
                :folio_number => "ABCDEFG",
                :check_in_date => "2014-06-01",
                :check_out_date => "2014-06-05",
                :room_rate => 170_00,
                :room_tax => 30_00,
                :no_show => false,
                :advanced_deposit => false,
                :fire_safe => true,
                :property_phone => "1112223345",
                :additional_charges => [
                  {
                    :kind => Braintree::Transaction::AdditionalCharge::Telephone,
                    :amount => 50_00,
                  },
                  {
                    :kind => Braintree::Transaction::AdditionalCharge::Other,
                    :amount => 150_00,
                  },
                ],
              }
            },
          )
          result.success?.should be(true)
        end

        it "returns errors if validations on industry lodging data fails" do
          result = Braintree::Transaction.create(
            :type => "sale",
            :amount => 500_00,
            :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::CardTypeIndicators::Prepaid,
              :expiration_date => "05/2009"
            },
            :industry => {
              :industry_type => Braintree::Transaction::IndustryType::Lodging,
              :data => {
                :folio_number => "foo bar",
                :check_in_date => "2014-06-30",
                :check_out_date => "2014-06-01",
                :room_rate => "asdfasdf",
                :additional_charges => [
                  {
                    :kind => "unknown",
                    :amount => 20_00,
                  },
                ],
              }
            },
          )
          result.success?.should be(false)
          invalid_folio = Braintree::ErrorCodes::Transaction::Industry::Lodging::FolioNumberIsInvalid
          check_out_date_must_follow_check_in_date = Braintree::ErrorCodes::Transaction::Industry::Lodging::CheckOutDateMustFollowCheckInDate
          room_rate_format_is_invalid = Braintree::ErrorCodes::Transaction::Industry::Lodging::RoomRateFormatIsInvalid
          invalid_additional_charge_kind = Braintree::ErrorCodes::Transaction::Industry::AdditionalCharge::KindIsInvalid
          result.errors.for(:transaction).for(:industry).map { |e| e.code }.sort.should include(*[invalid_folio, check_out_date_must_follow_check_in_date, room_rate_format_is_invalid])
          result.errors.for(:transaction).for(:industry).for(:additional_charges).for(:index_0).on(:kind).map { |e| e.code }.sort.should include(*[invalid_additional_charge_kind])
        end
      end

      context "for travel cruise" do
        it "accepts valid industry data" do
          result = Braintree::Transaction.create(
            :type => "sale",
            :amount => 1_00,
            :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::CardTypeIndicators::Prepaid,
              :expiration_date => "05/2009"
            },
            :industry => {
              :industry_type => Braintree::Transaction::IndustryType::TravelAndCruise,
              :data => {
                :travel_package => "flight",
                :departure_date => "2014-07-01",
                :lodging_check_in_date => "2014-07-07",
                :lodging_check_out_date => "2014-07-07",
                :lodging_name => "Royal Caribbean",
              }
            },
          )
          result.success?.should be(true)
        end

        it "returns errors if validations on industry data fails" do
          result = Braintree::Transaction.create(
            :type => "sale",
            :amount => 1_00,
            :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::CardTypeIndicators::Prepaid,
              :expiration_date => "05/2009"
            },
            :industry => {
              :industry_type => Braintree::Transaction::IndustryType::TravelAndCruise,
              :data => {
                :lodging_name => "Royal Caribbean"
              }
            },
          )
          result.success?.should be(false)
          result.errors.for(:transaction).for(:industry).map { |e| e.code }.sort.should == [Braintree::ErrorCodes::Transaction::Industry::TravelCruise::TravelPackageIsInvalid]
        end
      end

      context "for travel flight" do
        it "accepts valid industry data" do
          result = Braintree::Transaction.create(
            :type => "sale",
            :amount => 1_00,
            :payment_method_nonce => Braintree::Test::Nonce::PayPalOneTimePayment,
            :options => {
              :submit_for_settlement => true
            },
            :industry => {
              :industry_type => Braintree::Transaction::IndustryType::TravelAndFlight,
              :data => {
                :passenger_first_name => "John",
                :passenger_last_name => "Doe",
                :passenger_middle_initial => "M",
                :passenger_title => "Mr.",
                :issued_date => Date.new(2018, 1, 1),
                :travel_agency_name => "Expedia",
                :travel_agency_code => "12345678",
                :ticket_number => "ticket-number",
                :issuing_carrier_code => "AA",
                :customer_code => "customer-code",
                :fare_amount => 70_00,
                :fee_amount => 10_00,
                :tax_amount => 20_00,
                :restricted_ticket => false,
                :legs => [
                  {
                    :conjunction_ticket => "CJ0001",
                    :exchange_ticket => "ET0001",
                    :coupon_number => "1",
                    :service_class => "Y",
                    :carrier_code => "AA",
                    :fare_basis_code => "W",
                    :flight_number => "AA100",
                    :departure_date => Date.new(2018, 1, 2),
                    :departure_airport_code => "MDW",
                    :departure_time => "08:00",
                    :arrival_airport_code => "ATX",
                    :arrival_time => "10:00",
                    :stopover_permitted => false,
                    :fare_amount => 35_00,
                    :fee_amount => 5_00,
                    :tax_amount => 10_00,
                    :endorsement_or_restrictions => "NOT REFUNDABLE"
                  },
                  {
                    :conjunction_ticket => "CJ0002",
                    :exchange_ticket => "ET0002",
                    :coupon_number => "1",
                    :service_class => "Y",
                    :carrier_code => "AA",
                    :fare_basis_code => "W",
                    :flight_number => "AA200",
                    :departure_date => Date.new(2018, 1, 3),
                    :departure_airport_code => "ATX",
                    :departure_time => "12:00",
                    :arrival_airport_code => "MDW",
                    :arrival_time => "14:00",
                    :stopover_permitted => false,
                    :fare_amount => 35_00,
                    :fee_amount => 5_00,
                    :tax_amount => 10_00,
                    :endorsement_or_restrictions => "NOT REFUNDABLE"
                  }
                ]
              }
            },
          )
          result.success?.should be(true)
        end

        it "returns errors if validations on industry data fails" do
          result = Braintree::Transaction.create(
            :type => "sale",
            :amount => 1_00,
            :payment_method_nonce => Braintree::Test::Nonce::PayPalOneTimePayment,
            :options => {
              :submit_for_settlement => true
            },
            :industry => {
              :industry_type => Braintree::Transaction::IndustryType::TravelAndFlight,
              :data => {
                :fare_amount => -1_23,
                :restricted_ticket => false,
                :legs => [
                  {
                    :fare_amount => -1_23
                  }
                ]
              }
            },
          )
          result.success?.should be(false)
          result.errors.for(:transaction).for(:industry).map { |e| e.code }.sort.should == [Braintree::ErrorCodes::Transaction::Industry::TravelFlight::FareAmountCannotBeNegative]
          result.errors.for(:transaction).for(:industry).for(:legs).for(:index_0).map { |e| e.code }.sort.should == [Braintree::ErrorCodes::Transaction::Industry::Leg::TravelFlight::FareAmountCannotBeNegative]
        end
      end
    end

    context "elo" do
      it "returns a successful result if successful" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :merchant_account_id => SpecHelper::AdyenMerchantAccountId,
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Elo,
            :cvv => "737",
            :expiration_date => "10/2020"
          },
        )
        result.success?.should == true
        result.transaction.id.should =~ /^\w{6,}$/
        result.transaction.type.should == "sale"
        result.transaction.amount.should == BigDecimal(Braintree::Test::TransactionAmounts::Authorize)
        result.transaction.processor_authorization_code.should_not be_nil
        result.transaction.voice_referral_number.should be_nil
        result.transaction.credit_card_details.bin.should == Braintree::Test::CreditCardNumbers::Elo[0, 6]
        result.transaction.credit_card_details.last_4.should == Braintree::Test::CreditCardNumbers::Elo[-4..-1]
        result.transaction.credit_card_details.expiration_date.should == "10/2020"
        result.transaction.credit_card_details.customer_location.should == "US"
      end
    end

    it "returns a successful result if successful" do
      result = Braintree::Transaction.create(
        :type => "sale",
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
      )
      result.success?.should == true
      result.transaction.id.should =~ /^\w{6,}$/
      result.transaction.type.should == "sale"
      result.transaction.amount.should == BigDecimal(Braintree::Test::TransactionAmounts::Authorize)
      result.transaction.processor_authorization_code.should_not be_nil
      result.transaction.processor_response_code.should == "1000"
      result.transaction.processor_response_text.should == "Approved"
      result.transaction.processor_response_type.should == Braintree::ProcessorResponseTypes::Approved
      result.transaction.voice_referral_number.should be_nil
      result.transaction.credit_card_details.bin.should == Braintree::Test::CreditCardNumbers::Visa[0, 6]
      result.transaction.credit_card_details.last_4.should == Braintree::Test::CreditCardNumbers::Visa[-4..-1]
      result.transaction.credit_card_details.expiration_date.should == "05/2009"
      result.transaction.credit_card_details.customer_location.should == "US"
      result.transaction.retrieval_reference_number.should_not be_nil
      result.transaction.acquirer_reference_number.should be_nil
    end

    it "returns a successful network response code if successful" do
      result = Braintree::Transaction.create(
        :type => "sale",
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
      )
      expect(result.success?).to eq(true)
      expect(result.transaction.type).to eq("sale")
      expect(result.transaction.amount).to eq(BigDecimal(Braintree::Test::TransactionAmounts::Authorize))
      expect(result.transaction.processor_authorization_code).not_to be_nil
      expect(result.transaction.processor_response_code).to eq("1000")
      expect(result.transaction.processor_response_text).to eq("Approved")
      expect(result.transaction.processor_response_type).to eq(Braintree::ProcessorResponseTypes::Approved)
      expect(result.transaction.network_response_code).to eq("XX")
      expect(result.transaction.network_response_text).to eq("sample network response text")
      expect(result.transaction.voice_referral_number).to be_nil
      expect(result.transaction.credit_card_details.bin).to eq(Braintree::Test::CreditCardNumbers::Visa[0, 6])
      expect(result.transaction.credit_card_details.last_4).to eq(Braintree::Test::CreditCardNumbers::Visa[-4..-1])
      expect(result.transaction.credit_card_details.expiration_date).to eq("05/2009")
      expect(result.transaction.credit_card_details.customer_location).to eq("US")
    end

    it "returns a successful result using an access token" do
      oauth_gateway = Braintree::Gateway.new(
        :client_id => "client_id$#{Braintree::Configuration.environment}$integration_client_id",
        :client_secret => "client_secret$#{Braintree::Configuration.environment}$integration_client_secret",
        :logger => Logger.new("/dev/null"),
      )
      access_token = Braintree::OAuthTestHelper.create_token(oauth_gateway, {
        :merchant_public_id => "integration_merchant_id",
        :scope => "read_write"
      }).credentials.access_token

      gateway = Braintree::Gateway.new(
        :access_token => access_token,
        :logger => Logger.new("/dev/null"),
      )

      result = gateway.transaction.create(
        :type => "sale",
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
      )

      result.success?.should == true
      result.transaction.id.should =~ /^\w{6,}$/
      result.transaction.type.should == "sale"
      result.transaction.amount.should == BigDecimal(Braintree::Test::TransactionAmounts::Authorize)
      result.transaction.processor_authorization_code.should_not be_nil
      result.transaction.voice_referral_number.should be_nil
      result.transaction.credit_card_details.bin.should == Braintree::Test::CreditCardNumbers::Visa[0, 6]
      result.transaction.credit_card_details.last_4.should == Braintree::Test::CreditCardNumbers::Visa[-4..-1]
      result.transaction.credit_card_details.expiration_date.should == "05/2009"
      result.transaction.credit_card_details.customer_location.should == "US"
    end

    it "accepts additional security parameters: device_data" do
      result = Braintree::Transaction.create(
        :type => "sale",
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
        :device_data => "device_data",
      )

      result.success?.should == true
    end

    it "accepts additional security parameters: risk data" do
      result = Braintree::Transaction.create(
        :type => "sale",
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
        :risk_data => {
          :customer_browser => "IE6",
          :customer_device_id => "customer_device_id_012",
          :customer_ip => "192.168.0.1",
          :customer_location_zip => "91244",
          :customer_tenure => "20",
        },
      )

      result.success?.should == true
    end

    it "accepts billing_address_id in place of billing_address" do
      result = Braintree::Customer.create()
      address_result = Braintree::Address.create(
        :customer_id => result.customer.id,
        :country_code_alpha2 => "US",
      )

      result = Braintree::Transaction.create(
        :type => "sale",
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :customer_id => result.customer.id,
        :billing_address_id => address_result.address.id,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
      )

      result.success?.should == true
    end

    it "returns processor response code and text as well as the additional processor response if soft declined" do
      result = Braintree::Transaction.create(
        :type => "sale",
        :amount => Braintree::Test::TransactionAmounts::Decline,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
      )
      result.success?.should == false
      result.transaction.id.should =~ /^\w{6,}$/
      result.transaction.type.should == "sale"
      result.transaction.status.should == Braintree::Transaction::Status::ProcessorDeclined
      result.transaction.processor_response_code.should == "2000"
      result.transaction.processor_response_text.should == "Do Not Honor"
      result.transaction.processor_response_type.should == Braintree::ProcessorResponseTypes::SoftDeclined
      result.transaction.additional_processor_response.should == "2000 : Do Not Honor"
    end

    it "returns processor response code and text as well as the additional processor response if hard declined" do
      result = Braintree::Transaction.create(
        :type => "sale",
        :amount => Braintree::Test::TransactionAmounts::HardDecline,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
      )
      result.success?.should == false
      result.transaction.id.should =~ /^\w{6,}$/
      result.transaction.type.should == "sale"
      result.transaction.status.should == Braintree::Transaction::Status::ProcessorDeclined
      result.transaction.processor_response_code.should == "2015"
      result.transaction.processor_response_text.should == "Transaction Not Allowed"
      result.transaction.processor_response_type.should == Braintree::ProcessorResponseTypes::HardDeclined
      result.transaction.additional_processor_response.should == "2015 : Transaction Not Allowed"
    end

    it "accepts all four country codes" do
      result = Braintree::Transaction.sale(
        :amount => "100",
        :customer => {
          :last_name => "Adama",
        },
        :credit_card => {
          :number => "5105105105105100",
          :expiration_date => "05/2012"
        },
        :billing => {
          :country_name => "Botswana",
          :country_code_alpha2 => "BW",
          :country_code_alpha3 => "BWA",
          :country_code_numeric => "072"
        },
        :shipping => {
          :country_name => "Bhutan",
          :country_code_alpha2 => "BT",
          :country_code_alpha3 => "BTN",
          :country_code_numeric => "064"
        },
        :options => {
          :add_billing_address_to_payment_method => true,
          :store_in_vault => true
        },
      )
      result.success?.should == true
      transaction = result.transaction
      transaction.billing_details.country_name.should == "Botswana"
      transaction.billing_details.country_code_alpha2.should == "BW"
      transaction.billing_details.country_code_alpha3.should == "BWA"
      transaction.billing_details.country_code_numeric.should == "072"

      transaction.shipping_details.country_name.should == "Bhutan"
      transaction.shipping_details.country_code_alpha2.should == "BT"
      transaction.shipping_details.country_code_alpha3.should == "BTN"
      transaction.shipping_details.country_code_numeric.should == "064"

      transaction.vault_credit_card.billing_address.country_name.should == "Botswana"
      transaction.vault_credit_card.billing_address.country_code_alpha2.should == "BW"
      transaction.vault_credit_card.billing_address.country_code_alpha3.should == "BWA"
      transaction.vault_credit_card.billing_address.country_code_numeric.should == "072"
    end

    it "returns an error if provided inconsistent country information" do
      result = Braintree::Transaction.sale(
        :amount => "100",
        :credit_card => {
          :number => "5105105105105100",
          :expiration_date => "05/2012"
        },
        :billing => {
          :country_name => "Botswana",
          :country_code_alpha2 => "US",
        },
      )

      result.success?.should == false
      result.errors.for(:transaction).for(:billing).on(:base).map { |e| e.code }.should include(Braintree::ErrorCodes::Address::InconsistentCountry)
    end

    it "returns an error if given an incorrect alpha2 code" do
      result = Braintree::Transaction.sale(
        :amount => "100",
        :credit_card => {
          :number => "5105105105105100",
          :expiration_date => "05/2012"
        },
        :billing => {
          :country_code_alpha2 => "ZZ"
        },
      )

      result.success?.should == false
      codes = result.errors.for(:transaction).for(:billing).on(:country_code_alpha2).map { |e| e.code }
      codes.should include(Braintree::ErrorCodes::Address::CountryCodeAlpha2IsNotAccepted)
    end

    it "returns an error if given an incorrect alpha3 code" do
      result = Braintree::Transaction.sale(
        :amount => "100",
        :credit_card => {
          :number => "5105105105105100",
          :expiration_date => "05/2012"
        },
        :billing => {
          :country_code_alpha3 => "ZZZ"
        },
      )

      result.success?.should == false
      codes = result.errors.for(:transaction).for(:billing).on(:country_code_alpha3).map { |e| e.code }
      codes.should include(Braintree::ErrorCodes::Address::CountryCodeAlpha3IsNotAccepted)
    end

    it "returns an error if given an incorrect numeric code" do
      result = Braintree::Transaction.sale(
        :amount => "100",
        :credit_card => {
          :number => "5105105105105100",
          :expiration_date => "05/2012"
        },
        :billing => {
          :country_code_numeric => "FOO"
        },
      )

      result.success?.should == false
      codes = result.errors.for(:transaction).for(:billing).on(:country_code_numeric).map { |e| e.code }
      codes.should include(Braintree::ErrorCodes::Address::CountryCodeNumericIsNotAccepted)
    end

    it "returns an error if provided product sku is invalid" do
      result = Braintree::Transaction.sale(
        :amount => "100",
        :credit_card => {
          :number => "5105105105105100",
          :expiration_date => "05/2012"
        },
        :product_sku => "product$ku!",
      )

      result.success?.should == false
      result.errors.for(:transaction).on(:product_sku).map { |e| e.code }.should include(Braintree::ErrorCodes::Transaction::ProductSkuIsInvalid)
    end

    it "returns an error if provided shipping phone number is invalid" do
      result = Braintree::Transaction.sale(
        :amount => "100",
        :credit_card => {
          :number => "5105105105105100",
          :expiration_date => "05/2012"
        },
        :shipping => {
          :phone_number => "123-234-3456=098765"
        },
      )

      result.success?.should == false
      result.errors.for(:transaction).for(:shipping).on(:phone_number).map { |e| e.code }.should include(Braintree::ErrorCodes::Transaction::ShippingPhoneNumberIsInvalid)
    end

    it "returns an error if provided shipping method is invalid" do
      result = Braintree::Transaction.sale(
        :amount => "100",
        :credit_card => {
          :number => "5105105105105100",
          :expiration_date => "05/2012"
        },
        :shipping => {
          :shipping_method => "urgent"
        },
      )

      result.success?.should == false
      result.errors.for(:transaction).for(:shipping).on(:shipping_method).map { |e| e.code }.should include(Braintree::ErrorCodes::Transaction::ShippingMethodIsInvalid)
    end

    it "returns an error if provided billing phone number is invalid" do
      result = Braintree::Transaction.sale(
        :amount => "100",
        :credit_card => {
          :number => "5105105105105100",
          :expiration_date => "05/2012"
        },
        :billing => {
          :phone_number => "123-234-3456=098765"
        },
      )

      result.success?.should == false
      result.errors.for(:transaction).for(:billing).on(:phone_number).map { |e| e.code }.should include(Braintree::ErrorCodes::Transaction::BillingPhoneNumberIsInvalid)
    end

    context "gateway rejection reason" do
      it "exposes the cvv gateway rejection reason" do
        old_merchant = Braintree::Configuration.merchant_id
        old_public_key = Braintree::Configuration.public_key
        old_private_key = Braintree::Configuration.private_key

        begin
          Braintree::Configuration.merchant_id = "processing_rules_merchant_id"
          Braintree::Configuration.public_key = "processing_rules_public_key"
          Braintree::Configuration.private_key = "processing_rules_private_key"

          result = Braintree::Transaction.sale(
            :amount => Braintree::Test::TransactionAmounts::Authorize,
            :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::Visa,
              :expiration_date => "05/2009",
              :cvv => "200"
            },
          )
          result.success?.should == false
          result.transaction.gateway_rejection_reason.should == Braintree::Transaction::GatewayRejectionReason::CVV
        ensure
          Braintree::Configuration.merchant_id = old_merchant
          Braintree::Configuration.public_key = old_public_key
          Braintree::Configuration.private_key = old_private_key
        end
      end

      it "exposes the application incomplete gateway rejection reason" do
        gateway = Braintree::Gateway.new(
          :client_id => "client_id$#{Braintree::Configuration.environment}$integration_client_id",
          :client_secret => "client_secret$#{Braintree::Configuration.environment}$integration_client_secret",
          :logger => Logger.new("/dev/null"),
        )
        result = gateway.merchant.create(
          :email => "name@email.com",
          :country_code_alpha3 => "USA",
          :payment_methods => ["credit_card", "paypal"],
        )

        gateway = Braintree::Gateway.new(
          :access_token => result.credentials.access_token,
          :logger => Logger.new("/dev/null"),
        )

        result = gateway.transaction.create(
          :type => "sale",
          :amount => "4000.00",
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "05/2020"
          },
        )
        result.success?.should == false
        result.transaction.gateway_rejection_reason.should == Braintree::Transaction::GatewayRejectionReason::ApplicationIncomplete
      end

      it "exposes the avs gateway rejection reason" do
        old_merchant = Braintree::Configuration.merchant_id
        old_public_key = Braintree::Configuration.public_key
        old_private_key = Braintree::Configuration.private_key

        begin
          Braintree::Configuration.merchant_id = "processing_rules_merchant_id"
          Braintree::Configuration.public_key = "processing_rules_public_key"
          Braintree::Configuration.private_key = "processing_rules_private_key"

          result = Braintree::Transaction.sale(
            :amount => Braintree::Test::TransactionAmounts::Authorize,
            :billing => {
              :street_address => "200 Fake Street"
            },
            :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::Visa,
              :expiration_date => "05/2009"
            },
          )
          result.success?.should == false
          result.transaction.gateway_rejection_reason.should == Braintree::Transaction::GatewayRejectionReason::AVS
        ensure
          Braintree::Configuration.merchant_id = old_merchant
          Braintree::Configuration.public_key = old_public_key
          Braintree::Configuration.private_key = old_private_key
        end
      end

      it "exposes the avs_and_cvv gateway rejection reason" do
        old_merchant = Braintree::Configuration.merchant_id
        old_public_key = Braintree::Configuration.public_key
        old_private_key = Braintree::Configuration.private_key

        begin
          Braintree::Configuration.merchant_id = "processing_rules_merchant_id"
          Braintree::Configuration.public_key = "processing_rules_public_key"
          Braintree::Configuration.private_key = "processing_rules_private_key"

          result = Braintree::Transaction.sale(
            :amount => Braintree::Test::TransactionAmounts::Authorize,
            :billing => {
              :postal_code => "20000"
            },
            :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::Visa,
              :expiration_date => "05/2009",
              :cvv => "200"
            },
          )
          result.success?.should == false
          result.transaction.gateway_rejection_reason.should == Braintree::Transaction::GatewayRejectionReason::AVSAndCVV
        ensure
          Braintree::Configuration.merchant_id = old_merchant
          Braintree::Configuration.public_key = old_public_key
          Braintree::Configuration.private_key = old_private_key
        end
      end

      it "exposes the fraud gateway rejection reason" do
        with_advanced_fraud_kount_integration_merchant do
          result = Braintree::Transaction.sale(
            :amount => Braintree::Test::TransactionAmounts::Authorize,
            :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::Fraud,
              :expiration_date => "05/2017",
              :cvv => "333"
            },
          )
          result.success?.should == false
          result.transaction.gateway_rejection_reason.should == Braintree::Transaction::GatewayRejectionReason::Fraud
        end
      end

      it "exposes the risk_threshold gateway rejection reason (via test cc num)" do
        with_advanced_fraud_kount_integration_merchant do
          result = Braintree::Transaction.sale(
            :amount => Braintree::Test::TransactionAmounts::Authorize,
            :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::RiskThreshold,
              :expiration_date => "05/2017",
              :cvv => "333"
            },
          )
          result.success?.should == false
          result.transaction.gateway_rejection_reason.should == Braintree::Transaction::GatewayRejectionReason::RiskThreshold
        end
      end

      it "exposes the risk_threshold gateway rejection reason (via test test nonce)" do
        with_advanced_fraud_kount_integration_merchant do
          result = Braintree::Transaction.sale(
            :amount => Braintree::Test::TransactionAmounts::Authorize,
            :payment_method_nonce => Braintree::Test::Nonce::GatewayRejectedRiskThresholds,
          )
          result.success?.should == false
          result.transaction.gateway_rejection_reason.should == Braintree::Transaction::GatewayRejectionReason::RiskThreshold
        end
      end

      it "exposes the token issuance gateway rejection reason" do
        result = Braintree::Transaction.sale(
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :merchant_account_id => SpecHelper::FakeVenmoAccountMerchantAccountId,
          :payment_method_nonce => Braintree::Test::Nonce::VenmoAccountTokenIssuanceError,
        )
        result.success?.should == false
        result.transaction.gateway_rejection_reason.should == Braintree::Transaction::GatewayRejectionReason::TokenIssuance
      end

      xit "exposes the excessive_retry gateway rejection reason" do
        with_duplicate_checking_merchant do
          result = nil
          counter = 0
          excessive_retry = false
          until excessive_retry || counter == 20
            result = Braintree::Transaction.sale(
              :amount => Braintree::Test::TransactionAmounts::Decline,
              :credit_card => {
                :number => Braintree::Test::CreditCardNumbers::Visa,
                :expiration_month => "05",
                :expiration_year => "2011"
              },
              )
            excessive_retry = result.transaction.status == "gateway_rejected"
            counter +=1
          end
          expect(result.transaction.gateway_rejection_reason). to eq(Braintree::Transaction::GatewayRejectionReason::ExcessiveRetry)
        end
      end
    end

    it "accepts credit card expiration month and expiration year" do
      result = Braintree::Transaction.create(
        :type => "sale",
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_month => "05",
          :expiration_year => "2011"
        },
      )
      result.success?.should == true
      result.transaction.credit_card_details.expiration_month.should == "05"
      result.transaction.credit_card_details.expiration_year.should == "2011"
      result.transaction.credit_card_details.expiration_date.should == "05/2011"
    end

    it "accepts exchange_rate_quote_id" do
      result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::Visa,
              :expiration_date => "05/2009"
          },
          :exchange_rate_quote_id => "dummyExchangeRateQuoteId-Brainree-Ruby",
      )
      result.success?.should == true
      result.transaction.credit_card_details.expiration_date.should == "05/2009"
    end

    it "returns an error if provided invalid exchange_rate_quote_id" do
      result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::Visa,
              :expiration_date => "05/2009"
          },
          :exchange_rate_quote_id => "a" * 4010,
      )
      result.success?.should == false
      result.errors.for(:transaction).on(:exchange_rate_quote_id)[0].code.should == Braintree::ErrorCodes::Transaction::ExchangeRateQuoteIdTooLong
    end

    it "returns some error if customer_id is invalid" do
      result = Braintree::Transaction.create(
        :type => "sale",
        :amount => Braintree::Test::TransactionAmounts::Decline,
        :customer_id => 123456789,
      )
      result.success?.should == false
      result.errors.for(:transaction).on(:customer_id)[0].code.should == "91510"
      result.message.should == "Customer ID is invalid."
    end

    it "can create custom fields" do
      result = Braintree::Transaction.create(
        :type => "sale",
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
        :custom_fields => {
          :store_me => "custom value"
        },
      )
      result.success?.should == true
      result.transaction.custom_fields.should == {:store_me => "custom value"}
    end

    it "returns nil if a custom field is not defined" do
      create_result = Braintree::Transaction.sale(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "12/2012"
        },
        :custom_fields => {
          :store_me => ""
        },
      )

      result = Braintree::Transaction.find(create_result.transaction.id)

      result.custom_fields.should == {}
    end

    it "returns an error if custom_field is not registered" do
      result = Braintree::Transaction.create(
        :type => "sale",
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
        :custom_fields => {
          :invalid_key => "custom value"
        },
      )
      result.success?.should == false
      result.errors.for(:transaction).on(:custom_fields)[0].message.should == "Custom field is invalid: invalid_key."
    end

    it "returns the given params if validations fail" do
      params = {
        :transaction => {
          :type => "sale",
          :amount => nil,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "05/2009"
          }
        }
      }
      result = Braintree::Transaction.create(params[:transaction])
      result.success?.should == false
      result.params.should == {:transaction => {:type => "sale", :amount => nil, :credit_card => {:expiration_date => "05/2009"}}}
    end

    it "returns errors if validations fail (tests many errors at once for spec speed)" do
      params = {
        :transaction => {
          :type => "pants",
          :amount => nil,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "05/2009"
          },
          :customer_id => "invalid",
          :order_id => "too long" * 250,
          :channel => "too long" * 250,
          :payment_method_token => "too long and doesn't belong to customer" * 250
        }
      }
      result = Braintree::Transaction.create(params[:transaction])
      result.success?.should == false
      result.errors.for(:transaction).on(:base).map { |error| error.code }.should include(Braintree::ErrorCodes::Transaction::PaymentMethodDoesNotBelongToCustomer)
      result.errors.for(:transaction).on(:customer_id)[0].code.should == Braintree::ErrorCodes::Transaction::CustomerIdIsInvalid
      result.errors.for(:transaction).on(:payment_method_token)[0].code.should == Braintree::ErrorCodes::Transaction::PaymentMethodTokenIsInvalid
      result.errors.for(:transaction).on(:type)[0].code.should == Braintree::ErrorCodes::Transaction::TypeIsInvalid
    end

    it "returns an error if amount is negative" do
      params = {
        :transaction => {
          :type => "credit",
          :amount => "-1"
        }
      }
      result = Braintree::Transaction.create(params[:transaction])
      result.success?.should == false
      result.errors.for(:transaction).on(:amount)[0].code.should == Braintree::ErrorCodes::Transaction::AmountCannotBeNegative
    end

    it "returns an error if amount is not supported by processor" do
      result = Braintree::Transaction.create(
        :type => "sale",
        :merchant_account_id => SpecHelper::HiperBRLMerchantAccountId,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Hiper,
          :expiration_date => "05/2009"
        },
        :amount => "0.20",
        :options => {
          :credit_card => {
            :account_type => "credit",
          }
        },
      )
      result.success?.should == false
      result.errors.for(:transaction).on(:amount)[0].code.should == Braintree::ErrorCodes::Transaction::AmountNotSupportedByProcessor
    end

    it "returns an error if amount is invalid format" do
      params = {
        :transaction => {
          :type => "sale",
          :amount => "shorts"
        }
      }
      result = Braintree::Transaction.create(params[:transaction])
      result.success?.should == false
      result.errors.for(:transaction).on(:amount)[0].code.should == Braintree::ErrorCodes::Transaction::AmountIsInvalid
    end

    it "returns an error if type is not given" do
      params = {
        :transaction => {
          :type => nil
        }
      }
      result = Braintree::Transaction.create(params[:transaction])
      result.success?.should == false
      result.errors.for(:transaction).on(:type)[0].code.should == Braintree::ErrorCodes::Transaction::TypeIsRequired
    end

    it "returns an error if no credit card is given" do
      params = {
        :transaction => {
        }
      }
      result = Braintree::Transaction.create(params[:transaction])
      result.success?.should == false
      result.errors.for(:transaction).on(:base)[0].code.should == Braintree::ErrorCodes::Transaction::CreditCardIsRequired
    end

    it "returns an error if the given payment method token doesn't belong to the customer" do
      customer = Braintree::Customer.create!(
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2010"
        },
      )
      result = Braintree::Transaction.create(
        :type => "sale",
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :customer_id => customer.id,
        :payment_method_token => customer.credit_cards[0].token + "x",
      )
      result.success?.should == false
      result.errors.for(:transaction).on(:base)[0].code.should == Braintree::ErrorCodes::Transaction::PaymentMethodDoesNotBelongToCustomer
    end

    context "new credit card for existing customer" do
      it "allows a new credit card to be used for an existing customer" do
        customer = Braintree::Customer.create!(
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "05/2010"
          },
        )
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :customer_id => customer.id,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "12/12"
          },
        )
        result.success?.should == true
        result.transaction.credit_card_details.masked_number.should == "401288******1881"
        result.transaction.vault_credit_card.should be_nil
      end

      it "allows a new credit card to be used and stored in the vault" do
        customer = Braintree::Customer.create!(
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "05/2010"
          },
        )
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :customer_id => customer.id,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "12/12",
          },
          :options => {:store_in_vault => true},
        )
        result.success?.should == true
        result.transaction.credit_card_details.masked_number.should == "401288******1881"
        result.transaction.vault_credit_card.masked_number.should == "401288******1881"
        result.transaction.credit_card_details.unique_number_identifier.should_not be_nil
      end
    end

    it "snapshots plan, add_ons and discounts from subscription" do
      customer = Braintree::Customer.create!(
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2010"
        },
      )

      result = Braintree::Subscription.create(
        :payment_method_token => customer.credit_cards.first.token,
        :plan_id => SpecHelper::TriallessPlan[:id],
        :add_ons => {
          :add => [
            {
              :amount => BigDecimal("11.00"),
              :inherited_from_id => SpecHelper::AddOnIncrease10,
              :quantity => 2,
              :number_of_billing_cycles => 5
            },
            {
              :amount => BigDecimal("21.00"),
              :inherited_from_id => SpecHelper::AddOnIncrease20,
              :quantity => 3,
              :number_of_billing_cycles => 6
            }
          ]
        },
        :discounts => {
          :add => [
            {
              :amount => BigDecimal("7.50"),
              :inherited_from_id => SpecHelper::Discount7,
              :quantity => 2,
              :never_expires => true
            }
          ]
        },
      )

      result.success?.should be(true)
      transaction = result.subscription.transactions.first

      transaction.plan_id.should == SpecHelper::TriallessPlan[:id]

      transaction.add_ons.size.should == 2
      add_ons = transaction.add_ons.sort_by { |add_on| add_on.id }

      add_ons.first.id.should == "increase_10"
      add_ons.first.amount.should == BigDecimal("11.00")
      add_ons.first.quantity.should == 2
      add_ons.first.number_of_billing_cycles.should == 5
      add_ons.first.never_expires?.should be(false)

      add_ons.last.id.should == "increase_20"
      add_ons.last.amount.should == BigDecimal("21.00")
      add_ons.last.quantity.should == 3
      add_ons.last.number_of_billing_cycles.should == 6
      add_ons.last.never_expires?.should be(false)

      transaction.discounts.size.should == 1

      transaction.discounts.first.id.should == "discount_7"
      transaction.discounts.first.amount.should == BigDecimal("7.50")
      transaction.discounts.first.quantity.should == 2
      transaction.discounts.first.number_of_billing_cycles.should be_nil
      transaction.discounts.first.never_expires?.should be(true)
    end

    context "descriptors" do
      it "accepts name and phone" do
        result = Braintree::Transaction.sale(
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "05/2009"
          },
          :descriptor => {
            :name => "123*123456789012345678",
            :phone => "3334445555",
            :url => "ebay.com"
          },
        )
        result.success?.should == true
        result.transaction.descriptor.name.should == "123*123456789012345678"
        result.transaction.descriptor.phone.should == "3334445555"
        result.transaction.descriptor.url.should == "ebay.com"
      end

      it "has validation errors if format is invalid" do
        result = Braintree::Transaction.sale(
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "05/2009"
          },
          :descriptor => {
            :name => "badcompanyname12*badproduct12",
            :phone => "%bad4445555",
            :url => "12345678901234"
          },
        )
        result.success?.should == false
        result.errors.for(:transaction).for(:descriptor).on(:name)[0].code.should == Braintree::ErrorCodes::Descriptor::NameFormatIsInvalid
        result.errors.for(:transaction).for(:descriptor).on(:phone)[0].code.should == Braintree::ErrorCodes::Descriptor::PhoneFormatIsInvalid
        result.errors.for(:transaction).for(:descriptor).on(:url)[0].code.should == Braintree::ErrorCodes::Descriptor::UrlFormatIsInvalid
      end
    end

    context "level 2 fields" do
      it "accepts tax_amount, tax_exempt, and purchase_order_number" do
        result = Braintree::Transaction.sale(
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "05/2009"
          },
          :tax_amount => "0.05",
          :tax_exempt => false,
          :purchase_order_number => "12345678901234567",
        )
        result.success?.should == true
        result.transaction.tax_amount.should == BigDecimal("0.05")
        result.transaction.tax_exempt.should == false
        result.transaction.purchase_order_number.should == "12345678901234567"
      end

      it "accepts tax_amount as a BigDecimal" do
        result = Braintree::Transaction.sale(
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "05/2009"
          },
          :tax_amount => BigDecimal("1.99"),
          :tax_exempt => true,
        )
        result.success?.should == true
        result.transaction.tax_amount.should == BigDecimal("1.99")
        result.transaction.tax_exempt.should == true
        result.transaction.purchase_order_number.should be_nil
      end

      context "validations" do
        it "tax_amount" do
          result = Braintree::Transaction.sale(
            :amount => Braintree::Test::TransactionAmounts::Authorize,
            :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::Visa,
              :expiration_date => "05/2009"
            },
            :tax_amount => "abcd",
          )
          result.success?.should == false
          result.errors.for(:transaction).on(:tax_amount)[0].code.should == Braintree::ErrorCodes::Transaction::TaxAmountFormatIsInvalid
        end

        it "purchase_order_number length" do
          result = Braintree::Transaction.sale(
            :amount => Braintree::Test::TransactionAmounts::Authorize,
            :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::Visa,
              :expiration_date => "05/2009"
            },
            :purchase_order_number => "a" * 18,
          )
          result.success?.should == false
          result.errors.for(:transaction).on(:purchase_order_number)[0].code.should == Braintree::ErrorCodes::Transaction::PurchaseOrderNumberIsTooLong
        end

        it "purchase_order_number format" do
          result = Braintree::Transaction.sale(
            :amount => Braintree::Test::TransactionAmounts::Authorize,
            :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::Visa,
              :expiration_date => "05/2009"
            },
            :purchase_order_number => "\303\237\303\245\342\210\202",
          )
          result.success?.should == false
          result.errors.for(:transaction).on(:purchase_order_number)[0].code.should == Braintree::ErrorCodes::Transaction::PurchaseOrderNumberIsInvalid
        end
      end
    end

    context "transaction_source" do
      it "marks a transactions as recurring_first" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "12/12",
          },
          :transaction_source => "recurring_first",
        )
        result.success?.should == true
        result.transaction.recurring.should == true
      end

      it "marks a transactions as recurring" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "12/12",
          },
          :transaction_source => "recurring",
        )
        result.success?.should == true
        result.transaction.recurring.should == true
      end

      it "successfully creates a transaction with installment_first" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "12/12",
          },
          :transaction_source => "installment_first",
        )
        result.success?.should == true
      end

      it "successfully creates a transaction with installment" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "12/12",
          },
          :transaction_source => "installment",
        )
        result.success?.should == true
      end

      it "marks a transactions as merchant" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "12/12",
          },
          :transaction_source => "merchant",
        )
        result.success?.should == true
        result.transaction.recurring.should == false
      end

      it "marks a transactions as moto" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "12/12",
          },
          :transaction_source => "moto",
        )
        result.success?.should == true
        result.transaction.recurring.should == false
      end

      it "handles validation when transaction source invalid" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "12/12",
          },
          :transaction_source => "invalid_value",
        )
        result.success?.should == false
        result.errors.for(:transaction).on(:transaction_source)[0].code.should == Braintree::ErrorCodes::Transaction::TransactionSourceIsInvalid
      end
    end

    context "store_in_vault_on_success" do
      context "passed as true" do
        it "stores vault records when transaction succeeds" do
          result = Braintree::Transaction.create(
            :type => "sale",
            :amount => Braintree::Test::TransactionAmounts::Authorize,
            :customer => {
              :last_name => "Doe"
            },
            :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::Visa,
              :expiration_date => "12/12",
            },
            :options => {:store_in_vault_on_success => true},
          )
          result.success?.should == true
          result.transaction.vault_customer.last_name.should == "Doe"
          result.transaction.vault_credit_card.masked_number.should == "401288******1881"
          result.transaction.credit_card_details.unique_number_identifier.should_not be_nil
        end

        it "does not store vault records when true and transaction fails" do
          result = Braintree::Transaction.create(
            :type => "sale",
            :amount => Braintree::Test::TransactionAmounts::Decline,
            :customer => {
              :last_name => "Doe"
            },
            :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::Visa,
              :expiration_date => "12/12",
            },
            :options => {:store_in_vault_on_success => true},
          )
          result.success?.should == false
          result.transaction.vault_customer.should be_nil
          result.transaction.vault_credit_card.should be_nil
        end
      end

      context "passed as false" do
        it "does not store vault records when transaction succeeds" do
          result = Braintree::Transaction.create(
            :type => "sale",
            :amount => Braintree::Test::TransactionAmounts::Authorize,
            :customer => {
              :last_name => "Doe"
            },
            :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::Visa,
              :expiration_date => "12/12",
            },
            :options => {:store_in_vault_on_success => false},
          )
          result.success?.should == true
          result.transaction.vault_customer.should be_nil
          result.transaction.vault_credit_card.should be_nil
        end

        it "does not store vault records when transaction fails" do
          result = Braintree::Transaction.create(
            :type => "sale",
            :amount => Braintree::Test::TransactionAmounts::Decline,
            :customer => {
              :last_name => "Doe"
            },
            :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::Visa,
              :expiration_date => "12/12",
            },
            :options => {:store_in_vault_on_success => false},
          )
          result.success?.should == false
          result.transaction.vault_customer.should be_nil
          result.transaction.vault_credit_card.should be_nil
        end
      end
    end

    context "service fees" do
      it "allows specifying service fees" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :merchant_account_id => SpecHelper::NonDefaultSubMerchantAccountId,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "12/12",
          },
          :service_fee_amount => "1.00",
        )
        result.success?.should == true
        result.transaction.service_fee_amount.should == BigDecimal("1.00")
      end

      it "raises an error if transaction merchant account is a master" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :merchant_account_id => SpecHelper::NonDefaultMerchantAccountId,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "12/12",
          },
          :service_fee_amount => "1.00",
        )
        result.success?.should == false
        expected_error_code = Braintree::ErrorCodes::Transaction::ServiceFeeAmountNotAllowedOnMasterMerchantAccount
        result.errors.for(:transaction).on(:service_fee_amount)[0].code.should == expected_error_code
      end

      it "raises an error if no service fee is present on a sub merchant account transaction" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :merchant_account_id => SpecHelper::NonDefaultSubMerchantAccountId,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "12/12",
          },
        )
        result.success?.should == false
        expected_error_code = Braintree::ErrorCodes::Transaction::SubMerchantAccountRequiresServiceFeeAmount
        result.errors.for(:transaction).on(:merchant_account_id)[0].code.should == expected_error_code
      end

      it "raises an error if service fee amount is negative" do
        result = Braintree::Transaction.create(
          :merchant_account_id => SpecHelper::NonDefaultSubMerchantAccountId,
          :service_fee_amount => "-1.00",
        )
        result.success?.should == false
        result.errors.for(:transaction).on(:service_fee_amount)[0].code.should == Braintree::ErrorCodes::Transaction::ServiceFeeAmountCannotBeNegative
      end

      it "raises an error if service fee amount is invalid" do
        result = Braintree::Transaction.create(
          :merchant_account_id => SpecHelper::NonDefaultSubMerchantAccountId,
          :service_fee_amount => "invalid amount",
        )
        result.success?.should == false
        result.errors.for(:transaction).on(:service_fee_amount)[0].code.should == Braintree::ErrorCodes::Transaction::ServiceFeeAmountFormatIsInvalid
      end
    end

    context "escrow" do
      it "allows specifying transactions to be held for escrow" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :merchant_account_id => SpecHelper::NonDefaultSubMerchantAccountId,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "12/12",
          },
          :service_fee_amount => "10.00",
          :options => {:hold_in_escrow => true},
        )

        result.success?.should == true
        result.transaction.escrow_status.should == Braintree::Transaction::EscrowStatus::HoldPending
      end

      it "raises an error if transaction merchant account is a master" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :merchant_account_id => SpecHelper::NonDefaultMerchantAccountId,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "12/12",
          },
          :service_fee_amount => "1.00",
          :options => {:hold_in_escrow => true},
        )
        result.success?.should == false
        expected_error_code = Braintree::ErrorCodes::Transaction::CannotHoldInEscrow
        result.errors.for(:transaction).on(:base)[0].code.should == expected_error_code
      end
    end

    context "client API" do
      it "can create a transaction with a shared card nonce" do
        nonce = nonce_for_new_payment_method(
          :credit_card => {
            :number => "4111111111111111",
            :expiration_month => "11",
            :expiration_year => "2099",
          },
          :share => true,
        )
        nonce.should_not be_nil

        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :payment_method_nonce => nonce,
        )
        result.success?.should == true
      end

      it "can create a transaction with a vaulted card nonce" do
        customer = Braintree::Customer.create!
        nonce = nonce_for_new_payment_method(
          :credit_card => {
            :number => "4111111111111111",
            :expiration_month => "11",
            :expiration_year => "2099",
          },
          :client_token_options => {
            :customer_id => customer.id,
          },
        )
        nonce.should_not be_nil

        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :payment_method_nonce => nonce,
        )
        result.success?.should == true
      end

      it "can create a transaction with a vaulted PayPal account" do
        customer = Braintree::Customer.create!
        nonce = nonce_for_new_payment_method(
          :paypal_account => {
            :consent_code => "PAYPAL_CONSENT_CODE",
          },
          :client_token_options => {
            :customer_id => customer.id,
          },
        )
        nonce.should_not be_nil

        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :payment_method_nonce => nonce,
        )
        result.success?.should == true
        result.transaction.paypal_details.should_not be_nil
        result.transaction.paypal_details.debug_id.should_not be_nil
      end

      it "can create a transaction with a params nonce with PayPal account params" do
        customer = Braintree::Customer.create!
        nonce = nonce_for_new_payment_method(
          :paypal_account => {
            :consent_code => "PAYPAL_CONSENT_CODE",
          },
        )
        nonce.should_not be_nil

        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :payment_method_nonce => nonce,
        )
        result.success?.should == true
        result.transaction.paypal_details.should_not be_nil
        result.transaction.paypal_details.debug_id.should_not be_nil
      end

      it "can create a transaction with a fake apple pay nonce" do
        customer = Braintree::Customer.create!
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :payment_method_nonce => Braintree::Test::Nonce::ApplePayVisa,
        )
        result.success?.should == true
        result.transaction.should_not be_nil
        apple_pay_details = result.transaction.apple_pay_details
        apple_pay_details.should_not be_nil
        apple_pay_details.bin.should_not be_nil
        apple_pay_details.card_type.should == Braintree::ApplePayCard::CardType::Visa
        apple_pay_details.payment_instrument_name.should == "Visa 8886"
        apple_pay_details.source_description.should == "Visa 8886"
        apple_pay_details.expiration_month.to_i.should > 0
        apple_pay_details.expiration_year.to_i.should > 0
        apple_pay_details.cardholder_name.should_not be_nil
        apple_pay_details.image_url.should_not be_nil
        apple_pay_details.token.should be_nil
        apple_pay_details.prepaid.should_not be_nil
        apple_pay_details.healthcare.should_not be_nil
        apple_pay_details.debit.should_not be_nil
        apple_pay_details.durbin_regulated.should_not be_nil
        apple_pay_details.commercial.should_not be_nil
        apple_pay_details.payroll.should_not be_nil
        apple_pay_details.product_id.should_not be_nil
      end

      it "can create a vaulted transaction with a fake apple pay nonce" do
        customer = Braintree::Customer.create!
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :payment_method_nonce => Braintree::Test::Nonce::ApplePayVisa,
          :options => {:store_in_vault_on_success => true},
        )
        result.success?.should == true
        result.transaction.should_not be_nil
        apple_pay_details = result.transaction.apple_pay_details
        apple_pay_details.should_not be_nil
        apple_pay_details.card_type.should == Braintree::ApplePayCard::CardType::Visa
        apple_pay_details.payment_instrument_name.should == "Visa 8886"
        apple_pay_details.source_description.should == "Visa 8886"
        apple_pay_details.expiration_month.to_i.should > 0
        apple_pay_details.expiration_year.to_i.should > 0
        apple_pay_details.cardholder_name.should_not be_nil
        apple_pay_details.image_url.should_not be_nil
        apple_pay_details.token.should_not be_nil
      end

      it "can create a transaction with a fake google pay proxy card nonce" do
        customer = Braintree::Customer.create!
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :payment_method_nonce => Braintree::Test::Nonce::GooglePayDiscover,
        )
        result.success?.should == true
        result.transaction.should_not be_nil
        google_pay_details = result.transaction.google_pay_details
        google_pay_details.should_not be_nil
        google_pay_details.bin.should_not be_nil
        google_pay_details.card_type.should == Braintree::CreditCard::CardType::Discover
        google_pay_details.virtual_card_type.should == Braintree::CreditCard::CardType::Discover
        google_pay_details.last_4.should == "1117"
        google_pay_details.virtual_card_last_4.should == "1117"
        google_pay_details.source_description.should == "Discover 1111"
        google_pay_details.expiration_month.to_i.should > 0
        google_pay_details.expiration_year.to_i.should > 0
        google_pay_details.google_transaction_id.should == "google_transaction_id"
        google_pay_details.image_url.should_not be_nil
        google_pay_details.is_network_tokenized?.should == false
        google_pay_details.token.should be_nil
        google_pay_details.prepaid.should_not be_nil
        google_pay_details.healthcare.should_not be_nil
        google_pay_details.debit.should_not be_nil
        google_pay_details.durbin_regulated.should_not be_nil
        google_pay_details.commercial.should_not be_nil
        google_pay_details.payroll.should_not be_nil
        google_pay_details.product_id.should_not be_nil
      end

      it "can create a vaulted transaction with a fake google pay proxy card nonce" do
        customer = Braintree::Customer.create!
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :payment_method_nonce => Braintree::Test::Nonce::GooglePayDiscover,
          :options => {:store_in_vault_on_success => true},
        )
        result.success?.should == true
        result.transaction.should_not be_nil
        google_pay_details = result.transaction.google_pay_details
        google_pay_details.should_not be_nil
        google_pay_details.card_type.should == Braintree::CreditCard::CardType::Discover
        google_pay_details.virtual_card_type.should == Braintree::CreditCard::CardType::Discover
        google_pay_details.last_4.should == "1117"
        google_pay_details.virtual_card_last_4.should == "1117"
        google_pay_details.source_description.should == "Discover 1111"
        google_pay_details.expiration_month.to_i.should > 0
        google_pay_details.expiration_year.to_i.should > 0
        google_pay_details.google_transaction_id.should == "google_transaction_id"
        google_pay_details.image_url.should_not be_nil
        google_pay_details.is_network_tokenized?.should == false
        google_pay_details.token.should_not be_nil
      end

      it "can create a transaction with a fake google pay network token nonce" do
        customer = Braintree::Customer.create!
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :payment_method_nonce => Braintree::Test::Nonce::GooglePayMasterCard,
        )
        result.success?.should == true
        result.transaction.should_not be_nil
        google_pay_details = result.transaction.google_pay_details
        google_pay_details.should_not be_nil
        google_pay_details.card_type.should == Braintree::CreditCard::CardType::MasterCard
        google_pay_details.virtual_card_type.should == Braintree::CreditCard::CardType::MasterCard
        google_pay_details.last_4.should == "4444"
        google_pay_details.virtual_card_last_4.should == "4444"
        google_pay_details.source_description.should == "MasterCard 4444"
        google_pay_details.expiration_month.to_i.should > 0
        google_pay_details.expiration_year.to_i.should > 0
        google_pay_details.google_transaction_id.should == "google_transaction_id"
        google_pay_details.is_network_tokenized?.should == true
      end

      it "can create a transaction with a fake venmo account nonce" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :merchant_account_id => SpecHelper::FakeVenmoAccountMerchantAccountId,
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :payment_method_nonce => Braintree::Test::Nonce::VenmoAccount,
          :options => {:store_in_vault => true},
        )
        result.should be_success

        result.transaction.payment_instrument_type.should == Braintree::PaymentInstrumentType::VenmoAccount
        venmo_account_details = result.transaction.venmo_account_details
        venmo_account_details.should be_a(Braintree::Transaction::VenmoAccountDetails)
        venmo_account_details.token.should respond_to(:to_str)
        venmo_account_details.username.should == "venmojoe"
        venmo_account_details.venmo_user_id.should == "1234567891234567891"
        venmo_account_details.image_url.should include(".png")
        venmo_account_details.source_description.should == "Venmo Account: venmojoe"
      end

      it "can create a transaction with a fake venmo account nonce specifying a profile" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :merchant_account_id => SpecHelper::FakeVenmoAccountMerchantAccountId,
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :payment_method_nonce => Braintree::Test::Nonce::VenmoAccount,
          :options => {:store_in_vault => true, :venmo => {:profile_id => "integration_venmo_merchant_public_id"}},
        )
        result.should be_success
      end

      it "can create a transaction with an unknown nonce" do
        customer = Braintree::Customer.create!
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :payment_method_nonce => Braintree::Test::Nonce::AbstractTransactable,
        )
        result.success?.should == true
        result.transaction.should_not be_nil
      end

      it "can create a transaction with local payment webhook content" do
        result = Braintree::Transaction.sale(
          :amount => "100",
          :options => {
            :submit_for_settlement => true
          },
          :paypal_account => {
            :payer_id => "PAYER-1234",
            :payment_id => "PAY-5678",
          },
        )

        result.success?.should == true
        result.transaction.status.should == Braintree::Transaction::Status::Settling
        result.transaction.paypal_details.payer_id.should == "PAYER-1234"
        result.transaction.paypal_details.payment_id.should == "PAY-5678"
      end

      it "can create a transaction with a payee id" do
        customer = Braintree::Customer.create!
        nonce = nonce_for_new_payment_method(
          :paypal_account => {
            :consent_code => "PAYPAL_CONSENT_CODE",
          },
        )
        nonce.should_not be_nil

        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :payment_method_nonce => nonce,
          :paypal_account => {
            :payee_id => "fake-payee-id"
          },
        )

        result.success?.should == true
        result.transaction.paypal_details.should_not be_nil
        result.transaction.paypal_details.debug_id.should_not be_nil
        result.transaction.paypal_details.payee_id.should == "fake-payee-id"
      end

      it "can create a transaction with a payee id in the options params" do
        customer = Braintree::Customer.create!
        nonce = nonce_for_new_payment_method(
          :paypal_account => {
            :consent_code => "PAYPAL_CONSENT_CODE",
          },
        )
        nonce.should_not be_nil

        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :payment_method_nonce => nonce,
          :paypal_account => {},
          :options => {
            :payee_id => "fake-payee-id"
          },
        )

        result.success?.should == true
        result.transaction.paypal_details.should_not be_nil
        result.transaction.paypal_details.debug_id.should_not be_nil
        result.transaction.paypal_details.payee_id.should == "fake-payee-id"
      end

      it "can create a transaction with a payee id in options.paypal" do
        customer = Braintree::Customer.create!
        nonce = nonce_for_new_payment_method(
          :paypal_account => {
            :consent_code => "PAYPAL_CONSENT_CODE",
          },
        )
        nonce.should_not be_nil

        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :payment_method_nonce => nonce,
          :options => {
            :paypal => {
              :payee_id => "fake-payee-id"
            }
          },
        )

        result.success?.should == true
        result.transaction.paypal_details.should_not be_nil
        result.transaction.paypal_details.debug_id.should_not be_nil
        result.transaction.paypal_details.payee_id.should == "fake-payee-id"
      end

      it "can create a transaction with a payee email" do
        customer = Braintree::Customer.create!
        nonce = nonce_for_new_payment_method(
          :paypal_account => {
            :consent_code => "PAYPAL_CONSENT_CODE",
          },
        )
        nonce.should_not be_nil

        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :payment_method_nonce => nonce,
          :paypal_account => {
            :payee_email => "bt_seller_us@paypal.com"
          },
        )

        result.success?.should == true
        result.transaction.paypal_details.should_not be_nil
        result.transaction.paypal_details.debug_id.should_not be_nil
        result.transaction.paypal_details.payee_email.should == "bt_seller_us@paypal.com"
      end

      it "can create a transaction with a payee email in the options params" do
        customer = Braintree::Customer.create!
        nonce = nonce_for_new_payment_method(
          :paypal_account => {
            :consent_code => "PAYPAL_CONSENT_CODE",
          },
        )
        nonce.should_not be_nil

        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :payment_method_nonce => nonce,
          :paypal_account => {},
          :options => {
            :payee_email => "bt_seller_us@paypal.com"
          },
        )

        result.success?.should == true
        result.transaction.paypal_details.should_not be_nil
        result.transaction.paypal_details.debug_id.should_not be_nil
        result.transaction.paypal_details.payee_email.should == "bt_seller_us@paypal.com"
      end

      it "can create a transaction with a payee email in options.paypal" do
        customer = Braintree::Customer.create!
        nonce = nonce_for_new_payment_method(
          :paypal_account => {
            :consent_code => "PAYPAL_CONSENT_CODE",
          },
        )
        nonce.should_not be_nil

        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :payment_method_nonce => nonce,
          :options => {
            :paypal => {
              :payee_email => "bt_seller_us@paypal.com"
            }
          },
        )

        result.success?.should == true
        result.transaction.paypal_details.should_not be_nil
        result.transaction.paypal_details.debug_id.should_not be_nil
        result.transaction.paypal_details.payee_email.should == "bt_seller_us@paypal.com"
      end

      it "can create a transaction with a paypal custom field" do
        customer = Braintree::Customer.create!
        nonce = nonce_for_new_payment_method(
          :paypal_account => {
            :consent_code => "PAYPAL_CONSENT_CODE",
          },
        )
        nonce.should_not be_nil

        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :payment_method_nonce => nonce,
          :options => {
            :paypal => {
              :custom_field => "Additional info"
            }
          },
        )

        result.success?.should == true
        result.transaction.paypal_details.should_not be_nil
        result.transaction.paypal_details.debug_id.should_not be_nil
        result.transaction.paypal_details.custom_field.should == "Additional info"
      end

      it "can create a transaction with a paypal description" do
        customer = Braintree::Customer.create!
        nonce = nonce_for_new_payment_method(
          :paypal_account => {
            :consent_code => "PAYPAL_CONSENT_CODE",
          },
        )
        nonce.should_not be_nil

        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :payment_method_nonce => nonce,
          :options => {
            :paypal => {
              :description => "A great product"
            }
          },
        )

        result.success?.should == true
        result.transaction.paypal_details.should_not be_nil
        result.transaction.paypal_details.debug_id.should_not be_nil
        result.transaction.paypal_details.description.should == "A great product"
      end

      it "can create a transaction with STC supplementary data" do
        customer = Braintree::Customer.create!
        nonce = nonce_for_new_payment_method(
          :paypal_account => {
            :consent_code => "PAYPAL_CONSENT_CODE",
          },
        )
        nonce.should_not be_nil

        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :payment_method_nonce => nonce,
          :options => {
            :paypal => {
              :supplementary_data => {
                :key1 => "value1",
                :key2 => "value2",
              }
            }
          },
        )

        # note - supplementary data is not returned in response
        result.success?.should == true
      end
    end

    context "three_d_secure" do
      it "can create a transaction with a three_d_secure token" do
        three_d_secure_token = SpecHelper.create_3ds_verification(
          SpecHelper::ThreeDSecureMerchantAccountId,
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_month => "12",
          :expiration_year => "2012",
        )

        result = Braintree::Transaction.create(
          :type => "sale",
          :merchant_account_id => SpecHelper::ThreeDSecureMerchantAccountId,
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "12/12",
          },
          :three_d_secure_token => three_d_secure_token,
        )

        result.success?.should == true
      end

      it "gateway rejects transactions if 3DS is required but not provided" do
        nonce = nonce_for_new_payment_method(
          :credit_card => {
            :number => "4111111111111111",
            :expiration_month => "11",
            :expiration_year => "2099",
          },
        )
        nonce.should_not be_nil
        result = Braintree::Transaction.create(
          :merchant_account_id => SpecHelper::ThreeDSecureMerchantAccountId,
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :payment_method_nonce => nonce,
          :options => {
            :three_d_secure => {
              :required => true,
            }
          },
        )

        result.success?.should == false
        result.transaction.gateway_rejection_reason.should == Braintree::Transaction::GatewayRejectionReason::ThreeDSecure
      end


      it "can create a transaction without a three_d_secure_authentication_id" do
        result = Braintree::Transaction.create(
          :merchant_account_id => SpecHelper::ThreeDSecureMerchantAccountId,
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "12/12",
          },
        )
        result.success?.should == true
      end

      context "with three_d_secure_authentication_id" do
        it "can create a transaction with a three_d_secure_authentication_id" do
          three_d_secure_authentication_id = SpecHelper.create_3ds_verification(
            SpecHelper::ThreeDSecureMerchantAccountId,
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_month => "12",
            :expiration_year => "2022",
          )

          result = Braintree::Transaction.create(
            :merchant_account_id => SpecHelper::ThreeDSecureMerchantAccountId,
            :type => "sale",
            :amount => Braintree::Test::TransactionAmounts::Authorize,
            :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::Visa,
              :expiration_date => "12/22",
            },
            :three_d_secure_authentication_id => three_d_secure_authentication_id,
          )

          result.success?.should == true
        end
        it "returns an error if sent a nil three_d_secure_authentication_id" do
          result = Braintree::Transaction.create(
            :merchant_account_id => SpecHelper::ThreeDSecureMerchantAccountId,
            :type => "sale",
            :amount => Braintree::Test::TransactionAmounts::Authorize,
            :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::Visa,
              :expiration_date => "12/12",
            },
            :three_d_secure_authentication_id => nil,
          )
          result.success?.should == false
          result.errors.for(:transaction).on(:three_d_secure_authentication_id)[0].code.should == Braintree::ErrorCodes::Transaction::ThreeDSecureAuthenticationIdIsInvalid
        end
        it "returns an error if merchant_account in the payment_method does not match with 3ds data" do
          three_d_secure_authentication_id = SpecHelper.create_3ds_verification(
            SpecHelper::ThreeDSecureMerchantAccountId,
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_month => "12",
            :expiration_year => "2012",
          )

          result = Braintree::Transaction.create(
            :type => "sale",
            :amount => Braintree::Test::TransactionAmounts::Authorize,
            :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::MasterCard,
              :expiration_date => "12/12",
            },
            :three_d_secure_authentication_id => three_d_secure_authentication_id,
          )
          result.success?.should == false
          result.errors.for(:transaction).on(:three_d_secure_authentication_id)[0].code.should == Braintree::ErrorCodes::Transaction::ThreeDSecureTransactionPaymentMethodDoesntMatchThreeDSecureAuthenticationPaymentMethod
        end
        it "returns an error if 3ds lookup data does not match txn data" do
          three_d_secure_authentication_id = SpecHelper.create_3ds_verification(
            SpecHelper::ThreeDSecureMerchantAccountId,
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_month => "12",
            :expiration_year => "2012",
          )

          result = Braintree::Transaction.create(
            :merchant_account_id => SpecHelper::ThreeDSecureMerchantAccountId,
            :type => "sale",
            :amount => Braintree::Test::TransactionAmounts::Authorize,
            :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::MasterCard,
              :expiration_date => "12/12",
            },
            :three_d_secure_authentication_id => three_d_secure_authentication_id,
          )
          result.success?.should == false
          result.errors.for(:transaction).on(:three_d_secure_authentication_id)[0].code.should == Braintree::ErrorCodes::Transaction::ThreeDSecureTransactionPaymentMethodDoesntMatchThreeDSecureAuthenticationPaymentMethod
        end
        it "returns an error if three_d_secure_authentication_id is supplied with three_d_secure_pass_thru" do
          three_d_secure_authentication_id = SpecHelper.create_3ds_verification(
            SpecHelper::ThreeDSecureMerchantAccountId,
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_month => "12",
            :expiration_year => "2012",
          )
          result = Braintree::Transaction.create(
            :merchant_account_id => SpecHelper::ThreeDSecureMerchantAccountId,
            :type => "sale",
            :amount => Braintree::Test::TransactionAmounts::Authorize,
            :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::Visa,
              :expiration_date => "12/12",
            },
            :three_d_secure_authentication_id => three_d_secure_authentication_id,
            :three_d_secure_pass_thru => {
              :eci_flag => "02",
              :cavv => "some_cavv",
              :xid => "some_xid",
              :three_d_secure_version => "1.0.2",
              :authentication_response => "Y",
              :directory_response => "Y",
              :cavv_algorithm => "2",
              :ds_transaction_id => "some_ds_id",
            },
          )
          result.success?.should == false
          result.errors.for(:transaction).on(:three_d_secure_authentication_id)[0].code.should == Braintree::ErrorCodes::Transaction::ThreeDSecureAuthenticationIdWithThreeDSecurePassThruIsInvalid
        end
      end

      context "with three_d_secure_token" do
        it "can create a transaction with a three_d_secure token" do
          three_d_secure_token = SpecHelper.create_3ds_verification(
            SpecHelper::ThreeDSecureMerchantAccountId,
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_month => "12",
            :expiration_year => "2012",
          )

          result = Braintree::Transaction.create(
            :merchant_account_id => SpecHelper::ThreeDSecureMerchantAccountId,
            :type => "sale",
            :amount => Braintree::Test::TransactionAmounts::Authorize,
            :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::Visa,
              :expiration_date => "12/12",
            },
            :three_d_secure_token => three_d_secure_token,
          )

          result.success?.should == true
        end

        it "returns an error if sent a nil three_d_secure token" do
          result = Braintree::Transaction.create(
            :merchant_account_id => SpecHelper::ThreeDSecureMerchantAccountId,
            :type => "sale",
            :amount => Braintree::Test::TransactionAmounts::Authorize,
            :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::Visa,
              :expiration_date => "12/12",
            },
            :three_d_secure_token => nil,
          )
          result.success?.should == false
          result.errors.for(:transaction).on(:three_d_secure_token)[0].code.should == Braintree::ErrorCodes::Transaction::ThreeDSecureTokenIsInvalid
        end

        it "returns an error if 3ds lookup data does not match txn data" do
          three_d_secure_token = SpecHelper.create_3ds_verification(
            SpecHelper::ThreeDSecureMerchantAccountId,
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_month => "12",
            :expiration_year => "2012",
          )

          result = Braintree::Transaction.create(
            :merchant_account_id => SpecHelper::ThreeDSecureMerchantAccountId,
            :type => "sale",
            :amount => Braintree::Test::TransactionAmounts::Authorize,
            :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::MasterCard,
              :expiration_date => "12/12",
            },
            :three_d_secure_token => three_d_secure_token,
          )
          result.success?.should == false
          result.errors.for(:transaction).on(:three_d_secure_token)[0].code.should == Braintree::ErrorCodes::Transaction::ThreeDSecureTransactionDataDoesntMatchVerify
        end
      end

      it "can create a transaction with a three_d_secure_pass_thru" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "12/12",
          },
          :three_d_secure_pass_thru => {
            :eci_flag => "02",
            :cavv => "some_cavv",
            :xid => "some_xid",
            :three_d_secure_version => "1.0.2",
            :authentication_response => "Y",
            :directory_response => "Y",
            :cavv_algorithm => "2",
            :ds_transaction_id => "some_ds_id",
          },
        )

        result.success?.should == true
        result.transaction.status.should == Braintree::Transaction::Status::Authorized
      end

      it "returns an error for transaction with three_d_secure_pass_thru when processor settings do not support 3DS for card type" do
        result = Braintree::Transaction.create(
          :merchant_account_id => "heartland_ma",
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "12/12",
          },
          :three_d_secure_pass_thru => {
            :eci_flag => "02",
            :cavv => "some_cavv",
            :xid => "some_xid",
            :three_d_secure_version => "1.0.2",
            :authentication_response => "Y",
            :directory_response => "Y",
            :cavv_algorithm => "2",
            :ds_transaction_id => "some_ds_id",
          },
        )
        result.success?.should == false
        result.errors.for(:transaction).on(:merchant_account_id)[0].code.should == Braintree::ErrorCodes::Transaction::ThreeDSecureMerchantAccountDoesNotSupportCardType
      end

      it "returns an error for transaction when the three_d_secure_pass_thru eci_flag is missing" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "12/12",
          },
          :three_d_secure_pass_thru => {
            :eci_flag => "",
            :cavv => "some_cavv",
            :xid => "some_xid",
          },
        )
        result.success?.should == false
        result.errors.for(:transaction).for(:three_d_secure_pass_thru).on(:eci_flag)[0].code.should == Braintree::ErrorCodes::Transaction::ThreeDSecureEciFlagIsRequired
      end

      it "returns an error for transaction when the three_d_secure_pass_thru cavv or xid is missing" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "12/12",
          },
          :three_d_secure_pass_thru => {
            :eci_flag => "05",
            :cavv => "",
            :xid => "",
            :three_d_secure_version => "1.0.2",
            :authentication_response => "Y",
            :directory_response => "Y",
            :cavv_algorithm => "2",
            :ds_transaction_id => "some_ds_id",
          },
        )
        result.success?.should == false
        result.errors.for(:transaction).for(:three_d_secure_pass_thru).on(:cavv)[0].code.should == Braintree::ErrorCodes::Transaction::ThreeDSecureCavvIsRequired
      end

      it "returns an error for transaction when the three_d_secure_pass_thru eci_flag is invalid" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "12/12",
          },
          :three_d_secure_pass_thru => {
            :eci_flag => "bad_eci_flag",
            :cavv => "some_cavv",
            :xid => "some_xid",
            :three_d_secure_version => "1.0.2",
            :authentication_response => "Y",
            :directory_response => "Y",
            :cavv_algorithm => "2",
            :ds_transaction_id => "some_ds_id",
          },
        )
        result.success?.should == false
        result.errors.for(:transaction).for(:three_d_secure_pass_thru).on(:eci_flag)[0].code.should == Braintree::ErrorCodes::Transaction::ThreeDSecureEciFlagIsInvalid
      end

      it "returns an error for transaction when the three_d_secure_pass_thru three_d_secure_version is invalid" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "12/12",
          },
          :three_d_secure_pass_thru => {
            :eci_flag => "02",
            :cavv => "some_cavv",
            :xid => "some_xid",
            :three_d_secure_version => "invalid",
            :authentication_response => "Y",
            :directory_response => "Y",
            :cavv_algorithm => "2",
            :ds_transaction_id => "some_ds_id",
          },
        )
        result.success?.should == false
        result.errors.for(:transaction).for(:three_d_secure_pass_thru).on(:three_d_secure_version)[0].code.should == Braintree::ErrorCodes::Transaction::ThreeDSecureThreeDSecureVersionIsInvalid
      end

      it "returns an error for transaction when the three_d_secure_pass_thru authentication_response is invalid" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :merchant_account_id => SpecHelper:: AdyenMerchantAccountId,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "12/12",
          },
          :three_d_secure_pass_thru => {
            :eci_flag => "02",
            :cavv => "some_cavv",
            :xid => "some_xid",
            :three_d_secure_version => "1.0.2",
            :authentication_response => "asdf",
            :directory_response => "Y",
            :cavv_algorithm => "2",
            :ds_transaction_id => "some_ds_id",
          },
        )
        result.success?.should == false
        result.errors.for(:transaction).for(:three_d_secure_pass_thru).on(:authentication_response)[0].code.should == Braintree::ErrorCodes::Transaction::ThreeDSecureAuthenticationResponseIsInvalid
      end

      it "returns an error for transaction when the three_d_secure_pass_thru directory_response is invalid" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :merchant_account_id => SpecHelper:: AdyenMerchantAccountId,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "12/12",
          },
          :three_d_secure_pass_thru => {
            :eci_flag => "02",
            :cavv => "some_cavv",
            :xid => "some_xid",
            :three_d_secure_version => "1.0.2",
            :authentication_response => "Y",
            :directory_response => "abc",
            :cavv_algorithm => "2",
            :ds_transaction_id => "some_ds_id",
          },
        )
        result.success?.should == false
        result.errors.for(:transaction).for(:three_d_secure_pass_thru).on(:directory_response)[0].code.should == Braintree::ErrorCodes::Transaction::ThreeDSecureDirectoryResponseIsInvalid
      end

      it "returns an error for transaction when the three_d_secure_pass_thru cavv_algorithm is invalid" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :merchant_account_id => SpecHelper:: AdyenMerchantAccountId,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "12/12",
          },
          :three_d_secure_pass_thru => {
            :eci_flag => "02",
            :cavv => "some_cavv",
            :xid => "some_xid",
            :three_d_secure_version => "1.0.2",
            :authentication_response => "Y",
            :directory_response => "Y",
            :cavv_algorithm => "bad_alg",
            :ds_transaction_id => "some_ds_id",
          },
        )
        result.success?.should == false
        result.errors.for(:transaction).for(:three_d_secure_pass_thru).on(:cavv_algorithm)[0].code.should == Braintree::ErrorCodes::Transaction::ThreeDSecureCavvAlgorithmIsInvalid
      end
    end

    context "paypal" do
      context "using a vaulted paypal account payment_method_token" do
        it "can create a transaction" do
          payment_method_result = Braintree::PaymentMethod.create(
            :customer_id => Braintree::Customer.create.customer.id,
            :payment_method_nonce => Braintree::Test::Nonce::PayPalBillingAgreement,
          )
          result = Braintree::Transaction.create(
            :type => "sale",
            :amount => Braintree::Test::TransactionAmounts::Authorize,
            :payment_method_token => payment_method_result.payment_method.token,
          )

          result.should be_success
          result.transaction.payment_instrument_type.should == Braintree::PaymentInstrumentType::PayPalAccount
          result.transaction.paypal_details.should_not be_nil
          result.transaction.paypal_details.debug_id.should_not be_nil
        end
      end

      context "future" do
        it "can create a paypal transaction with a nonce without vaulting" do
          payment_method_token = rand(36**3).to_s(36)
          nonce = nonce_for_paypal_account(
            :consent_code => "PAYPAL_CONSENT_CODE",
            :token => payment_method_token,
          )

          result = Braintree::Transaction.create(
            :type => "sale",
            :amount => Braintree::Test::TransactionAmounts::Authorize,
            :payment_method_nonce => nonce,
          )

          result.should be_success
          result.transaction.paypal_details.should_not be_nil
          result.transaction.paypal_details.debug_id.should_not be_nil

          expect do
            Braintree::PaymentMethod.find(payment_method_token)
          end.to raise_error(Braintree::NotFoundError, "payment method with token \"#{payment_method_token}\" not found")
        end

        it "can create a paypal transaction and vault a paypal account" do
          payment_method_token = rand(36**3).to_s(36)
          nonce = nonce_for_paypal_account(
            :consent_code => "PAYPAL_CONSENT_CODE",
            :token => payment_method_token,
          )

          result = Braintree::Transaction.create(
            :type => "sale",
            :amount => Braintree::Test::TransactionAmounts::Authorize,
            :payment_method_nonce => nonce,
            :options => {:store_in_vault => true},
          )

          result.success?.should == true
          result.transaction.paypal_details.should_not be_nil
          result.transaction.paypal_details.debug_id.should_not be_nil

          found_paypal_account = Braintree::PaymentMethod.find(payment_method_token)
          found_paypal_account.should be_a(Braintree::PayPalAccount)
          found_paypal_account.token.should == payment_method_token
        end
      end

      context "billing agreement" do
        it "can create a paypal billing agreement" do
          result = Braintree::Transaction.create(
            :type => "sale",
            :amount => Braintree::Test::TransactionAmounts::Authorize,
            :payment_method_nonce => Braintree::Test::Nonce::PayPalBillingAgreement,
            :options => {:store_in_vault => true},
          )

          result.should be_success
          result.transaction.paypal_details.should_not be_nil
          result.transaction.paypal_details.debug_id.should_not be_nil
          result.transaction.paypal_details.billing_agreement_id.should_not be_nil
        end
      end

      context "local payments" do
        it "can create a local payment transaction with a nonce" do
          result = Braintree::Transaction.create(
            :type => "sale",
            :amount => Braintree::Test::TransactionAmounts::Authorize,
            :payment_method_nonce => Braintree::Test::Nonce::LocalPayment,
          )

          result.should be_success
          result.transaction.local_payment_details.should_not be_nil
          result.transaction.local_payment_details.funding_source.should_not be_nil
          result.transaction.local_payment_details.payment_id.should_not be_nil
          result.transaction.local_payment_details.capture_id.should_not be_nil
          result.transaction.local_payment_details.transaction_fee_amount.should_not be_nil
          result.transaction.local_payment_details.transaction_fee_currency_iso_code.should_not be_nil
        end
      end

      context "onetime" do
        it "can create a paypal transaction with a nonce" do
          result = Braintree::Transaction.create(
            :type => "sale",
            :amount => Braintree::Test::TransactionAmounts::Authorize,
            :payment_method_nonce => Braintree::Test::Nonce::PayPalOneTimePayment,
          )

          result.should be_success
          result.transaction.paypal_details.should_not be_nil
          result.transaction.paypal_details.debug_id.should_not be_nil
        end

        it "can create a paypal transaction and does not vault even if asked to" do
          payment_method_token = rand(36**3).to_s(36)
          nonce = nonce_for_paypal_account(
            :access_token => "PAYPAL_ACCESS_TOKEN",
            :token => payment_method_token,
          )

          result = Braintree::Transaction.create(
            :type => "sale",
            :amount => Braintree::Test::TransactionAmounts::Authorize,
            :payment_method_nonce => nonce,
            :options => {:store_in_vault => true},
          )

          result.success?.should == true
          result.transaction.paypal_details.should_not be_nil
          result.transaction.paypal_details.debug_id.should_not be_nil

          expect do
            Braintree::PaymentMethod.find(payment_method_token)
          end.to raise_error(Braintree::NotFoundError, "payment method with token \"#{payment_method_token}\" not found")
        end
      end

      context "submit" do
        it "submits for settlement if instructed to do so" do
          result = Braintree::Transaction.sale(
            :amount => "100",
            :payment_method_nonce => Braintree::Test::Nonce::PayPalOneTimePayment,
            :options => {
              :submit_for_settlement => true
            },
          )
          result.success?.should == true
          result.transaction.status.should == Braintree::Transaction::Status::Settling
        end
      end

      context "void" do
        it "successfully voids a paypal transaction that's been authorized" do
          sale_transaction = Braintree::Transaction.sale!(
            :amount => Braintree::Test::TransactionAmounts::Authorize,
            :payment_method_nonce => Braintree::Test::Nonce::PayPalOneTimePayment,
          )

          void_transaction = Braintree::Transaction.void!(sale_transaction.id)
          void_transaction.should == sale_transaction
          void_transaction.status.should == Braintree::Transaction::Status::Voided
        end

        it "fails to void a paypal transaction that's been declined" do
          sale_transaction = Braintree::Transaction.sale(
            :amount => Braintree::Test::TransactionAmounts::Decline,
            :payment_method_nonce => Braintree::Test::Nonce::PayPalOneTimePayment,
          ).transaction

          expect do
            Braintree::Transaction.void!(sale_transaction.id)
          end.to raise_error(Braintree::ValidationsFailed)
        end
      end

      describe "refund" do
        context "partial refunds" do
          it "allows partial refunds" do
            transaction = create_paypal_transaction_for_refund

            result = Braintree::Transaction.refund(transaction.id, transaction.amount / 2)
            result.should be_success
            result.transaction.type.should == "credit"
          end

          it "allows multiple partial refunds" do
            transaction = create_paypal_transaction_for_refund

            transaction_1 = Braintree::Transaction.refund(transaction.id, transaction.amount / 2).transaction
            transaction_2 = Braintree::Transaction.refund(transaction.id, transaction.amount / 2).transaction

            transaction = Braintree::Transaction.find(transaction.id)
            transaction.refund_ids.sort.should == [transaction_1.id, transaction_2.id].sort
          end

          it "allows partial refunds passed in an options hash" do
            transaction = create_paypal_transaction_for_refund

            transaction_1 = Braintree::Transaction.refund(transaction.id, :amount => transaction.amount / 2).transaction
            transaction_2 = Braintree::Transaction.refund(transaction.id, :amount => transaction.amount / 2).transaction

            transaction = Braintree::Transaction.find(transaction.id)
            transaction.refund_ids.sort.should == [transaction_1.id, transaction_2.id].sort
          end
        end

        it "returns a successful result if successful" do
          transaction = create_paypal_transaction_for_refund

          result = Braintree::Transaction.refund(transaction.id)

          result.success?.should == true
          result.transaction.type.should == "credit"
        end

        it "allows an order_id to be passed for the refund" do
          transaction = create_paypal_transaction_for_refund

          result = Braintree::Transaction.refund(transaction.id, :order_id => "123458798123")

          result.success?.should == true
          result.transaction.type.should == "credit"
          result.transaction.order_id.should == "123458798123"
        end

        it "allows amount and order_id to be passed for the refund" do
          transaction = create_paypal_transaction_for_refund

          result = Braintree::Transaction.refund(transaction.id, :amount => transaction.amount/2, :order_id => "123458798123")

          result.success?.should == true
          result.transaction.type.should == "credit"
          result.transaction.order_id.should == "123458798123"
          result.transaction.amount.should == transaction.amount/2
        end

        it "allows merchant_account_id to be passed for the refund" do
          transaction = create_transaction_to_refund

          result = Braintree::Transaction.refund(
            transaction.id,
            :merchant_account_id => SpecHelper::NonDefaultMerchantAccountId,
          )

          result.success?.should == true
          result.transaction.type.should == "credit"
          result.transaction.merchant_account_id.should == SpecHelper::NonDefaultMerchantAccountId
        end

        it "does not allow arbitrary options to be passed" do
          transaction = create_paypal_transaction_for_refund

          expect {
            Braintree::Transaction.refund(transaction.id, :blah => "123458798123")
          }.to raise_error(ArgumentError)
        end

        it "assigns the refunded_transaction_id to the original transaction" do
          transaction = create_paypal_transaction_for_refund
          refund_transaction = Braintree::Transaction.refund(transaction.id).transaction

          refund_transaction.refunded_transaction_id.should == transaction.id
        end

        it "returns an error result if unsettled" do
          transaction = Braintree::Transaction.sale!(
            :amount => Braintree::Test::TransactionAmounts::Authorize,
            :payment_method_nonce => Braintree::Test::Nonce::PayPalOneTimePayment,
          )
          result = Braintree::Transaction.refund(transaction.id)
          result.success?.should == false
          result.errors.for(:transaction).on(:base)[0].code.should == Braintree::ErrorCodes::Transaction::CannotRefundUnlessSettled
        end

        it "handles soft declined refund authorizations" do
          transaction = Braintree::Transaction.sale!(
            :amount => "9000.00",
            :payment_method_nonce => Braintree::Test::Nonce::Transactable,
            :options => {
              :submit_for_settlement => true
            },
          )
          config = Braintree::Configuration.instantiate
          response = config.http.put("#{config.base_merchant_path}/transactions/#{transaction.id}/settle")
          result = Braintree::Transaction.refund(transaction.id, :amount => "2046.00")
          result.success?.should == false
          result.transaction.id.should =~ /^\w{6,}$/
          result.transaction.type.should == "credit"
          result.transaction.status.should == Braintree::Transaction::Status::ProcessorDeclined
          result.transaction.processor_response_code.should == "2046"
          result.transaction.processor_response_text.should == "Declined"
          result.transaction.processor_response_type.should == Braintree::ProcessorResponseTypes::SoftDeclined
          result.transaction.additional_processor_response.should == "2046 : Declined"
        end

        it "handles hard declined refund authorizations" do
          transaction = Braintree::Transaction.sale!(
            :amount => "9000.00",
            :payment_method_nonce => Braintree::Test::Nonce::Transactable,
            :options => {
              :submit_for_settlement => true
            },
          )
          config = Braintree::Configuration.instantiate
          response = config.http.put("#{config.base_merchant_path}/transactions/#{transaction.id}/settle")
          result = Braintree::Transaction.refund(transaction.id, :amount => "2009.00")
          result.success?.should == false
          result.transaction.id.should =~ /^\w{6,}$/
          result.transaction.type.should == "credit"
          result.transaction.status.should == Braintree::Transaction::Status::ProcessorDeclined
          result.transaction.processor_response_code.should == "2009"
          result.transaction.processor_response_text.should == "No Such Issuer"
          result.transaction.processor_response_type.should == Braintree::ProcessorResponseTypes::HardDeclined
          result.transaction.additional_processor_response.should == "2009 : No Such Issuer"
        end
      end

      context "handling errors" do
        it "handles bad unvalidated nonces" do
          nonce = nonce_for_paypal_account(
            :access_token => "PAYPAL_ACCESS_TOKEN",
            :consent_code => "PAYPAL_CONSENT_CODE",
          )

          result = Braintree::Transaction.create(
            :type => "sale",
            :amount => Braintree::Test::TransactionAmounts::Authorize,
            :payment_method_nonce => nonce,
          )

          result.should_not be_success
          result.errors.for(:transaction).for(:paypal_account).first.code.should == "82903"
        end

        it "handles non-existent nonces" do
          result = Braintree::Transaction.create(
            :type => "sale",
            :amount => Braintree::Test::TransactionAmounts::Authorize,
            :payment_method_nonce => "NON_EXISTENT_NONCE",
          )

          result.should_not be_success
          result.errors.for(:transaction).first.code.should == "91565"
        end
      end
    end

    context "line items" do
      it "allows creation with empty line items and returns none" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => "35.05",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :line_items => [],
        )
        result.success?.should == true
        result.transaction.line_items.should == []
      end

      it "allows creation with single line item with minimal fields and returns it" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => "45.15",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :line_items => [
            {
              :quantity => "1.0232",
              :name => "Name #1",
              :kind => "debit",
              :unit_amount => "45.1232",
              :total_amount => "45.15",
            },
          ],
        )
        result.success?.should == true
        result.transaction.line_items.length.should == 1
        line_item = result.transaction.line_items[0]
        line_item.quantity.should == BigDecimal("1.0232")
        line_item.name.should == "Name #1"
        line_item.kind.should == "debit"
        line_item.unit_amount.should == BigDecimal("45.1232")
        line_item.total_amount.should == BigDecimal("45.15")
      end

      it "allows creation with single line item with zero amount fields and returns it" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => "45.15",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :line_items => [
            {
              :quantity => "1.0232",
              :name => "Name #1",
              :kind => "debit",
              :unit_amount => "45.1232",
              :total_amount => "45.15",
              :unit_tax_amount => "0",
              :discount_amount => "0",
              :tax_amount => "0",
            },
          ],
        )
        result.success?.should == true
        result.transaction.line_items.length.should == 1
        line_item = result.transaction.line_items[0]
        line_item.quantity.should == BigDecimal("1.0232")
        line_item.name.should == "Name #1"
        line_item.kind.should == "debit"
        line_item.unit_amount.should == BigDecimal("45.1232")
        line_item.total_amount.should == BigDecimal("45.15")
        line_item.unit_tax_amount.should == BigDecimal("0")
        line_item.discount_amount.should == BigDecimal("0")
        line_item.tax_amount.should == BigDecimal("0")
      end

      it "allows creation with single line item and returns it" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => "45.15",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :line_items => [
            {
              :quantity => "1.0232",
              :name => "Name #1",
              :description => "Description #1",
              :kind => "debit",
              :unit_amount => "45.1232",
              :unit_tax_amount => "1.23",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :tax_amount => "4.50",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
              :url => "https://example.com/products/23434",
            },
          ],
        )
        result.success?.should == true
        result.transaction.line_items.length.should == 1
        line_item = result.transaction.line_items[0]
        line_item.quantity.should == BigDecimal("1.0232")
        line_item.name.should == "Name #1"
        line_item.description.should == "Description #1"
        line_item.kind.should == "debit"
        line_item.unit_amount.should == BigDecimal("45.1232")
        line_item.unit_tax_amount.should == BigDecimal("1.23")
        line_item.unit_of_measure.should == "gallon"
        line_item.discount_amount.should == BigDecimal("1.02")
        line_item.tax_amount.should == BigDecimal("4.50")
        line_item.total_amount.should == BigDecimal("45.15")
        line_item.product_code.should == "23434"
        line_item.commodity_code.should == "9SAASSD8724"
        line_item.url.should == "https://example.com/products/23434"
      end

      it "allows creation with multiple line items and returns them" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => "35.05",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :line_items => [
            {
              :quantity => "1.0232",
              :name => "Name #1",
              :kind => "debit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :tax_amount => "4.50",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
            {
              :quantity => "2.02",
              :name => "Name #2",
              :kind => "credit",
              :unit_amount => "5",
              :unit_of_measure => "gallon",
              :tax_amount => "1.50",
              :total_amount => "10.1",
            },
          ],
        )
        result.success?.should == true
        result.transaction.line_items.length.should == 2
        line_item_1 = result.transaction.line_items.find { |line_item| line_item.name == "Name #1" }
        line_item_1.quantity.should == BigDecimal("1.0232")
        line_item_1.name.should == "Name #1"
        line_item_1.kind.should == "debit"
        line_item_1.unit_amount.should == BigDecimal("45.1232")
        line_item_1.unit_of_measure.should == "gallon"
        line_item_1.discount_amount.should == BigDecimal("1.02")
        line_item_1.tax_amount.should == BigDecimal("4.50")
        line_item_1.total_amount.should == BigDecimal("45.15")
        line_item_1.product_code.should == "23434"
        line_item_1.commodity_code.should == "9SAASSD8724"
        line_item_2 = result.transaction.line_items.find { |line_item| line_item.name == "Name #2" }
        line_item_2.quantity.should == BigDecimal("2.02")
        line_item_2.name.should == "Name #2"
        line_item_2.kind.should == "credit"
        line_item_2.unit_amount.should == BigDecimal("5")
        line_item_2.unit_of_measure.should == "gallon"
        line_item_2.total_amount.should == BigDecimal("10.1")
        line_item_2.tax_amount.should == BigDecimal("1.50")
        line_item_2.discount_amount.should == nil
        line_item_2.product_code.should == nil
        line_item_2.commodity_code.should == nil
      end

      it "handles validation error commodity code is too long" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => "35.05",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :line_items => [
            {
              :quantity => "1.2322",
              :name => "Name #1",
              :kind => "debit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
            {
              :quantity => "1.2322",
              :name => "Name #2",
              :kind => "credit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "1234567890123",
            },
          ],
        )
        result.success?.should == false
        result.errors.for(:transaction).for(:line_items).for(:index_1).on(:commodity_code)[0].code.should == Braintree::ErrorCodes::TransactionLineItem::CommodityCodeIsTooLong
      end

      it "handles validation error description is too long" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => "35.05",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :line_items => [
            {
              :quantity => "1.2322",
              :name => "Name #1",
              :kind => "debit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
            {
              :quantity => "1.2322",
              :name => "Name #2",
              :description => "X" * 128,
              :kind => "credit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
          ],
        )
        result.success?.should == false
        result.errors.for(:transaction).for(:line_items).for(:index_1).on(:description)[0].code.should == Braintree::ErrorCodes::TransactionLineItem::DescriptionIsTooLong
      end

      it "handles validation error discount amount format is invalid" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => "35.05",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :line_items => [
            {
              :quantity => "1.2322",
              :name => "Name #1",
              :kind => "debit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
            {
              :quantity => "1.2322",
              :name => "Name #2",
              :kind => "credit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "$1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
          ],
        )
        result.success?.should == false
        result.errors.for(:transaction).for(:line_items).for(:index_1).on(:discount_amount)[0].code.should == Braintree::ErrorCodes::TransactionLineItem::DiscountAmountFormatIsInvalid
      end

      it "handles validation error discount amount is too large" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => "35.05",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :line_items => [
            {
              :quantity => "1.2322",
              :name => "Name #1",
              :kind => "debit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
            {
              :quantity => "1.2322",
              :name => "Name #2",
              :kind => "credit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "2147483648",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
          ],
        )
        result.success?.should == false
        result.errors.for(:transaction).for(:line_items).for(:index_1).on(:discount_amount)[0].code.should == Braintree::ErrorCodes::TransactionLineItem::DiscountAmountIsTooLarge
      end

      it "handles validation error discount amount cannot be negative" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => "35.05",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :line_items => [
            {
              :quantity => "1.2322",
              :name => "Name #1",
              :kind => "debit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
            {
              :quantity => "1.2322",
              :name => "Name #2",
              :kind => "credit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "-2",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
          ],
        )
        result.success?.should == false
        result.errors.for(:transaction).for(:line_items).for(:index_1).on(:discount_amount)[0].code.should == Braintree::ErrorCodes::TransactionLineItem::DiscountAmountCannotBeNegative
      end

      it "handles validation error tax amount format is invalid" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => "35.05",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :line_items => [
            {
              :quantity => "1.2322",
              :name => "Name #1",
              :kind => "debit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :tax_amount => "$1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
          ],
        )
        result.success?.should == false
        result.errors.for(:transaction).for(:line_items).for(:index_0).on(:tax_amount)[0].code.should == Braintree::ErrorCodes::TransactionLineItem::TaxAmountFormatIsInvalid
      end

      it "handles validation error tax amount is too large" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => "35.05",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :line_items => [
            {
              :quantity => "1.2322",
              :name => "Name #1",
              :kind => "debit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :tax_amount => "2147483648",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
          ],
        )
        result.success?.should == false
        result.errors.for(:transaction).for(:line_items).for(:index_0).on(:tax_amount)[0].code.should == Braintree::ErrorCodes::TransactionLineItem::TaxAmountIsTooLarge
      end

      it "handles validation error tax amount cannot be negative" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => "35.05",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :line_items => [
            {
              :quantity => "1.2322",
              :name => "Name #1",
              :kind => "debit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :tax_amount => "-2",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
          ],
        )
        result.success?.should == false
        result.errors.for(:transaction).for(:line_items).for(:index_0).on(:tax_amount)[0].code.should == Braintree::ErrorCodes::TransactionLineItem::TaxAmountCannotBeNegative
      end

      it "handles validation error kind is invalid" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => "35.05",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :line_items => [
            {
              :quantity => "1.2322",
              :name => "Name #1",
              :kind => "debit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
            {
              :quantity => "1.2322",
              :name => "Name #2",
              :kind => "sale",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
          ],
        )
        result.success?.should == false
        result.errors.for(:transaction).for(:line_items).for(:index_1).on(:kind)[0].code.should == Braintree::ErrorCodes::TransactionLineItem::KindIsInvalid
      end

      it "handles validation error kind is required" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => "35.05",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :line_items => [
            {
              :quantity => "1.2322",
              :name => "Name #1",
              :kind => "debit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
            {
              :quantity => "1.2322",
              :name => "Name #2",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
          ],
        )
        result.success?.should == false
        result.errors.for(:transaction).for(:line_items).for(:index_1).on(:kind)[0].code.should == Braintree::ErrorCodes::TransactionLineItem::KindIsRequired
      end

      it "handles validation error name is required" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => "35.05",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :line_items => [
            {
              :quantity => "1.2322",
              :name => "Name #1",
              :kind => "debit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
            {
              :quantity => "1.2322",
              :kind => "debit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
          ],
        )
        result.success?.should == false
        result.errors.for(:transaction).for(:line_items).for(:index_1).on(:name)[0].code.should == Braintree::ErrorCodes::TransactionLineItem::NameIsRequired
      end

      it "handles validation error name is too long" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => "35.05",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :line_items => [
            {
              :quantity => "1.2322",
              :name => "Name #1",
              :kind => "debit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
            {
              :quantity => "1.2322",
              :name => "X"*36,
              :kind => "debit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
          ],
        )
        result.success?.should == false
        result.errors.for(:transaction).for(:line_items).for(:index_1).on(:name)[0].code.should == Braintree::ErrorCodes::TransactionLineItem::NameIsTooLong
      end

      it "handles validation error product code is too long" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => "35.05",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :line_items => [
            {
              :quantity => "1.2322",
              :name => "Name #1",
              :kind => "debit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
            {
              :quantity => "1.2322",
              :name => "Name #2",
              :kind => "credit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "1234567890123",
              :commodity_code => "9SAASSD8724",
            },
          ],
        )
        result.success?.should == false
        result.errors.for(:transaction).for(:line_items).for(:index_1).on(:product_code)[0].code.should == Braintree::ErrorCodes::TransactionLineItem::ProductCodeIsTooLong
      end

      it "handles validation error quantity format is invalid" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => "35.05",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :line_items => [
            {
              :quantity => "1.2322",
              :name => "Name #1",
              :kind => "debit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
            {
              :quantity => "1,2322",
              :name => "Name #2",
              :kind => "credit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
          ],
        )
        result.success?.should == false
        result.errors.for(:transaction).for(:line_items).for(:index_1).on(:quantity)[0].code.should == Braintree::ErrorCodes::TransactionLineItem::QuantityFormatIsInvalid
      end

      it "handles validation error quantity is required" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => "35.05",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :line_items => [
            {
              :quantity => "1.2322",
              :name => "Name #1",
              :kind => "debit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
            {
              :name => "Name #2",
              :kind => "credit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
          ],
        )
        result.success?.should == false
        result.errors.for(:transaction).for(:line_items).for(:index_1).on(:quantity)[0].code.should == Braintree::ErrorCodes::TransactionLineItem::QuantityIsRequired
      end

      it "handles validation error quantity is too large" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => "35.05",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :line_items => [
            {
              :quantity => "1.2322",
              :name => "Name #1",
              :kind => "debit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
            {
              :quantity => "2147483648",
              :name => "Name #2",
              :kind => "credit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
          ],
        )
        result.success?.should == false
        result.errors.for(:transaction).for(:line_items).for(:index_1).on(:quantity)[0].code.should == Braintree::ErrorCodes::TransactionLineItem::QuantityIsTooLarge
      end

      it "handles validation error total amount format is invalid" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => "35.05",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :line_items => [
            {
              :quantity => "1.2322",
              :name => "Name #1",
              :kind => "debit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
            {
              :quantity => "1.2322",
              :name => "Name #2",
              :kind => "credit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "$45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
          ],
        )
        result.success?.should == false
        result.errors.for(:transaction).for(:line_items).for(:index_1).on(:total_amount)[0].code.should == Braintree::ErrorCodes::TransactionLineItem::TotalAmountFormatIsInvalid
      end

      it "handles validation error total amount is required" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => "35.05",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :line_items => [
            {
              :quantity => "1.2322",
              :name => "Name #1",
              :kind => "debit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
            {
              :quantity => "1.2322",
              :name => "Name #2",
              :kind => "credit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
          ],
        )
        result.success?.should == false
        result.errors.for(:transaction).for(:line_items).for(:index_1).on(:total_amount)[0].code.should == Braintree::ErrorCodes::TransactionLineItem::TotalAmountIsRequired
      end

      it "handles validation error total amount is too large" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => "35.05",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :line_items => [
            {
              :quantity => "1.2322",
              :name => "Name #1",
              :kind => "debit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
            {
              :quantity => "1.2322",
              :name => "Name #2",
              :kind => "credit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "2147483648",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
          ],
        )
        result.success?.should == false
        result.errors.for(:transaction).for(:line_items).for(:index_1).on(:total_amount)[0].code.should == Braintree::ErrorCodes::TransactionLineItem::TotalAmountIsTooLarge
      end

      it "handles validation error total amount must be greater than zero" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => "35.05",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :line_items => [
            {
              :quantity => "1.2322",
              :name => "Name #1",
              :kind => "debit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
            {
              :quantity => "1.2322",
              :name => "Name #2",
              :kind => "credit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "-2",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
          ],
        )
        result.success?.should == false
        result.errors.for(:transaction).for(:line_items).for(:index_1).on(:total_amount)[0].code.should == Braintree::ErrorCodes::TransactionLineItem::TotalAmountMustBeGreaterThanZero
      end

      it "handles validation error unit amount format is invalid" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => "35.05",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :line_items => [
            {
              :quantity => "1.2322",
              :name => "Name #1",
              :kind => "debit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
            {
              :quantity => "1.2322",
              :name => "Name #2",
              :kind => "credit",
              :unit_amount => "45.01232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
          ],
        )
        result.success?.should == false
        result.errors.for(:transaction).for(:line_items).for(:index_1).on(:unit_amount)[0].code.should == Braintree::ErrorCodes::TransactionLineItem::UnitAmountFormatIsInvalid
      end

      it "handles validation error unit amount is required" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => "35.05",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :line_items => [
            {
              :quantity => "1.2322",
              :name => "Name #1",
              :kind => "debit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
            {
              :quantity => "1.2322",
              :name => "Name #2",
              :kind => "credit",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
          ],
        )
        result.success?.should == false
        result.errors.for(:transaction).for(:line_items).for(:index_1).on(:unit_amount)[0].code.should == Braintree::ErrorCodes::TransactionLineItem::UnitAmountIsRequired
      end

      it "handles validation error unit amount is too large" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => "35.05",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :line_items => [
            {
              :quantity => "1.2322",
              :name => "Name #1",
              :kind => "debit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
            {
              :quantity => "1.2322",
              :name => "Name #2",
              :kind => "credit",
              :unit_amount => "2147483648",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
          ],
        )
        result.success?.should == false
        result.errors.for(:transaction).for(:line_items).for(:index_1).on(:unit_amount)[0].code.should == Braintree::ErrorCodes::TransactionLineItem::UnitAmountIsTooLarge
      end

      it "handles validation error unit amount must be greater than zero" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => "35.05",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :line_items => [
            {
              :quantity => "1.2322",
              :name => "Name #1",
              :kind => "debit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
            {
              :quantity => "1.2322",
              :name => "Name #2",
              :kind => "credit",
              :unit_amount => "-2",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
          ],
        )
        result.success?.should == false
        result.errors.for(:transaction).for(:line_items).for(:index_1).on(:unit_amount)[0].code.should == Braintree::ErrorCodes::TransactionLineItem::UnitAmountMustBeGreaterThanZero
      end

      it "handles validation error unit of measure is too long" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => "35.05",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :line_items => [
            {
              :quantity => "1.2322",
              :name => "Name #1",
              :kind => "debit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
            {
              :quantity => "1.2322",
              :name => "Name #2",
              :kind => "credit",
              :unit_amount => "45.1232",
              :unit_of_measure => "1234567890123",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
          ],
        )
        result.success?.should == false
        result.errors.for(:transaction).for(:line_items).for(:index_1).on(:unit_of_measure)[0].code.should == Braintree::ErrorCodes::TransactionLineItem::UnitOfMeasureIsTooLong
      end

      it "handles validation error unit tax amount format is invalid" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => "35.05",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :line_items => [
            {
              :quantity => "1.2322",
              :name => "Name #1",
              :kind => "debit",
              :unit_amount => "45.1232",
              :unit_tax_amount => "2.34",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
            {
              :quantity => "1.2322",
              :name => "Name #2",
              :kind => "credit",
              :unit_amount => "45.0122",
              :unit_tax_amount => "2.012",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
          ],
        )
        result.success?.should == false
        result.errors.for(:transaction).for(:line_items).for(:index_1).on(:unit_tax_amount)[0].code.should == Braintree::ErrorCodes::TransactionLineItem::UnitTaxAmountFormatIsInvalid
      end

      it "handles validation error unit tax amount is too large" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => "35.05",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :line_items => [
            {
              :quantity => "1.2322",
              :name => "Name #1",
              :kind => "debit",
              :unit_amount => "45.1232",
              :unit_tax_amount => "1.23",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
            {
              :quantity => "1.2322",
              :name => "Name #2",
              :kind => "credit",
              :unit_amount => "45.0122",
              :unit_tax_amount => "2147483648",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
          ],
        )
        result.success?.should == false
        result.errors.for(:transaction).for(:line_items).for(:index_1).on(:unit_tax_amount)[0].code.should == Braintree::ErrorCodes::TransactionLineItem::UnitTaxAmountIsTooLarge
      end

      it "handles validation error unit tax amount cannot be negative" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => "35.05",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :line_items => [
            {
              :quantity => "1.2322",
              :name => "Name #1",
              :kind => "debit",
              :unit_amount => "45.1232",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
            {
              :quantity => "1.2322",
              :name => "Name #2",
              :kind => "credit",
              :unit_amount => "45.0122",
              :unit_tax_amount => "-1.23",
              :unit_of_measure => "gallon",
              :discount_amount => "1.02",
              :total_amount => "45.15",
              :product_code => "23434",
              :commodity_code => "9SAASSD8724",
            },
          ],
        )
        result.success?.should == false
        result.errors.for(:transaction).for(:line_items).for(:index_1).on(:unit_tax_amount)[0].code.should == Braintree::ErrorCodes::TransactionLineItem::UnitTaxAmountCannotBeNegative
      end

      it "handles validation errors on line items structure" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => "35.05",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :line_items => {
            :quantity => "2.02",
            :name => "Name #2",
            :kind => "credit",
            :unit_amount => "5",
            :unit_of_measure => "gallon",
            :total_amount => "10.1",
          },
        )
        result.success?.should == false
        result.errors.for(:transaction).on(:line_items)[0].code.should == Braintree::ErrorCodes::Transaction::LineItemsExpected
      end

      it "handles invalid arguments on line items structure" do
        expect do
          Braintree::Transaction.create(
            :type => "sale",
            :amount => "35.05",
            :payment_method_nonce => Braintree::Test::Nonce::Transactable,
            :line_items => [
              {
                :quantity => "2.02",
                :name => "Name #1",
                :kind => "credit",
                :unit_amount => "5",
                :unit_of_measure => "gallon",
                :total_amount => "10.1",
              },
              ["Name #2"],
              {
                :quantity => "2.02",
                :name => "Name #3",
                :kind => "credit",
                :unit_amount => "5",
                :unit_of_measure => "gallon",
                :total_amount => "10.1",
              },
            ],
          )
        end.to raise_error(ArgumentError)
      end

      it "handles validation errors on too many line items" do
        line_items = 250.times.map do |i|
          {
            :quantity => "2.02",
            :name => "Line item ##{i}",
            :kind => "credit",
            :unit_amount => "5",
            :unit_of_measure => "gallon",
            :total_amount => "10.1",
          }
        end
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => "35.05",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :line_items => line_items,
        )
        result.success?.should == false
        result.errors.for(:transaction).on(:line_items)[0].code.should == Braintree::ErrorCodes::Transaction::TooManyLineItems
      end
    end

    context "level 3 summary data" do
      it "accepts level 3 summary data" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :amount => "10.00",
          :shipping_amount => "1.00",
          :discount_amount => "2.00",
          :ships_from_postal_code => "12345",
        )

        result.success?.should == true
        result.transaction.shipping_amount.should == "1.00"
        result.transaction.discount_amount.should == "2.00"
        result.transaction.ships_from_postal_code.should == "12345"
      end

      it "handles validation errors on summary data" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :amount => "10.00",
          :shipping_amount => "1a00",
          :discount_amount => "-2.00",
          :ships_from_postal_code => "1$345",
        )

        result.success?.should == false
        result.errors.for(:transaction).on(:shipping_amount)[0].code.should == Braintree::ErrorCodes::Transaction::ShippingAmountFormatIsInvalid
        result.errors.for(:transaction).on(:discount_amount)[0].code.should == Braintree::ErrorCodes::Transaction::DiscountAmountCannotBeNegative
        result.errors.for(:transaction).on(:ships_from_postal_code)[0].code.should == Braintree::ErrorCodes::Transaction::ShipsFromPostalCodeInvalidCharacters
      end

      it "handles validation error discount amount format is invalid" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :amount => "10.00",
          :discount_amount => "2.001",
        )

        result.success?.should == false
        result.errors.for(:transaction).on(:discount_amount)[0].code.should == Braintree::ErrorCodes::Transaction::DiscountAmountFormatIsInvalid
      end

      it "handles validation error discount amount cannot be negative" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :amount => "10.00",
          :discount_amount => "-2",
        )

        result.success?.should == false
        result.errors.for(:transaction).on(:discount_amount)[0].code.should == Braintree::ErrorCodes::Transaction::DiscountAmountCannotBeNegative
      end

      it "handles validation error discount amount is too large" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :amount => "10.00",
          :discount_amount => "2147483648",
        )

        result.success?.should == false
        result.errors.for(:transaction).on(:discount_amount)[0].code.should == Braintree::ErrorCodes::Transaction::DiscountAmountIsTooLarge
      end

      it "handles validation error shipping amount format is invalid" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :amount => "10.00",
          :shipping_amount => "2.001",
        )

        result.success?.should == false
        result.errors.for(:transaction).on(:shipping_amount)[0].code.should == Braintree::ErrorCodes::Transaction::ShippingAmountFormatIsInvalid
      end

      it "handles validation error shipping amount cannot be negative" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :amount => "10.00",
          :shipping_amount => "-2",
        )

        result.success?.should == false
        result.errors.for(:transaction).on(:shipping_amount)[0].code.should == Braintree::ErrorCodes::Transaction::ShippingAmountCannotBeNegative
      end

      it "handles validation error shipping amount is too large" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :amount => "10.00",
          :shipping_amount => "2147483648",
        )

        result.success?.should == false
        result.errors.for(:transaction).on(:shipping_amount)[0].code.should == Braintree::ErrorCodes::Transaction::ShippingAmountIsTooLarge
      end

      it "handles validation error ships from postal code is too long" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :amount => "10.00",
          :ships_from_postal_code => "1234567890",
        )

        result.success?.should == false
        result.errors.for(:transaction).on(:ships_from_postal_code)[0].code.should == Braintree::ErrorCodes::Transaction::ShipsFromPostalCodeIsTooLong
      end

      it "handles validation error ships from postal code invalid characters" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :payment_method_nonce => Braintree::Test::Nonce::Transactable,
          :amount => "10.00",
          :ships_from_postal_code => "12345%78",
        )

        result.success?.should == false
        result.errors.for(:transaction).on(:ships_from_postal_code)[0].code.should == Braintree::ErrorCodes::Transaction::ShipsFromPostalCodeInvalidCharacters
      end
    end

    context "network_transaction_id" do
      it "receives network_transaction_id for visa transaction" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "05/2009"
          },
          :amount => "10.00",
        )
        result.success?.should == true
        result.transaction.network_transaction_id.should_not be_nil
      end
    end

    context "external vault" do
      it "returns a validation error if used with an unsupported instrument type" do
        customer = Braintree::Customer.create!
        result = Braintree::PaymentMethod.create(
          :payment_method_nonce => Braintree::Test::Nonce::PayPalBillingAgreement,
          :customer_id => customer.id,
        )
        payment_method_token = result.payment_method.token

        result = Braintree::Transaction.create(
          :type => "sale",
          :customer_id => customer.id,
          :payment_method_token => payment_method_token,
          :external_vault => {
            :status => Braintree::Transaction::ExternalVault::Status::WillVault,
          },
          :amount => "10.00",
        )
        result.success?.should == false
        result.errors.for(:transaction)[0].code.should == Braintree::ErrorCodes::Transaction::PaymentInstrumentWithExternalVaultIsInvalid
      end

      it "reject invalid status" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::MasterCard,
            :expiration_date => "05/2009"
          },
          :external_vault => {
            :status => "not_valid",
          },
          :amount => "10.00",
        )
        result.success?.should == false
        result.errors.for(:transaction).for(:external_vault).on(:status)[0].code.should == Braintree::ErrorCodes::Transaction::ExternalVault::StatusIsInvalid
      end

      context "Visa/Mastercard/Discover/AmEx" do
        it "accepts status" do
          result = Braintree::Transaction.create(
            :type => "sale",
            :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::Visa,
              :expiration_date => "05/2009"
            },
            :external_vault => {
              :status => Braintree::Transaction::ExternalVault::Status::WillVault,
            },
            :amount => "10.00",
          )
          result.success?.should == true
          result.transaction.network_transaction_id.should_not be_nil
        end

        it "accepts previous_network_transaction_id" do
          result = Braintree::Transaction.create(
            :type => "sale",
            :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::MasterCard,
              :expiration_date => "05/2009"
            },
            :external_vault => {
              :status => Braintree::Transaction::ExternalVault::Status::Vaulted,
              :previous_network_transaction_id => "123456789012345",
            },
            :amount => "10.00",
          )
          result.success?.should == true
          result.transaction.network_transaction_id.should_not be_nil
        end

        it "rejects non-vaulted status with previous_network_transaction_id" do
          result = Braintree::Transaction.create(
            :type => "sale",
            :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::Discover,
              :expiration_date => "05/2009"
            },
            :external_vault => {
              :status => Braintree::Transaction::ExternalVault::Status::WillVault,
              :previous_network_transaction_id => "123456789012345",
            },
            :amount => "10.00",
          )
          result.success?.should == false
          result.errors.for(:transaction).for(:external_vault).on(:status)[0].code.should == Braintree::ErrorCodes::Transaction::ExternalVault::StatusWithPreviousNetworkTransactionIdIsInvalid
        end
      end

      context "Non-(Visa/Mastercard/Discover/AmEx) card types" do
        it "accepts status" do
          result = Braintree::Transaction.create(
            :type => "sale",
            :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::JCBs[0],
              :expiration_date => "05/2009"
            },
            :external_vault => {
              :status => Braintree::Transaction::ExternalVault::Status::WillVault,
            },
            :amount => "10.00",
          )
          result.success?.should == true
          result.transaction.network_transaction_id.should_not be_nil
        end

        it "accepts blank previous_network_transaction_id" do
          result = Braintree::Transaction.create(
            :type => "sale",
            :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::JCBs[0],
              :expiration_date => "05/2009"
            },
            :external_vault => {
              :status => Braintree::Transaction::ExternalVault::Status::Vaulted,
              :previous_network_transaction_id => "",
            },
            :amount => "10.00",
          )
          result.success?.should == true
          result.transaction.network_transaction_id.should_not be_nil
        end
      end
    end

    context "account_type" do
      it "creates a Hiper transaction with account type credit" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :merchant_account_id => SpecHelper::HiperBRLMerchantAccountId,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Hiper,
            :expiration_date => "05/2009"
          },
          :amount => "10.00",
          :options => {
            :credit_card => {
              :account_type => "credit",
            }
          },
        )
        result.success?.should == true
        result.transaction.credit_card_details.account_type.should == "credit"
      end

      it "creates a Hipercard transaction with account_type credit" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :merchant_account_id => SpecHelper::HiperBRLMerchantAccountId,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Hipercard,
            :expiration_date => "05/2009"
          },
          :amount => "10.00",
          :options => {
            :credit_card => {
              :account_type => "credit",
            }
          },
        )
        result.success?.should == true
        result.transaction.credit_card_details.account_type.should == "credit"
      end

      it "creates a Hiper transaction with account_type debit" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :merchant_account_id => SpecHelper::HiperBRLMerchantAccountId,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Hiper,
            :expiration_date => "05/2009"
          },
          :amount => "10.00",
          :options => {
            :credit_card => {
              :account_type => "debit",
            },
            :submit_for_settlement => true,
          },
        )
        result.success?.should == true
        result.transaction.credit_card_details.account_type.should == "debit"
      end

      it "does not allow auths with account_type debit" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :merchant_account_id => SpecHelper::HiperBRLMerchantAccountId,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Hiper,
            :expiration_date => "05/2009"
          },
          :amount => "10.00",
          :options => {
            :credit_card => {
              :account_type => "debit",
            },
          },
        )
        result.success?.should == false
        result.errors.for(:transaction).for(:options).for(:credit_card).on(:account_type)[0].code.should == Braintree::ErrorCodes::Transaction::Options::CreditCard::AccountTypeDebitDoesNotSupportAuths
      end

      it "does not allow invalid account_type" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :merchant_account_id => SpecHelper::HiperBRLMerchantAccountId,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Hiper,
            :expiration_date => "05/2009"
          },
          :amount => "10.00",
          :options => {
            :credit_card => {
              :account_type => "ach",
            },
          },
        )
        result.success?.should == false
        result.errors.for(:transaction).for(:options).for(:credit_card).on(:account_type)[0].code.should == Braintree::ErrorCodes::Transaction::Options::CreditCard::AccountTypeIsInvalid
      end

      it "does not allow account_type not supported by merchant" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "05/2009"
          },
          :amount => "10.00",
          :options => {
            :credit_card => {
              :account_type => "credit",
            },
          },
        )
        result.success?.should == false
        result.errors.for(:transaction).for(:options).for(:credit_card).on(:account_type)[0].code.should == Braintree::ErrorCodes::Transaction::Options::CreditCard::AccountTypeNotSupported
      end
    end
  end

  describe "self.create!" do
    it "returns the transaction if valid" do
      transaction = Braintree::Transaction.create!(
        :type => "sale",
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
      )
      transaction.id.should =~ /^\w{6,}$/
      transaction.type.should == "sale"
      transaction.amount.should == BigDecimal(Braintree::Test::TransactionAmounts::Authorize)
      transaction.credit_card_details.bin.should == Braintree::Test::CreditCardNumbers::Visa[0, 6]
      transaction.credit_card_details.last_4.should == Braintree::Test::CreditCardNumbers::Visa[-4..-1]
      transaction.credit_card_details.expiration_date.should == "05/2009"
    end

    it "raises a validationsfailed if invalid" do
      expect do
        Braintree::Transaction.create!(
          :type => "sale",
          :amount => nil,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "05/2009"
          },
        )
      end.to raise_error(Braintree::ValidationsFailed)
    end
  end

  describe "self.refund" do
    context "partial refunds" do
      it "allows partial refunds" do
        transaction = create_transaction_to_refund
        result = Braintree::Transaction.refund(transaction.id, transaction.amount / 2)
        result.success?.should == true
        result.transaction.type.should == "credit"
      end

      it "allows multiple partial refunds" do
        transaction = create_transaction_to_refund
        transaction_1 = Braintree::Transaction.refund(transaction.id, transaction.amount / 2).transaction
        transaction_2 = Braintree::Transaction.refund(transaction.id, transaction.amount / 2).transaction

        transaction = Braintree::Transaction.find(transaction.id)
        transaction.refund_ids.sort.should == [transaction_1.id, transaction_2.id].sort
      end
    end

    it "returns a successful result if successful" do
      transaction = create_transaction_to_refund
      transaction.status.should == Braintree::Transaction::Status::Settled
      result = Braintree::Transaction.refund(transaction.id)
      result.success?.should == true
      result.transaction.type.should == "credit"
    end

    it "assigns the refunded_transaction_id to the original transaction" do
      transaction = create_transaction_to_refund
      refund_transaction = Braintree::Transaction.refund(transaction.id).transaction

      refund_transaction.refunded_transaction_id.should == transaction.id
    end

    it "returns an error if already refunded" do
      transaction = create_transaction_to_refund
      result = Braintree::Transaction.refund(transaction.id)
      result.success?.should == true
      result = Braintree::Transaction.refund(transaction.id)
      result.success?.should == false
      result.errors.for(:transaction).on(:base)[0].code.should == Braintree::ErrorCodes::Transaction::HasAlreadyBeenRefunded
    end

    it "returns an error result if unsettled" do
      transaction = Braintree::Transaction.create!(
        :type => "sale",
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
      )
      result = Braintree::Transaction.refund(transaction.id)
      result.success?.should == false
      result.errors.for(:transaction).on(:base)[0].code.should == Braintree::ErrorCodes::Transaction::CannotRefundUnlessSettled
    end
  end

  describe "self.refund!" do
    it "returns the refund if valid refund" do
      transaction = create_transaction_to_refund

      refund_transaction = Braintree::Transaction.refund!(transaction.id)

      refund_transaction.refunded_transaction_id.should == transaction.id
      refund_transaction.type.should == "credit"
      transaction.amount.should == refund_transaction.amount
    end

    it "raises a ValidationsFailed if invalid" do
      transaction = create_transaction_to_refund
      invalid_refund_amount = transaction.amount + 1
      invalid_refund_amount.should be > transaction.amount

      expect do
        Braintree::Transaction.refund!(transaction.id,invalid_refund_amount)
      end.to raise_error(Braintree::ValidationsFailed)
    end
  end

  describe "self.sale" do
    it "returns a successful result with type=sale if successful" do
      result = Braintree::Transaction.sale(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
      )
      result.success?.should == true
      result.transaction.id.should =~ /^\w{6,}$/
      result.transaction.type.should == "sale"
      result.transaction.amount.should == BigDecimal(Braintree::Test::TransactionAmounts::Authorize)
      result.transaction.credit_card_details.bin.should == Braintree::Test::CreditCardNumbers::Visa[0, 6]
      result.transaction.credit_card_details.last_4.should == Braintree::Test::CreditCardNumbers::Visa[-4..-1]
      result.transaction.credit_card_details.expiration_date.should == "05/2009"
    end

    it "works when given all attributes" do
      result = Braintree::Transaction.sale(
        :amount => "100.00",
        :order_id => "123",
        :product_sku => "productsku01",
        :channel => "MyShoppingCartProvider",
        :credit_card => {
          :cardholder_name => "The Cardholder",
          :number => "5105105105105100",
          :expiration_date => "05/2011",
          :cvv => "123"
        },
        :customer => {
          :first_name => "Dan",
          :last_name => "Smith",
          :company => "Braintree",
          :email => "dan@example.com",
          :phone => "419-555-1234",
          :fax => "419-555-1235",
          :website => "http://braintreepayments.com"
        },
        :billing => {
          :first_name => "Carl",
          :last_name => "Jones",
          :company => "Braintree",
          :street_address => "123 E Main St",
          :extended_address => "Suite 403",
          :locality => "Chicago",
          :region => "IL",
          :phone_number => "122-555-1237",
          :postal_code => "60622",
          :country_name => "United States of America"
        },
        :shipping => {
          :first_name => "Andrew",
          :last_name => "Mason",
          :company => "Braintree",
          :street_address => "456 W Main St",
          :extended_address => "Apt 2F",
          :locality => "Bartlett",
          :region => "IL",
          :phone_number => "122-555-1236",
          :postal_code => "60103",
          :country_name => "United States of America",
          :shipping_method => Braintree::Transaction::AddressDetails::ShippingMethod::Electronic
        },
      )
      result.success?.should == true
      transaction = result.transaction
      transaction.id.should =~ /\A\w{6,}\z/
      transaction.type.should == "sale"
      transaction.status.should == Braintree::Transaction::Status::Authorized
      transaction.amount.should == BigDecimal("100.00")
      transaction.currency_iso_code.should == "USD"
      transaction.order_id.should == "123"
      transaction.channel.should == "MyShoppingCartProvider"
      transaction.processor_response_code.should == "1000"
      transaction.authorization_expires_at.between?(Time.now, Time.now + (60 * 60 * 24 * 60)).should == true
      transaction.created_at.between?(Time.now - 60, Time.now).should == true
      transaction.updated_at.between?(Time.now - 60, Time.now).should == true
      transaction.credit_card_details.bin.should == "510510"
      transaction.credit_card_details.cardholder_name.should == "The Cardholder"
      transaction.credit_card_details.last_4.should == "5100"
      transaction.credit_card_details.masked_number.should == "510510******5100"
      transaction.credit_card_details.card_type.should == "MasterCard"
      transaction.avs_error_response_code.should == nil
      transaction.avs_postal_code_response_code.should == "M"
      transaction.avs_street_address_response_code.should == "M"
      transaction.cvv_response_code.should == "M"
      transaction.customer_details.first_name.should == "Dan"
      transaction.customer_details.last_name.should == "Smith"
      transaction.customer_details.company.should == "Braintree"
      transaction.customer_details.email.should == "dan@example.com"
      transaction.customer_details.phone.should == "419-555-1234"
      transaction.customer_details.fax.should == "419-555-1235"
      transaction.customer_details.website.should == "http://braintreepayments.com"
      transaction.billing_details.first_name.should == "Carl"
      transaction.billing_details.last_name.should == "Jones"
      transaction.billing_details.company.should == "Braintree"
      transaction.billing_details.street_address.should == "123 E Main St"
      transaction.billing_details.extended_address.should == "Suite 403"
      transaction.billing_details.locality.should == "Chicago"
      transaction.billing_details.region.should == "IL"
      transaction.billing_details.postal_code.should == "60622"
      transaction.billing_details.country_name.should == "United States of America"
      transaction.billing_details.country_code_alpha2.should == "US"
      transaction.billing_details.country_code_alpha3.should == "USA"
      transaction.billing_details.country_code_numeric.should == "840"
      transaction.shipping_details.first_name.should == "Andrew"
      transaction.shipping_details.last_name.should == "Mason"
      transaction.shipping_details.company.should == "Braintree"
      transaction.shipping_details.street_address.should == "456 W Main St"
      transaction.shipping_details.extended_address.should == "Apt 2F"
      transaction.shipping_details.locality.should == "Bartlett"
      transaction.shipping_details.region.should == "IL"
      transaction.shipping_details.postal_code.should == "60103"
      transaction.shipping_details.country_name.should == "United States of America"
      transaction.shipping_details.country_code_alpha2.should == "US"
      transaction.shipping_details.country_code_alpha3.should == "USA"
      transaction.shipping_details.country_code_numeric.should == "840"
    end

    it "allows merchant account to be specified" do
      result = Braintree::Transaction.sale(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :merchant_account_id => SpecHelper::NonDefaultMerchantAccountId,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
      )
      result.success?.should == true
      result.transaction.merchant_account_id.should == SpecHelper::NonDefaultMerchantAccountId
    end

    it "uses default merchant account when it is not specified" do
      result = Braintree::Transaction.sale(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
      )
      result.success?.should == true
      result.transaction.merchant_account_id.should == SpecHelper::DefaultMerchantAccountId
    end

    it "can store customer and credit card in the vault" do
      result = Braintree::Transaction.sale(
        :amount => "100",
        :customer => {
          :first_name => "Adam",
          :last_name => "Williams"
        },
        :credit_card => {
          :number => "5105105105105100",
          :expiration_date => "05/2012"
        },
        :options => {
          :store_in_vault => true
        },
      )
      result.success?.should == true
      transaction = result.transaction
      transaction.customer_details.id.should =~ /\A\d{6,}\z/
      transaction.vault_customer.id.should == transaction.customer_details.id
      transaction.credit_card_details.token.should =~ /\A\w{4,}\z/
      transaction.vault_credit_card.token.should == transaction.credit_card_details.token
    end

    it "associates a billing address with a credit card in the vault" do
      result = Braintree::Transaction.sale(
        :amount => "100",
        :customer => {
          :first_name => "Adam",
          :last_name => "Williams"
        },
        :credit_card => {
          :number => "5105105105105100",
          :expiration_date => "05/2012"
        },
        :billing => {
          :first_name => "Carl",
          :last_name => "Jones",
          :company => "Braintree",
          :street_address => "123 E Main St",
          :extended_address => "Suite 403",
          :locality => "Chicago",
          :region => "IL",
          :postal_code => "60622",
          :country_name => "United States of America"
        },
        :options => {
          :store_in_vault => true,
          :add_billing_address_to_payment_method => true,
        },
      )
      result.success?.should == true
      transaction = result.transaction
      transaction.customer_details.id.should =~ /\A\d{6,}\z/
      transaction.vault_customer.id.should == transaction.customer_details.id
      credit_card = Braintree::CreditCard.find(transaction.vault_credit_card.token)
      transaction.billing_details.id.should == credit_card.billing_address.id
      transaction.vault_billing_address.id.should == credit_card.billing_address.id
      credit_card.billing_address.first_name.should == "Carl"
      credit_card.billing_address.last_name.should == "Jones"
      credit_card.billing_address.company.should == "Braintree"
      credit_card.billing_address.street_address.should == "123 E Main St"
      credit_card.billing_address.extended_address.should == "Suite 403"
      credit_card.billing_address.locality.should == "Chicago"
      credit_card.billing_address.region.should == "IL"
      credit_card.billing_address.postal_code.should == "60622"
      credit_card.billing_address.country_name.should == "United States of America"
    end

    it "can store the shipping address in the vault" do
      result = Braintree::Transaction.sale(
        :amount => "100",
        :customer => {
          :first_name => "Adam",
          :last_name => "Williams"
        },
        :credit_card => {
          :number => "5105105105105100",
          :expiration_date => "05/2012"
        },
        :shipping => {
          :first_name => "Carl",
          :last_name => "Jones",
          :company => "Braintree",
          :street_address => "123 E Main St",
          :extended_address => "Suite 403",
          :locality => "Chicago",
          :region => "IL",
          :postal_code => "60622",
          :country_name => "United States of America"
        },
        :options => {
          :store_in_vault => true,
          :store_shipping_address_in_vault => true,
        },
      )
      result.success?.should == true
      transaction = result.transaction
      transaction.customer_details.id.should =~ /\A\d{6,}\z/
      transaction.vault_customer.id.should == transaction.customer_details.id
      transaction.vault_shipping_address.id.should == transaction.vault_customer.addresses[0].id
      shipping_address = transaction.vault_customer.addresses[0]
      shipping_address.first_name.should == "Carl"
      shipping_address.last_name.should == "Jones"
      shipping_address.company.should == "Braintree"
      shipping_address.street_address.should == "123 E Main St"
      shipping_address.extended_address.should == "Suite 403"
      shipping_address.locality.should == "Chicago"
      shipping_address.region.should == "IL"
      shipping_address.postal_code.should == "60622"
      shipping_address.country_name.should == "United States of America"
    end

    it "stores a unique number identifier in the vault" do
      result = Braintree::Transaction.sale(
        :amount => "100",
        :credit_card => {
          :number => "5105105105105100",
          :expiration_date => "05/2012"
        },
        :options => {:store_in_vault => true},
      )

      result.success?.should == true

      transaction = result.transaction
      transaction.credit_card_details.unique_number_identifier.should_not be_nil
    end

    it "submits for settlement if given transaction[options][submit_for_settlement]" do
      result = Braintree::Transaction.sale(
        :amount => "100",
        :credit_card => {
          :number => "5105105105105100",
          :expiration_date => "05/2012"
        },
        :options => {
          :submit_for_settlement => true
        },
      )
      result.success?.should == true
      result.transaction.status.should == Braintree::Transaction::Status::SubmittedForSettlement
    end

    it "can specify the customer id and payment method token" do
      customer_id = "customer_#{rand(10**10)}"
      payment_method_token = "credit_card_#{rand(10**10)}"
      result = Braintree::Transaction.sale(
        :amount => "100",
        :customer => {
          :id => customer_id,
          :first_name => "Adam",
          :last_name => "Williams"
        },
        :credit_card => {
          :token => payment_method_token,
          :number => "5105105105105100",
          :expiration_date => "05/2012"
        },
        :options => {
          :store_in_vault => true
        },
      )
      result.success?.should == true
      transaction = result.transaction
      transaction.customer_details.id.should == customer_id
      transaction.vault_customer.id.should == customer_id
      transaction.credit_card_details.token.should == payment_method_token
      transaction.vault_credit_card.token.should == payment_method_token
    end

    it "can specify existing shipping address" do
      customer = Braintree::Customer.create!(
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2010"
        },
      )
      address = Braintree::Address.create!(
        :customer_id => customer.id,
        :street_address => "123 Fake St.",
      )
      result = Braintree::Transaction.sale(
        :amount => "100",
        :customer_id => customer.id,
        :shipping_address_id => address.id,
      )
      result.success?.should == true
      transaction = result.transaction
      transaction.shipping_details.street_address.should == "123 Fake St."
      transaction.customer_details.id.should == customer.id
      transaction.shipping_details.id.should == address.id
    end

    it "returns an error result if validations fail" do
      params = {
        :transaction => {
          :amount => nil,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "05/2009"
          }
        }
      }
      result = Braintree::Transaction.sale(params[:transaction])
      result.success?.should == false
      result.params.should == {:transaction => {:type => "sale", :amount => nil, :credit_card => {:expiration_date => "05/2009"}}}
      result.errors.for(:transaction).on(:amount)[0].code.should == Braintree::ErrorCodes::Transaction::AmountIsRequired
    end

    it "validates currency_iso_code and creates transaction" do
      params = {
        :transaction => {
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :currency_iso_code => "USD",
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "05/2009"
          }
        }
      }
      result = Braintree::Transaction.sale(params[:transaction])
      result.success?.should == true
      result.transaction.currency_iso_code == "USD"
    end

    it "validates currency_iso_code and returns error" do
      params = {
        :transaction => {
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :currency_iso_code => "CAD",
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "05/2009"
          }
        }
      }
      result = Braintree::Transaction.sale(params[:transaction])
      result.success?.should == false
      result.errors.for(:transaction).on(:currency_iso_code)[0].code.should == Braintree::ErrorCodes::Transaction::CurrencyCodeNotSupportedByMerchantAccount
    end

    it "validates currency_iso_code and creates transaction with specified merchant account" do
      params = {
        :transaction => {
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :merchant_account_id => SpecHelper::NonDefaultMerchantAccountId,
          :currency_iso_code => "USD",
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "05/2009"
          }
        }
      }
      result = Braintree::Transaction.sale(params[:transaction])
      result.success?.should == true
      result.transaction.currency_iso_code == "USD"
      result.transaction.merchant_account_id == SpecHelper::NonDefaultMerchantAccountId
    end

    it "validates currency_iso_code and returns error with specified merchant account" do
      params = {
        :transaction => {
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :merchant_account_id => SpecHelper::NonDefaultMerchantAccountId,
          :currency_iso_code => "CAD",
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "05/2009"
          }
        }
      }
      result = Braintree::Transaction.sale(params[:transaction])
      result.success?.should == false
      result.errors.for(:transaction).on(:currency_iso_code)[0].code.should == Braintree::ErrorCodes::Transaction::CurrencyCodeNotSupportedByMerchantAccount
    end

    it "validates tax_amount for Aib domestic sweden transaction and returns error" do
      params = {
          :transaction => {
              :amount => Braintree::Test::TransactionAmounts::Authorize,
              :merchant_account_id => SpecHelper::AibSwedenMaMerchantAccountId,
              :credit_card => {
                  :number => Braintree::Test::CreditCardNumbers::Visa,
                  :expiration_date => "05/2030"
              }
          }
      }
      result = Braintree::Transaction.sale(params[:transaction])
      result.success?.should == false
      result.errors.for(:transaction).on(:tax_amount)[0].code.should == Braintree::ErrorCodes::Transaction::TaxAmountIsRequiredForAibSwedish
    end

    it "skips advanced fraud checking if transaction[options][skip_advanced_fraud_checking] is set to true" do
      with_advanced_fraud_kount_integration_merchant do
        result = Braintree::Transaction.sale(
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "05/2009"
          },
          :options => {
            :skip_advanced_fraud_checking => true
          },
        )
        result.success?.should == true
        result.transaction.risk_data.should be_nil
      end
    end

    it "works with Apple Pay params" do
      params = {
        :amount => "3.12",
        :apple_pay_card => {
          :number => "370295001292109",
          :cardholder_name => "JANE SMITH",
          :cryptogram => "AAAAAAAA/COBt84dnIEcwAA3gAAGhgEDoLABAAhAgAABAAAALnNCLw==",
          :expiration_month => "10",
          :expiration_year => "14",
          :eci_indicator => "07",
        }
      }
      result = Braintree::Transaction.sale(params)
      result.success?.should == true
      result.transaction.status.should == Braintree::Transaction::Status::Authorized
    end

    context "Google Pay params" do
      it "works with full params" do
        params = {
          :amount => "3.12",
          :google_pay_card => {
            :number => "4012888888881881",
            :cryptogram => "AAAAAAAA/COBt84dnIEcwAA3gAAGhgEDoLABAAhAgAABAAAALnNCLw==",
            :google_transaction_id => "25469d622c1dd37cb1a403c6d438e850",
            :expiration_month => "10",
            :expiration_year => "14",
            :source_card_type => "Visa",
            :source_card_last_four => "1111",
            :eci_indicator => "05",
          }
        }
        result = Braintree::Transaction.sale(params)
        result.success?.should == true
        result.transaction.status.should == Braintree::Transaction::Status::Authorized
      end

      it "works with only number, cryptogram, expiration and transaction ID (network tokenized card)" do
        params = {
          :amount => "3.12",
          :google_pay_card => {
            :number => "4012888888881881",
            :cryptogram => "AAAAAAAA/COBt84dnIEcwAA3gAAGhgEDoLABAAhAgAABAAAALnNCLw==",
            :google_transaction_id => "25469d622c1dd37cb1a403c6d438e850",
            :expiration_month => "10",
            :expiration_year => "14",
          }
        }
        result = Braintree::Transaction.sale(params)
        result.success?.should == true
        result.transaction.status.should == Braintree::Transaction::Status::Authorized
      end

      it "works with only number, expiration and transaction ID (non-tokenized card)" do
        params = {
          :amount => "3.12",
          :google_pay_card => {
            :number => "4012888888881881",
            :google_transaction_id => "25469d622c1dd37cb1a403c6d438e850",
            :expiration_month => "10",
            :expiration_year => "14",
          }
        }
        result = Braintree::Transaction.sale(params)
        result.success?.should == true
        result.transaction.status.should == Braintree::Transaction::Status::Authorized
      end
    end


    context "3rd party Card on File Network Token" do
      it "Works with all params" do
        params = {
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "05/2009",
            :network_tokenization_attributes => {
              :cryptogram => "/wAAAAAAAcb8AlGUF/1JQEkAAAA=",
              :ecommerce_indicator => "05",
              :token_requestor_id => "45310020105"
            },
          }
        }
        result = Braintree::Transaction.sale(params)
        expect(result.success?).to eq true
        expect(result.transaction.status).to eq Braintree::Transaction::Status::Authorized
        expect(result.transaction.processed_with_network_token?).to eq true

        network_token_details = result.transaction.network_token_details
        expect(network_token_details.is_network_tokenized?).to eq true
      end

      it "returns errors if validations on cryptogram fails" do
        params = {
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "05/2009",
            :network_tokenization_attributes => {
              :ecommerce_indicator => "05",
              :token_requestor_id => "45310020105"
            },
          }
        }
        result = Braintree::Transaction.sale(params)

        expect(result.success?).to eq(false)
        expect(result.errors.for(:transaction).for(:credit_card).map { |e| e.code }.sort).to eq [Braintree::ErrorCodes::CreditCard::NetworkTokenizationAttributeCryptogramIsRequired]
      end
    end

    xit "Amex Pay with Points" do
      context "transaction creation" do
        it "succeeds when submit_for_settlement is true" do
          result = Braintree::Transaction.sale(
            :amount => Braintree::Test::TransactionAmounts::Authorize,
            :merchant_account_id => SpecHelper::FakeAmexDirectMerchantAccountId,
            :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::AmexPayWithPoints::Success,
              :expiration_date => "05/2009"
            },
            :options => {
              :submit_for_settlement => true,
              :amex_rewards => {
                :request_id => "ABC123",
                :points => "1000",
                :currency_amount => "10.00",
                :currency_iso_code => "USD"
              }
            },
          )
          result.success?.should == true
          result.transaction.status.should == Braintree::Transaction::Status::SubmittedForSettlement
        end

        it "succeeds even if the card is ineligible" do
          result = Braintree::Transaction.sale(
            :amount => Braintree::Test::TransactionAmounts::Authorize,
            :merchant_account_id => SpecHelper::FakeAmexDirectMerchantAccountId,
            :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::AmexPayWithPoints::IneligibleCard,
              :expiration_date => "05/2009"
            },
            :options => {
              :submit_for_settlement => true,
              :amex_rewards => {
                :request_id => "ABC123",
                :points => "1000",
                :currency_amount => "10.00",
                :currency_iso_code => "USD"
              }
            },
          )
          result.success?.should == true
          result.transaction.status.should == Braintree::Transaction::Status::SubmittedForSettlement
        end

        it "succeeds even if the card's balance is insufficient" do
          result = Braintree::Transaction.sale(
            :amount => Braintree::Test::TransactionAmounts::Authorize,
            :merchant_account_id => SpecHelper::FakeAmexDirectMerchantAccountId,
            :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::AmexPayWithPoints::InsufficientPoints,
              :expiration_date => "05/2009"
            },
            :options => {
              :submit_for_settlement => true,
              :amex_rewards => {
                :request_id => "ABC123",
                :points => "1000",
                :currency_amount => "10.00",
                :currency_iso_code => "USD"
              }
            },
          )
          result.success?.should == true
          result.transaction.status.should == Braintree::Transaction::Status::SubmittedForSettlement
        end
      end

      context "submit for settlement" do
        it "succeeds" do
          result = Braintree::Transaction.sale(
            :amount => Braintree::Test::TransactionAmounts::Authorize,
            :merchant_account_id => SpecHelper::FakeAmexDirectMerchantAccountId,
            :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::AmexPayWithPoints::Success,
              :expiration_date => "05/2009"
            },
            :options => {
              :amex_rewards => {
                :request_id => "ABC123",
                :points => "1000",
                :currency_amount => "10.00",
                :currency_iso_code => "USD"
              }
            },
          )
          result.success?.should == true

          result = Braintree::Transaction.submit_for_settlement(result.transaction.id)
          result.success?.should == true
          result.transaction.status.should == Braintree::Transaction::Status::SubmittedForSettlement
        end

        it "succeeds even if the card is ineligible" do
          result = Braintree::Transaction.sale(
            :amount => Braintree::Test::TransactionAmounts::Authorize,
            :merchant_account_id => SpecHelper::FakeAmexDirectMerchantAccountId,
            :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::AmexPayWithPoints::IneligibleCard,
              :expiration_date => "05/2009"
            },
            :options => {
              :amex_rewards => {
                :request_id => "ABC123",
                :points => "1000",
                :currency_amount => "10.00",
                :currency_iso_code => "USD"
              }
            },
          )
          result.success?.should == true
          result.transaction.status.should == Braintree::Transaction::Status::Authorized

          result = Braintree::Transaction.submit_for_settlement(result.transaction.id)
          result.success?.should == true
          result.transaction.status.should == Braintree::Transaction::Status::SubmittedForSettlement
        end

        it "succeeds even if the card's balance is insufficient" do
          result = Braintree::Transaction.sale(
            :amount => Braintree::Test::TransactionAmounts::Authorize,
            :merchant_account_id => SpecHelper::FakeAmexDirectMerchantAccountId,
            :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::AmexPayWithPoints::IneligibleCard,
              :expiration_date => "05/2009"
            },
            :options => {
              :amex_rewards => {
                :request_id => "ABC123",
                :points => "1000",
                :currency_amount => "10.00",
                :currency_iso_code => "USD"
              }
            },
          )
          result.success?.should == true
          result.transaction.status.should == Braintree::Transaction::Status::Authorized

          result = Braintree::Transaction.submit_for_settlement(result.transaction.id)
          result.success?.should == true
          result.transaction.status.should == Braintree::Transaction::Status::SubmittedForSettlement
        end
      end
    end
  end

  describe "self.sale!" do
    it "returns the transaction if valid" do
      transaction = Braintree::Transaction.sale!(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
      )
      transaction.id.should =~ /^\w{6,}$/
      transaction.type.should == "sale"
      transaction.amount.should == BigDecimal(Braintree::Test::TransactionAmounts::Authorize)
      transaction.credit_card_details.bin.should == Braintree::Test::CreditCardNumbers::Visa[0, 6]
      transaction.credit_card_details.last_4.should == Braintree::Test::CreditCardNumbers::Visa[-4..-1]
      transaction.credit_card_details.expiration_date.should == "05/2009"
    end

    it "raises a ValidationsFailed if invalid" do
      expect do
        Braintree::Transaction.sale!(
          :amount => nil,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "05/2009"
          },
        )
      end.to raise_error(Braintree::ValidationsFailed)
    end
  end

  describe "self.submit_for_settlement" do
    it "returns a successful result if successful" do
      transaction = Braintree::Transaction.sale!(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "06/2009"
        },
      )
      result = Braintree::Transaction.submit_for_settlement(transaction.id)
      result.success?.should == true
    end

    it "can submit a specific amount for settlement" do
      transaction = Braintree::Transaction.sale!(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "06/2009"
        },
      )
      transaction.amount.should == BigDecimal("1000.00")
      result = Braintree::Transaction.submit_for_settlement(transaction.id, "999.99")
      result.success?.should == true
      result.transaction.amount.should == BigDecimal("999.99")
      result.transaction.status.should == Braintree::Transaction::Status::SubmittedForSettlement
      result.transaction.updated_at.between?(Time.now - 60, Time.now).should == true
    end

    it "returns a successful result if order_id is passed in as an options hash" do
      transaction = Braintree::Transaction.sale!(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "06/2009"
        },
      )
      options = {:order_id => "ABC123"}
      result = Braintree::Transaction.submit_for_settlement(transaction.id, nil, options)
      result.success?.should == true
      result.transaction.order_id.should == "ABC123"
      result.transaction.status.should == Braintree::Transaction::Status::SubmittedForSettlement
    end

    it "returns a successful result if descritpors are passed in as an options hash" do
      transaction = Braintree::Transaction.sale!(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "06/2009"
        },
      )

      options = {
        :descriptor => {
          :name => "123*123456789012345678",
          :phone => "3334445555",
          :url => "ebay.com"
        }
      }

      result = Braintree::Transaction.submit_for_settlement(transaction.id, nil, options)
      result.success?.should == true
      result.transaction.status.should == Braintree::Transaction::Status::SubmittedForSettlement
      result.transaction.descriptor.name.should == "123*123456789012345678"
      result.transaction.descriptor.phone.should == "3334445555"
      result.transaction.descriptor.url.should == "ebay.com"
    end

    it "raises an error if an invalid option is passed in" do
      transaction = Braintree::Transaction.sale!(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "06/2009"
        },
      )

      options = {:order_id => "ABC123", :invalid_option => "i'm invalid"}

      expect do
        Braintree::Transaction.submit_for_settlement(transaction.id, nil, options)
      end.to raise_error(ArgumentError)
    end

    it "returns an error result if settlement is too large" do
      transaction = Braintree::Transaction.sale!(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :merchant_account_id => SpecHelper::CardProcessorBRLMerchantAccountId,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "06/2009"
        },
      )
      transaction.amount.should == BigDecimal("1000.00")
      result = Braintree::Transaction.submit_for_settlement(transaction.id, "1000.01")
      result.success?.should == false
      result.errors.for(:transaction).on(:amount)[0].code.should == Braintree::ErrorCodes::Transaction::SettlementAmountIsTooLarge
      result.params[:transaction][:amount].should == "1000.01"
    end

    it "returns an error result if status is not authorized" do
      transaction = Braintree::Transaction.sale(
        :amount => Braintree::Test::TransactionAmounts::Decline,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "06/2009"
        },
      ).transaction
      result = Braintree::Transaction.submit_for_settlement(transaction.id)
      result.success?.should == false
      result.errors.for(:transaction).on(:base)[0].code.should == Braintree::ErrorCodes::Transaction::CannotSubmitForSettlement
    end

    context "service fees" do
      it "returns an error result if amount submitted for settlement is less than service fee amount" do
        transaction = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :merchant_account_id => SpecHelper::NonDefaultSubMerchantAccountId,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "06/2009"
          },
          :service_fee_amount => "1.00",
        ).transaction
        result = Braintree::Transaction.submit_for_settlement(transaction.id, "0.01")
        result.success?.should == false
        result.errors.for(:transaction).on(:amount)[0].code.should == Braintree::ErrorCodes::Transaction::SettlementAmountIsLessThanServiceFeeAmount
      end
    end

    xit "succeeds when level 2 data is provided" do
      result = Braintree::Transaction.sale(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :merchant_account_id => SpecHelper::FakeAmexDirectMerchantAccountId,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::AmexPayWithPoints::Success,
          :expiration_date => "05/2009"
        },
        :options => {
          :amex_rewards => {
            :request_id => "ABC123",
            :points => "1000",
            :currency_amount => "10.00",
            :currency_iso_code => "USD"
          }
        },
      )
      result.success?.should == true

      result = Braintree::Transaction.submit_for_settlement(result.transaction.id, nil, :tax_amount => "2.00", :tax_exempt => false, :purchase_order_number => "0Rd3r#")
      result.success?.should == true
      result.transaction.status.should == Braintree::Transaction::Status::SubmittedForSettlement
    end

    xit "succeeds when level 3 data is provided" do
      result = Braintree::Transaction.sale(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :merchant_account_id => SpecHelper::FakeAmexDirectMerchantAccountId,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::AmexPayWithPoints::Success,
          :expiration_date => "05/2009"
        },
        :options => {
          :amex_rewards => {
            :request_id => "ABC123",
            :points => "1000",
            :currency_amount => "10.00",
            :currency_iso_code => "USD"
          }
        },
      )
      result.success?.should == true

      result = Braintree::Transaction.submit_for_settlement(
        result.transaction.id,
        nil,
        :discount_amount => "2.00",
        :shipping_amount => "1.23",
        :ships_from_postal_code => "90210",
        :line_items => [
          {
            :quantity => 1,
            :unit_amount => 1,
            :name => "New line item",
            :kind => "debit",
            :total_amount => "18.00",
            :discount_amount => "12.00",
            :tax_amount => "0",
          },
        ],
      )
      result.success?.should == true
      result.transaction.status.should == Braintree::Transaction::Status::SubmittedForSettlement
    end
  end

  describe "self.submit_for_settlement!" do
    it "returns the transaction if successful" do
      original_transaction = Braintree::Transaction.sale!(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "06/2009"
        },
      )
      options = {:order_id => "ABC123"}
      transaction = Braintree::Transaction.submit_for_settlement!(original_transaction.id, "0.01", options)
      transaction.status.should == Braintree::Transaction::Status::SubmittedForSettlement
      transaction.id.should == original_transaction.id
      transaction.order_id.should == options[:order_id]
    end

    it "raises a ValidationsFailed if unsuccessful" do
      transaction = Braintree::Transaction.sale!(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :merchant_account_id => SpecHelper::CardProcessorBRLMerchantAccountId,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "06/2009"
        },
      )
      transaction.amount.should == BigDecimal("1000.00")
      expect do
        Braintree::Transaction.submit_for_settlement!(transaction.id, "1000.01")
      end.to raise_error(Braintree::ValidationsFailed)
    end
  end

  describe "update details" do
    context "when status is submitted_for_settlement" do
      let(:transaction) do
        Braintree::Transaction.sale!(
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :merchant_account_id => SpecHelper::DefaultMerchantAccountId,
          :descriptor => {
            :name => "123*123456789012345678",
            :phone => "3334445555",
            :url => "ebay.com"
          },
          :order_id => "123",
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "06/2009"
          },
          :options => {
            :submit_for_settlement => true
          },
        )
      end

      it "successfully updates details" do
        result = Braintree::Transaction.update_details(transaction.id, {
          :amount => Braintree::Test::TransactionAmounts::Authorize.to_f - 1,
          :descriptor => {
            :name => "456*123456789012345678",
            :phone => "3334445555",
            :url => "ebay.com",
          },
          :order_id => "456"
        })
        result.success?.should == true
        result.transaction.amount.should == BigDecimal(Braintree::Test::TransactionAmounts::Authorize) - 1
        result.transaction.order_id.should == "456"
        result.transaction.descriptor.name.should ==  "456*123456789012345678"
      end

      it "raises an error when a key is invalid" do
        expect do
          Braintree::Transaction.update_details(transaction.id, {
            :invalid_key => Braintree::Test::TransactionAmounts::Authorize.to_f - 1,
            :descriptor => {
              :name => "456*123456789012345678",
              :phone => "3334445555",
              :url => "ebay.com",
            },
            :order_id => "456"
          })
        end.to raise_error(ArgumentError)
      end

      describe "errors" do
        it "returns an error response when the settlement amount is invalid" do
          result = Braintree::Transaction.update_details(transaction.id, {
            :amount => "10000",
            :descriptor => {
              :name => "456*123456789012345678",
              :phone => "3334445555",
              :url => "ebay.com",
            },
            :order_id => "456"
          })
          result.success?.should == false
          result.errors.for(:transaction).on(:amount)[0].code.should == Braintree::ErrorCodes::Transaction::SettlementAmountIsTooLarge
        end

        it "returns an error response when the descriptor is invalid" do
          result = Braintree::Transaction.update_details(transaction.id, {
            :amount => Braintree::Test::TransactionAmounts::Authorize.to_f - 1,
            :descriptor => {
              :name => "invalid descriptor name",
              :phone => "invalid phone",
              :url => "12345678901234"
            },
            :order_id => "456"
          })
          result.success?.should == false
          result.errors.for(:transaction).for(:descriptor).on(:name)[0].code.should == Braintree::ErrorCodes::Descriptor::NameFormatIsInvalid
          result.errors.for(:transaction).for(:descriptor).on(:phone)[0].code.should == Braintree::ErrorCodes::Descriptor::PhoneFormatIsInvalid
          result.errors.for(:transaction).for(:descriptor).on(:url)[0].code.should == Braintree::ErrorCodes::Descriptor::UrlFormatIsInvalid
        end

        it "returns an error response when the order_id is invalid" do
          result = Braintree::Transaction.update_details(transaction.id, {
            :amount => Braintree::Test::TransactionAmounts::Authorize.to_f - 1,
            :descriptor => {
              :name => "456*123456789012345678",
              :phone => "3334445555",
              :url => "ebay.com",
            },
            :order_id => "x" * 256
          })
          result.success?.should == false
          result.errors.for(:transaction).on(:order_id)[0].code.should == Braintree::ErrorCodes::Transaction::OrderIdIsTooLong
        end

        it "returns an error on an unsupported processor" do
          transaction = Braintree::Transaction.sale!(
            :amount => Braintree::Test::TransactionAmounts::Authorize,
            :merchant_account_id => SpecHelper::FakeAmexDirectMerchantAccountId,
            :descriptor => {
              :name => "123*123456789012345678",
              :phone => "3334445555",
              :url => "ebay.com"
            },
            :order_id => "123",
            :credit_card => {
              :number => Braintree::Test::CreditCardNumbers::AmexPayWithPoints::Success,
              :expiration_date => "05/2009"
            },
            :options => {
              :submit_for_settlement => true
            },
          )
          result = Braintree::Transaction.update_details(transaction.id, {
            :amount => Braintree::Test::TransactionAmounts::Authorize.to_f - 1,
            :descriptor => {
              :name => "456*123456789012345678",
              :phone => "3334445555",
              :url => "ebay.com",
            },
            :order_id => "456"
          })
          result.success?.should == false
          result.errors.for(:transaction).on(:base)[0].code.should == Braintree::ErrorCodes::Transaction::ProcessorDoesNotSupportUpdatingTransactionDetails
        end
      end
    end

    context "when status is not submitted_for_settlement" do
      it "returns an error" do
        transaction = Braintree::Transaction.sale!(
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :merchant_account_id => SpecHelper::DefaultMerchantAccountId,
          :descriptor => {
            :name => "123*123456789012345678",
            :phone => "3334445555",
            :url => "ebay.com"
          },
          :order_id => "123",
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "06/2009"
          },
        )
        result = Braintree::Transaction.update_details(transaction.id, {
          :amount => Braintree::Test::TransactionAmounts::Authorize.to_f - 1,
          :descriptor => {
            :name => "456*123456789012345678",
            :phone => "3334445555",
            :url => "ebay.com",
          },
          :order_id => "456"
        })
        result.success?.should == false
        result.errors.for(:transaction).on(:base)[0].code.should == Braintree::ErrorCodes::Transaction::CannotUpdateTransactionDetailsNotSubmittedForSettlement
      end
    end

  end

  describe "submit for partial settlement" do
    it "successfully submits multiple times for partial settlement" do
      authorized_transaction = Braintree::Transaction.sale!(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :merchant_account_id => SpecHelper::DefaultMerchantAccountId,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "06/2009"
        },
      )

      result = Braintree::Transaction.submit_for_partial_settlement(authorized_transaction.id, 100)
      result.success?.should == true
      partial_settlement_transaction1 = result.transaction
      partial_settlement_transaction1.amount.should == 100
      partial_settlement_transaction1.type.should == Braintree::Transaction::Type::Sale
      partial_settlement_transaction1.status.should == Braintree::Transaction::Status::SubmittedForSettlement
      partial_settlement_transaction1.authorized_transaction_id.should == authorized_transaction.id

      refreshed_authorized_transaction = Braintree::Transaction.find(authorized_transaction.id)
      refreshed_authorized_transaction.status.should == Braintree::Transaction::Status::SettlementPending
      refreshed_authorized_transaction.partial_settlement_transaction_ids.should == [partial_settlement_transaction1.id]

      result = Braintree::Transaction.submit_for_partial_settlement(authorized_transaction.id, 800)
      result.success?.should == true
      partial_settlement_transaction2 = result.transaction
      partial_settlement_transaction2.amount.should == 800
      partial_settlement_transaction2.type.should == Braintree::Transaction::Type::Sale
      partial_settlement_transaction2.status.should == Braintree::Transaction::Status::SubmittedForSettlement
      partial_settlement_transaction2.authorized_transaction_id.should == authorized_transaction.id

      refreshed_authorized_transaction = Braintree::Transaction.find(authorized_transaction.id)
      refreshed_authorized_transaction.status.should == Braintree::Transaction::Status::SettlementPending
      refreshed_authorized_transaction.partial_settlement_transaction_ids.sort.should == [partial_settlement_transaction1.id, partial_settlement_transaction2.id].sort
    end

    it "allows partial settlement to be submitted with order_id" do
      authorized_transaction = Braintree::Transaction.sale!(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :merchant_account_id => SpecHelper::DefaultMerchantAccountId,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
      )

      result = Braintree::Transaction.submit_for_partial_settlement(authorized_transaction.id, 100, :order_id => 1234)
      result.success?.should == true
      partial_settlement_transaction = result.transaction
      partial_settlement_transaction.order_id.should == "1234"
    end

    it "returns an error with an order_id that's too long" do
      authorized_transaction = Braintree::Transaction.sale!(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :merchant_account_id => SpecHelper::DefaultMerchantAccountId,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
      )

      result = Braintree::Transaction.submit_for_partial_settlement(authorized_transaction.id, 100, :order_id => "1"*256)
      result.success?.should == false
      result.errors.for(:transaction).on(:order_id)[0].code.should == Braintree::ErrorCodes::Transaction::OrderIdIsTooLong
    end

    it "allows partial settlement to be submitted with descriptors" do
      authorized_transaction = Braintree::Transaction.sale!(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :merchant_account_id => SpecHelper::DefaultMerchantAccountId,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
      )

      result = Braintree::Transaction.submit_for_partial_settlement(
        authorized_transaction.id,
        100,
        :descriptor => {:name => "123*123456789012345678", :phone => "5555551234", :url => "url.com"},
      )
      result.success?.should == true
      partial_settlement_transaction = result.transaction
      partial_settlement_transaction.descriptor.name.should == "123*123456789012345678"
      partial_settlement_transaction.descriptor.phone.should == "5555551234"
      partial_settlement_transaction.descriptor.url.should == "url.com"
    end

    it "returns an error with a descriptor in an invalid format" do
      authorized_transaction = Braintree::Transaction.sale!(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :merchant_account_id => SpecHelper::DefaultMerchantAccountId,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
      )

      result = Braintree::Transaction.submit_for_partial_settlement(
        authorized_transaction.id,
        100,
        :descriptor => {
          :name => "invalid_format",
          :phone => "%bad4445555",
          :url => "12345678901234"
        },
      )
      result.success?.should == false
      result.errors.for(:transaction).for(:descriptor).on(:name)[0].code.should == Braintree::ErrorCodes::Descriptor::NameFormatIsInvalid
      result.errors.for(:transaction).for(:descriptor).on(:phone)[0].code.should == Braintree::ErrorCodes::Descriptor::PhoneFormatIsInvalid
      result.errors.for(:transaction).for(:descriptor).on(:url)[0].code.should == Braintree::ErrorCodes::Descriptor::UrlFormatIsInvalid
    end

    it "returns an error with an unsupported processor" do
      authorized_transaction = Braintree::Transaction.sale!(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :merchant_account_id => SpecHelper::FakeAmexDirectMerchantAccountId,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::AmexPayWithPoints::Success,
          :expiration_date => "05/2009"
        },
      )

      result = Braintree::Transaction.submit_for_partial_settlement(authorized_transaction.id, 100)
      result.success?.should == false
      result.errors.for(:transaction).on(:base)[0].code.should == Braintree::ErrorCodes::Transaction::ProcessorDoesNotSupportPartialSettlement
    end

    it "returns an error with an invalid payment instrument type" do
      authorized_transaction = Braintree::Transaction.sale!(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :merchant_account_id => SpecHelper::FakeVenmoAccountMerchantAccountId,
        :payment_method_nonce => Braintree::Test::Nonce::VenmoAccount,
      )

      result = Braintree::Transaction.submit_for_partial_settlement(authorized_transaction.id, 100)
      result.success?.should == false
      result.errors.for(:transaction).on(:base)[0].code.should == Braintree::ErrorCodes::Transaction::PaymentInstrumentTypeIsNotAccepted
    end

    it "returns an error result if settlement amount greater than authorized amount" do
      authorized_transaction = Braintree::Transaction.sale!(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :merchant_account_id => SpecHelper::DefaultMerchantAccountId,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "06/2009"
        },
      )

      result = Braintree::Transaction.submit_for_partial_settlement(authorized_transaction.id, 100)
      result.success?.should == true

      result = Braintree::Transaction.submit_for_partial_settlement(authorized_transaction.id, 901)
      result.success?.should == false
      result.errors.for(:transaction).on(:amount)[0].code.should == Braintree::ErrorCodes::Transaction::SettlementAmountIsTooLarge
    end

    it "returns an error result if status is not authorized" do
      authorized_transaction = Braintree::Transaction.sale!(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :merchant_account_id => SpecHelper::DefaultMerchantAccountId,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "06/2009"
        },
      )

      result = Braintree::Transaction.void(authorized_transaction.id)
      result.success?.should == true

      result = Braintree::Transaction.submit_for_partial_settlement(authorized_transaction.id, 100)
      result.success?.should == false
      result.errors.for(:transaction).on(:base)[0].code.should == Braintree::ErrorCodes::Transaction::CannotSubmitForSettlement
    end
  end

  describe "self.submit_for_partial_settlement!" do
    it "returns the transaction if successful" do
      original_transaction = Braintree::Transaction.sale!(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "06/2009"
        },
      )
      options = {:order_id => "ABC123"}
      transaction = Braintree::Transaction.submit_for_partial_settlement!(original_transaction.id, "0.01", options)
      transaction.status.should == Braintree::Transaction::Status::SubmittedForSettlement
      transaction.order_id.should == options[:order_id]
    end

    it "raises a ValidationsFailed if unsuccessful" do
      transaction = Braintree::Transaction.sale!(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "06/2009"
        },
      )
      transaction.amount.should == BigDecimal("1000.00")
      expect do
        Braintree::Transaction.submit_for_partial_settlement!(transaction.id, "1000.01")
      end.to raise_error(Braintree::ValidationsFailed)
    end
  end

  describe "self.release_from_escrow" do
    it "returns the transaction if successful" do
      original_transaction = create_escrowed_transcation

      result = Braintree::Transaction.release_from_escrow(original_transaction.id)
      result.transaction.escrow_status.should == Braintree::Transaction::EscrowStatus::ReleasePending
    end

    it "returns an error result if escrow_status is not HeldForEscrow" do
      transaction = Braintree::Transaction.sale!(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :merchant_account_id => SpecHelper::NonDefaultSubMerchantAccountId,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
        :service_fee_amount => "1.00",
      )

      transaction.escrow_status.should be_nil

      result = Braintree::Transaction.release_from_escrow(transaction.id)
      result.errors.for(:transaction).on(:base)[0].code.should == Braintree::ErrorCodes::Transaction::CannotReleaseFromEscrow
    end
  end

  describe "self.release_from_escrow!" do
    it "returns the transaction when successful" do
      original_transaction = create_escrowed_transcation

      transaction = Braintree::Transaction.release_from_escrow!(original_transaction.id)
      transaction.escrow_status.should == Braintree::Transaction::EscrowStatus::ReleasePending
    end

    it "raises an error when transaction is not successful" do
      transaction = Braintree::Transaction.sale!(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :merchant_account_id => SpecHelper::NonDefaultSubMerchantAccountId,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
        :service_fee_amount => "1.00",
      )

      transaction.escrow_status.should be_nil

      expect do
        Braintree::Transaction.release_from_escrow!(transaction.id)
      end.to raise_error(Braintree::ValidationsFailed)
    end
  end

  describe "self.cancel_release" do
    it "returns the transaction if successful" do
      transaction = create_escrowed_transcation
      result = Braintree::Transaction.release_from_escrow(transaction.id)
      result.transaction.escrow_status.should == Braintree::Transaction::EscrowStatus::ReleasePending

      result = Braintree::Transaction.cancel_release(transaction.id)

      result.success?.should be(true)
      result.transaction.escrow_status.should == Braintree::Transaction::EscrowStatus::Held
    end

    it "returns an error result if escrow_status is not ReleasePending" do
      transaction = create_escrowed_transcation

      result = Braintree::Transaction.cancel_release(transaction.id)

      result.success?.should be(false)
      result.errors.for(:transaction).on(:base)[0].code.should == Braintree::ErrorCodes::Transaction::CannotCancelRelease
    end
  end

  describe "self.cancel_release!" do
    it "returns the transaction when release is cancelled" do
      transaction = create_escrowed_transcation
      result = Braintree::Transaction.release_from_escrow(transaction.id)
      result.transaction.escrow_status.should == Braintree::Transaction::EscrowStatus::ReleasePending

      transaction = Braintree::Transaction.cancel_release!(transaction.id)

      transaction.escrow_status.should == Braintree::Transaction::EscrowStatus::Held
    end

    it "raises an error when release cannot be cancelled" do
      transaction = create_escrowed_transcation

      expect {
        transaction = Braintree::Transaction.cancel_release!(transaction.id)
      }.to raise_error(Braintree::ValidationsFailed)
    end
  end

  describe "self.credit" do
    it "returns a successful result with type=credit if successful" do
      result = Braintree::Transaction.credit(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
      )
      result.success?.should == true
      result.transaction.id.should =~ /^\w{6,}$/
      result.transaction.type.should == "credit"
      result.transaction.amount.should == BigDecimal(Braintree::Test::TransactionAmounts::Authorize)
      result.transaction.credit_card_details.bin.should == Braintree::Test::CreditCardNumbers::Visa[0, 6]
      result.transaction.credit_card_details.last_4.should == Braintree::Test::CreditCardNumbers::Visa[-4..-1]
      result.transaction.credit_card_details.expiration_date.should == "05/2009"
    end

    it "returns an error result if validations fail" do
      params = {
        :transaction => {
          :amount => nil,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "05/2009"
          }
        }
      }
      result = Braintree::Transaction.credit(params[:transaction])
      result.success?.should == false
      result.params.should == {:transaction => {:type => "credit", :amount => nil, :credit_card => {:expiration_date => "05/2009"}}}
      result.errors.for(:transaction).on(:amount)[0].code.should == Braintree::ErrorCodes::Transaction::AmountIsRequired
    end

    it "allows merchant account to be specified" do
      result = Braintree::Transaction.credit(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :merchant_account_id => SpecHelper::NonDefaultMerchantAccountId,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
      )
      result.success?.should == true
      result.transaction.merchant_account_id.should == SpecHelper::NonDefaultMerchantAccountId
    end

    it "uses default merchant account when it is not specified" do
      result = Braintree::Transaction.credit(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
      )
      result.success?.should == true
      result.transaction.merchant_account_id.should == SpecHelper::DefaultMerchantAccountId
    end

    it "disallows service fee on a credit" do
      params = {
        :transaction => {
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "05/2009"
          },
          :service_fee_amount => "1.00"
        }
      }
      result = Braintree::Transaction.credit(params[:transaction])
      result.success?.should == false
      result.errors.for(:transaction).on(:base).map(&:code).should include(Braintree::ErrorCodes::Transaction::ServiceFeeIsNotAllowedOnCredits)
    end
  end

  describe "self.credit!" do
    it "returns the transaction if valid" do
      transaction = Braintree::Transaction.credit!(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
      )
      transaction.id.should =~ /^\w{6,}$/
      transaction.type.should == "credit"
      transaction.amount.should == BigDecimal(Braintree::Test::TransactionAmounts::Authorize)
      transaction.credit_card_details.bin.should == Braintree::Test::CreditCardNumbers::Visa[0, 6]
      transaction.credit_card_details.last_4.should == Braintree::Test::CreditCardNumbers::Visa[-4..-1]
      transaction.credit_card_details.expiration_date.should == "05/2009"
    end

    it "raises a ValidationsFailed if invalid" do
      expect do
        Braintree::Transaction.credit!(
          :amount => nil,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "05/2009"
          },
        )
      end.to raise_error(Braintree::ValidationsFailed)
    end
  end

  describe "self.find" do
    it "finds the transaction with the given id" do
      result = Braintree::Transaction.create(
        :type => "sale",
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
      )
      result.success?.should == true
      created_transaction = result.transaction
      found_transaction = Braintree::Transaction.find(created_transaction.id)
      found_transaction.should == created_transaction
      found_transaction.graphql_id.should_not be_nil
    end

    it "finds the vaulted transaction with the given id" do
      result = Braintree::Transaction.create(
        :type => "sale",
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
        :options => {:store_in_vault => true},
      )
      result.success?.should == true
      created_transaction = result.transaction
      found_transaction = Braintree::Transaction.find(created_transaction.id)
      found_transaction.should == created_transaction

      found_transaction.credit_card_details.unique_number_identifier.should_not be_nil
    end

    it "raises a NotFoundError exception if transaction cannot be found" do
      expect do
        Braintree::Transaction.find("invalid-id")
      end.to raise_error(Braintree::NotFoundError, 'transaction with id "invalid-id" not found')
    end

    it "finds a transaction and returns an acquirer_reference_number if the transaction has one" do
      transaction = Braintree::Transaction.find("transactionwithacquirerreferencenumber")

      transaction.acquirer_reference_number.should == "123456789 091019"
    end

    context "disbursement_details" do
      it "includes disbursement_details on found transactions" do
        found_transaction = Braintree::Transaction.find("deposittransaction")

        found_transaction.disbursed?.should == true
        disbursement = found_transaction.disbursement_details

        expect(disbursement.disbursement_date).to be_a Date
        expect(disbursement.disbursement_date).to eq Date.parse("2013-04-10")
        disbursement.settlement_amount.should == "100.00"
        disbursement.settlement_currency_iso_code.should == "USD"
        disbursement.settlement_currency_exchange_rate.should == "1"
        disbursement.funds_held?.should == false
        disbursement.success?.should be(true)
      end

      it "is not disbursed" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "05/2009"
          },
        )
        result.success?.should == true
        created_transaction = result.transaction

        created_transaction.disbursed?.should == false
      end
    end

    context "disputes" do
      it "includes disputes on found transactions" do
        found_transaction = Braintree::Transaction.find("disputedtransaction")

        found_transaction.disputes.count.should == 1

        dispute = found_transaction.disputes.first
        dispute.received_date.should == Date.new(2014, 3, 1)
        dispute.reply_by_date.should == Date.new(2014, 3, 21)
        dispute.amount.should == Braintree::Util.to_big_decimal("250.00")
        dispute.currency_iso_code.should == "USD"
        dispute.reason.should == Braintree::Dispute::Reason::Fraud
        dispute.status.should == Braintree::Dispute::Status::Won
        dispute.transaction_details.amount.should == Braintree::Util.to_big_decimal("1000.00")
        dispute.transaction_details.id.should == "disputedtransaction"
        dispute.kind.should == Braintree::Dispute::Kind::Chargeback
        dispute.date_opened.should == Date.new(2014, 3, 1)
        dispute.date_won.should == Date.new(2014, 3, 7)
      end

      it "includes disputes on found transactions" do
        found_transaction = Braintree::Transaction.find("retrievaltransaction")

        found_transaction.disputes.count.should == 1

        dispute = found_transaction.disputes.first
        dispute.amount.should == Braintree::Util.to_big_decimal("1000.00")
        dispute.currency_iso_code.should == "USD"
        dispute.reason.should == Braintree::Dispute::Reason::Retrieval
        dispute.status.should == Braintree::Dispute::Status::Open
        dispute.transaction_details.amount.should == Braintree::Util.to_big_decimal("1000.00")
        dispute.transaction_details.id.should == "retrievaltransaction"
      end

      it "is not disputed" do
        result = Braintree::Transaction.create(
          :type => "sale",
          :amount => Braintree::Test::TransactionAmounts::Authorize,
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "05/2009"
          },
        )
        result.success?.should == true
        created_transaction = result.transaction

        created_transaction.disputes.should == []
      end
    end

    context "three_d_secure_info" do
      it "returns all the three_d_secure_info" do
        transaction = Braintree::Transaction.find("threedsecuredtransaction")

        expect(transaction.three_d_secure_info.authentication).to have_key(:trans_status)
        expect(transaction.three_d_secure_info.authentication).to have_key(:trans_status_reason)
        expect(transaction.three_d_secure_info.lookup).to have_key(:trans_status)
        expect(transaction.three_d_secure_info.lookup).to have_key(:trans_status_reason)
        expect(transaction.three_d_secure_info.cavv).to eq("somebase64value")
        expect(transaction.three_d_secure_info.ds_transaction_id).to eq("dstxnid")
        expect(transaction.three_d_secure_info.eci_flag).to eq("07")
        expect(transaction.three_d_secure_info.enrolled).to eq("Y")
        expect(transaction.three_d_secure_info.pares_status).to eq("Y")
        expect(transaction.three_d_secure_info).to be_liability_shift_possible
        expect(transaction.three_d_secure_info).to be_liability_shifted
        expect(transaction.three_d_secure_info.status).to eq("authenticate_successful")
        expect(transaction.three_d_secure_info.three_d_secure_authentication_id).to be
        expect(transaction.three_d_secure_info.three_d_secure_version).not_to be_nil
        expect(transaction.three_d_secure_info.xid).to eq("xidvalue")
      end

      it "is nil if the transaction wasn't 3d secured" do
        transaction = Braintree::Transaction.find("settledtransaction")

        expect(transaction.three_d_secure_info).to be_nil
      end
    end

    context "paypal" do
      it "returns all the required paypal fields" do
        transaction = Braintree::Transaction.find("settledtransaction")

        transaction.paypal_details.debug_id.should_not be_nil
        transaction.paypal_details.payer_email.should_not be_nil
        transaction.paypal_details.authorization_id.should_not be_nil
        transaction.paypal_details.payer_id.should_not be_nil
        transaction.paypal_details.payer_first_name.should_not be_nil
        transaction.paypal_details.payer_last_name.should_not be_nil
        transaction.paypal_details.payer_status.should_not be_nil
        transaction.paypal_details.seller_protection_status.should_not be_nil
        transaction.paypal_details.capture_id.should_not be_nil
        transaction.paypal_details.refund_id.should_not be_nil
        transaction.paypal_details.transaction_fee_amount.should_not be_nil
        transaction.paypal_details.transaction_fee_currency_iso_code.should_not be_nil
        transaction.paypal_details.refund_from_transaction_fee_amount.should_not be_nil
        transaction.paypal_details.refund_from_transaction_fee_currency_iso_code.should_not be_nil
      end
    end
  end

  describe "self.hold_in_escrow" do
    it "returns the transaction if successful" do
      result = Braintree::Transaction.create(
        :type => "sale",
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :merchant_account_id => SpecHelper::NonDefaultSubMerchantAccountId,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "12/12",
        },
        :service_fee_amount => "10.00",
      )

      result.transaction.escrow_status.should be_nil
      result = Braintree::Transaction.hold_in_escrow(result.transaction.id)

      result.success?.should be(true)
      result.transaction.escrow_status.should == Braintree::Transaction::EscrowStatus::HoldPending
    end

    it "returns an error result if the transaction cannot be held in escrow" do
      transaction = Braintree::Transaction.sale!(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :merchant_account_id => SpecHelper::NonDefaultMerchantAccountId,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
      )

      result = Braintree::Transaction.hold_in_escrow(transaction.id)
      result.errors.for(:transaction).on(:base)[0].code.should == Braintree::ErrorCodes::Transaction::CannotHoldInEscrow
    end
  end

  describe "self.hold_in_escrow!" do
    it "returns the transaction if successful" do
      result = Braintree::Transaction.create(
        :type => "sale",
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :merchant_account_id => SpecHelper::NonDefaultSubMerchantAccountId,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "12/12",
        },
        :service_fee_amount => "10.00",
      )

      result.transaction.escrow_status.should be_nil
      transaction = Braintree::Transaction.hold_in_escrow!(result.transaction.id)

      transaction.escrow_status.should == Braintree::Transaction::EscrowStatus::HoldPending
    end

    it "raises an error if the transaction cannot be held in escrow" do
      transaction = Braintree::Transaction.sale!(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :merchant_account_id => SpecHelper::NonDefaultMerchantAccountId,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
      )

      expect do
        Braintree::Transaction.hold_in_escrow!(transaction.id)
      end.to raise_error(Braintree::ValidationsFailed)
    end
  end

  describe "self.void" do
    it "returns a successful result if successful" do
      transaction = Braintree::Transaction.sale!(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
      )
      result = Braintree::Transaction.void(transaction.id)
      result.success?.should == true
      result.transaction.id.should == transaction.id
      result.transaction.status.should == Braintree::Transaction::Status::Voided
    end

    it "returns an error result if unsuccessful" do
      transaction = Braintree::Transaction.sale(
        :amount => Braintree::Test::TransactionAmounts::Decline,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
      ).transaction
      result = Braintree::Transaction.void(transaction.id)
      result.success?.should == false
      result.errors.for(:transaction).on(:base)[0].code.should == Braintree::ErrorCodes::Transaction::CannotBeVoided
    end
  end

  describe "self.void!" do
    it "returns the transaction if successful" do
      transaction = Braintree::Transaction.sale!(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
      )
      returned_transaction = Braintree::Transaction.void!(transaction.id)
      returned_transaction.should == transaction
      returned_transaction.status.should == Braintree::Transaction::Status::Voided
    end

    it "raises a ValidationsFailed if unsuccessful" do
      transaction = Braintree::Transaction.sale(
        :amount => Braintree::Test::TransactionAmounts::Decline,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
      ).transaction
      expect do
        Braintree::Transaction.void!(transaction.id)
      end.to raise_error(Braintree::ValidationsFailed)
    end
  end

  describe "status_history" do
    it "returns an array of StatusDetail" do
      transaction = Braintree::Transaction.sale!(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
      )
      result = Braintree::Transaction.submit_for_settlement!(transaction.id)
      result.status_history.size.should == 2
      result.status_history[0].status.should == Braintree::Transaction::Status::Authorized
      result.status_history[1].status.should == Braintree::Transaction::Status::SubmittedForSettlement
    end
  end

  describe "authorization_adjustments" do
    it "includes authorization adjustments on found transactions" do
      found_transaction = Braintree::Transaction.find("authadjustmenttransaction")

      found_transaction.authorization_adjustments.count.should == 1

      authorization_adjustment = found_transaction.authorization_adjustments.first
      authorization_adjustment.amount.should == "-20.00"
      authorization_adjustment.success.should == true
      authorization_adjustment.timestamp.should be_a Time
      authorization_adjustment.processor_response_code.should == "1000"
      authorization_adjustment.processor_response_text.should == "Approved"
    end

    it "includes authorization adjustments soft declined on found transactions" do
      found_transaction = Braintree::Transaction.find("authadjustmenttransactionsoftdeclined")

      found_transaction.authorization_adjustments.count.should == 1

      authorization_adjustment = found_transaction.authorization_adjustments.first
      authorization_adjustment.amount.should == "-20.00"
      authorization_adjustment.success.should == false
      authorization_adjustment.timestamp.should be_a Time
      authorization_adjustment.processor_response_code.should == "3000"
      authorization_adjustment.processor_response_text.should == "Processor Network Unavailable - Try Again"
      authorization_adjustment.processor_response_type.should == Braintree::ProcessorResponseTypes::SoftDeclined
    end

    it "includes authorization adjustments hard declined on found transactions" do
      found_transaction = Braintree::Transaction.find("authadjustmenttransactionharddeclined")

      found_transaction.authorization_adjustments.count.should == 1

      authorization_adjustment = found_transaction.authorization_adjustments.first
      authorization_adjustment.amount.should == "-20.00"
      authorization_adjustment.success.should == false
      authorization_adjustment.timestamp.should be_a Time
      authorization_adjustment.processor_response_code.should == "2015"
      authorization_adjustment.processor_response_text.should == "Transaction Not Allowed"
      authorization_adjustment.processor_response_type.should == Braintree::ProcessorResponseTypes::HardDeclined
    end
  end

  describe "vault_credit_card" do
    it "returns the Braintree::CreditCard if the transaction credit card is stored in the vault" do
      customer = Braintree::Customer.create!(
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2010"
        },
      )
      transaction = Braintree::CreditCard.sale(customer.credit_cards[0].token, {:amount => "100.00"}).transaction
      transaction.vault_credit_card.should == customer.credit_cards[0]
    end

    it "returns nil if the transaction credit card is not stored in the vault" do
      transaction = Braintree::Transaction.create!(
        :amount => "100.00",
        :type => "sale",
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2010"
        },
      )
      transaction.vault_credit_card.should == nil
    end
  end

  describe "vault_customer" do
    it "returns the Braintree::Customer if the transaction customer is stored in the vault" do
      customer = Braintree::Customer.create!(
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2010"
        },
      )
      transaction = Braintree::CreditCard.sale(customer.credit_cards[0].token, :amount => "100.00").transaction
      transaction.vault_customer.should == customer
    end

    it "returns nil if the transaction customer is not stored in the vault" do
      transaction = Braintree::Transaction.create!(
        :amount => "100.00",
        :type => "sale",
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2010"
        },
      )
      transaction.vault_customer.should == nil
    end
  end

  def create_transaction_to_refund
    transaction = Braintree::Transaction.sale!(
      :amount => Braintree::Test::TransactionAmounts::Authorize,
      :credit_card => {
        :number => Braintree::Test::CreditCardNumbers::Visa,
        :expiration_date => "05/2009"
      },
      :options => {
        :submit_for_settlement => true
      },
    )

    config = Braintree::Configuration.instantiate
    response = config.http.put("#{config.base_merchant_path}/transactions/#{transaction.id}/settle")
    Braintree::Transaction.find(transaction.id)
  end

  def create_paypal_transaction_for_refund
    transaction = Braintree::Transaction.sale!(
      :amount => Braintree::Test::TransactionAmounts::Authorize,
      :payment_method_nonce => Braintree::Test::Nonce::PayPalOneTimePayment,
      :options => {
        :submit_for_settlement => true
      },
    )

    config = Braintree::Configuration.instantiate
    config.http.put("#{config.base_merchant_path}/transactions/#{transaction.id}/settle")
    Braintree::Transaction.find(transaction.id)
  end

  def create_escrowed_transcation
    transaction = Braintree::Transaction.sale!(
      :amount => Braintree::Test::TransactionAmounts::Authorize,
      :merchant_account_id => SpecHelper::NonDefaultSubMerchantAccountId,
      :credit_card => {
        :number => Braintree::Test::CreditCardNumbers::Visa,
        :expiration_date => "05/2009"
      },
      :service_fee_amount => "1.00",
      :options => {:hold_in_escrow => true},
    )

    config = Braintree::Configuration.instantiate
    response = config.http.put("#{config.base_merchant_path}/transactions/#{transaction.id}/settle")
    response = config.http.put("#{config.base_merchant_path}/transactions/#{transaction.id}/escrow")
    Braintree::Transaction.find(transaction.id)
  end

  context "paypal" do
    it "can create a transaction for a paypal account" do
      result = Braintree::Transaction.sale(
        :amount => "10.00",
        :payment_method_nonce => Braintree::Test::Nonce::PayPalFuturePayment,
      )
      result.success?.should == true
      result.transaction.paypal_details.payer_email.should == "payer@example.com"
      result.transaction.paypal_details.payment_id.should match(/PAY-\w+/)
      result.transaction.paypal_details.authorization_id.should match(/AUTH-\w+/)
      result.transaction.paypal_details.image_url.should_not be_nil
    end

    it "can vault a paypal account on a transaction" do
      result = Braintree::Transaction.sale(
        :amount => "10.00",
        :payment_method_nonce => Braintree::Test::Nonce::PayPalFuturePayment,
        :options => {
          :store_in_vault => true
        },
      )
      result.success?.should == true
      result.transaction.paypal_details.token.should_not be_nil
      result.transaction.paypal_details.payer_email.should == "payer@example.com"
      result.transaction.paypal_details.payment_id.should match(/PAY-\w+/)
      result.transaction.paypal_details.authorization_id.should match(/AUTH-\w+/)
    end

    it "can create a transaction from a vaulted paypal account" do
      customer = Braintree::Customer.create!
      result = Braintree::PaymentMethod.create(
        :payment_method_nonce => Braintree::Test::Nonce::PayPalFuturePayment,
        :customer_id => customer.id,
      )

      result.should be_success
      result.payment_method.should be_a(Braintree::PayPalAccount)
      payment_method_token = result.payment_method.token

      result = Braintree::Transaction.sale(
        :amount => "100",
        :customer_id => customer.id,
        :payment_method_token => payment_method_token,
      )

      result.should be_success
      result.transaction.paypal_details.token.should == payment_method_token
      result.transaction.paypal_details.payer_email.should == "payer@example.com"
      result.transaction.paypal_details.payment_id.should match(/PAY-\w+/)
      result.transaction.paypal_details.authorization_id.should match(/AUTH-\w+/)
    end

    context "validation failure" do
      it "returns a validation error if consent code and access token are omitted" do
        nonce = nonce_for_paypal_account(:token => "TOKEN")
        result = Braintree::Transaction.sale(
          :amount => "10.00",
          :payment_method_nonce => nonce,
        )
        result.should_not be_success
        result.errors.for(:transaction).for(:paypal_account).first.code.should == Braintree::ErrorCodes::PayPalAccount::IncompletePayPalAccount
      end
    end
  end

  context "shared payment method" do
    before(:each) do
      @partner_merchant_gateway = Braintree::Gateway.new(
        :merchant_id => "integration_merchant_public_id",
        :public_key => "oauth_app_partner_user_public_key",
        :private_key => "oauth_app_partner_user_private_key",
        :environment => Braintree::Configuration.environment,
        :logger => Logger.new("/dev/null"),
      )
      @customer = @partner_merchant_gateway.customer.create(
        :first_name => "Joe",
        :last_name => "Brown",
        :company => "ExampleCo",
        :email => "joe@example.com",
        :phone => "312.555.1234",
        :fax => "614.555.5678",
        :website => "www.example.com",
      ).customer
      @address = @partner_merchant_gateway.address.create(
        :customer_id => @customer.id,
        :first_name => "Testy",
        :last_name => "McTesterson",
      ).address
      @credit_card = @partner_merchant_gateway.credit_card.create(
        :customer_id => @customer.id,
        :cardholder_name => "Adam Davis",
        :number => Braintree::Test::CreditCardNumbers::Visa,
        :expiration_date => "05/2009",
        :billing_address => {
          :first_name => "Adam",
          :last_name => "Davis",
          :postal_code => "95131"
        },
      ).credit_card

      oauth_gateway = Braintree::Gateway.new(
        :client_id => "client_id$#{Braintree::Configuration.environment}$integration_client_id",
        :client_secret => "client_secret$#{Braintree::Configuration.environment}$integration_client_secret",
        :logger => Logger.new("/dev/null"),
      )
      access_token = Braintree::OAuthTestHelper.create_token(oauth_gateway, {
        :merchant_public_id => "integration_merchant_id",
        :scope => "grant_payment_method,shared_vault_transactions"
      }).credentials.access_token

      @granting_gateway = Braintree::Gateway.new(
        :access_token => access_token,
        :logger => Logger.new("/dev/null"),
      )

    end

    it "oauth app details are returned on transaction created via nonce granting" do
      grant_result = @granting_gateway.payment_method.grant(@credit_card.token, false)

      result = Braintree::Transaction.sale(
        :payment_method_nonce => grant_result.payment_method_nonce.nonce,
        :amount => Braintree::Test::TransactionAmounts::Authorize,
      )
      result.transaction.facilitated_details.merchant_id.should == "integration_merchant_id"
      result.transaction.facilitated_details.merchant_name.should == "14ladders"
      result.transaction.facilitated_details.payment_method_nonce.should == grant_result.payment_method_nonce.nonce
      result.transaction.facilitator_details.should_not == nil
      result.transaction.facilitator_details.oauth_application_client_id.should == "client_id$#{Braintree::Configuration.environment}$integration_client_id"
      result.transaction.facilitator_details.oauth_application_name.should == "PseudoShop"
      result.transaction.billing_details.postal_code == nil
    end

    it "billing postal code is returned on transaction created via nonce granting when specified in the grant request" do
      grant_result = @granting_gateway.payment_method.grant(@credit_card.token, :allow_vaulting => false, :include_billing_postal_code => true)

      result = Braintree::Transaction.sale(
        :payment_method_nonce => grant_result.payment_method_nonce.nonce,
        :amount => Braintree::Test::TransactionAmounts::Authorize,
      )

      result.transaction.billing_details.postal_code == "95131"
    end

    it "allows transactions to be created with a shared payment method, customer, billing and shipping addresses" do
      result = @granting_gateway.transaction.sale(
        :shared_payment_method_token => @credit_card.token,
        :shared_customer_id => @customer.id,
        :shared_shipping_address_id => @address.id,
        :shared_billing_address_id => @address.id,
        :amount => Braintree::Test::TransactionAmounts::Authorize,
      )
      result.success?.should == true
      result.transaction.shipping_details.first_name.should == @address.first_name
      result.transaction.billing_details.first_name.should == @address.first_name
    end

    it "facilitated details are returned on transaction created via a shared_payment_method_token" do
      result = @granting_gateway.transaction.sale(
        :shared_payment_method_token => @credit_card.token,
        :amount => Braintree::Test::TransactionAmounts::Authorize,
      )
      result.transaction.facilitated_details.merchant_id.should == "integration_merchant_id"
      result.transaction.facilitated_details.merchant_name.should == "14ladders"
      result.transaction.facilitated_details.payment_method_nonce.should == nil
      result.transaction.facilitator_details.should_not == nil
      result.transaction.facilitator_details.oauth_application_client_id.should == "client_id$#{Braintree::Configuration.environment}$integration_client_id"
      result.transaction.facilitator_details.oauth_application_name.should == "PseudoShop"
    end

    it "facilitated details are returned on transaction created via a shared_payment_method_nonce" do
      shared_nonce = @partner_merchant_gateway.payment_method_nonce.create(
        @credit_card.token,
      ).payment_method_nonce.nonce

      result = @granting_gateway.transaction.sale(
        :shared_payment_method_nonce => shared_nonce,
        :amount => Braintree::Test::TransactionAmounts::Authorize,
      )
      result.transaction.facilitated_details.merchant_id.should == "integration_merchant_id"
      result.transaction.facilitated_details.merchant_name.should == "14ladders"
      result.transaction.facilitated_details.payment_method_nonce.should == nil
      result.transaction.facilitator_details.should_not == nil
      result.transaction.facilitator_details.oauth_application_client_id.should == "client_id$#{Braintree::Configuration.environment}$integration_client_id"
      result.transaction.facilitator_details.oauth_application_name.should == "PseudoShop"
    end
  end

  context "paypal here" do
    it "gets the details of an auth/capture transaction" do
      result = Braintree::Transaction.find("paypal_here_auth_capture_id")
      result.payment_instrument_type.should eq(Braintree::PaymentInstrumentType::PayPalHere)
      result.paypal_here_details.should_not be_nil

      details = result.paypal_here_details
      details.authorization_id.should_not be_nil
      details.capture_id.should_not be_nil
      details.invoice_id.should_not be_nil
      details.last_4.should_not be_nil
      details.payment_type.should_not be_nil
      details.transaction_fee_amount.should_not be_nil
      details.transaction_fee_currency_iso_code.should_not be_nil
      details.transaction_initiation_date.should_not be_nil
      details.transaction_updated_date.should_not be_nil
    end

    it "gets the details of a sale transaction" do
      result = Braintree::Transaction.find("paypal_here_sale_id")
      result.paypal_here_details.should_not be_nil

      details = result.paypal_here_details
      details.payment_id.should_not be_nil
    end

    it "gets the details of a refunded sale transaction" do
      result = Braintree::Transaction.find("paypal_here_refund_id")
      result.paypal_here_details.should_not be_nil

      details = result.paypal_here_details
      details.refund_id.should_not be_nil
    end
  end

  describe "card on file network tokenization" do
    it "creates a transaction with a vaulted, tokenized credit card" do
      result = Braintree::Transaction.sale(
        :amount => "112.44",
        :payment_method_token => "network_tokenized_credit_card",
      )
      result.success?.should == true
      transaction = result.transaction

      transaction.amount.should == BigDecimal("112.44")
      transaction.processed_with_network_token?.should == true
    end

    it "creates a transaction with a vaulted, non-tokenized credit card" do
      customer = Braintree::Customer.create!
      result = Braintree::PaymentMethod.create(
        :payment_method_nonce => Braintree::Test::Nonce::TransactableVisa,
        :customer_id => customer.id,
      )
      payment_method_token = result.payment_method.token

      result = Braintree::Transaction.sale(
        :amount => "112.44",
        :payment_method_token => payment_method_token,
      )
      result.success?.should == true
      transaction = result.transaction

      transaction.amount.should == BigDecimal("112.44")
      transaction.processed_with_network_token?.should == false
    end
  end

  describe "retried flag presence in response" do
    it "creates a retried transaction" do
      result = Braintree::Transaction.sale(
        :amount => Braintree::Test::TransactionAmounts::Decline,
        :payment_method_token => "network_tokenized_credit_card",
      )
      transaction = result.transaction
      transaction.retried.should == true
    end

    it "creates a non-retried transaction" do
      result = Braintree::Transaction.sale(
        :amount => Braintree::Test::TransactionAmounts::Authorize,
        :payment_method_token => "network_tokenized_credit_card",
      )
      transaction = result.transaction
      transaction.retried.should == nil
    end

    it "creates a transaction that is ineligible for retries" do
      result = Braintree::Transaction.sale(
        :merchant_account_id => SpecHelper::NonDefaultMerchantAccountId,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
        :amount => Braintree::Test::TransactionAmounts::Authorize,
      )
      transaction = result.transaction
      transaction.retried.should == nil
    end
  end

  describe "installments" do
    it "creates a transaction with an installment count" do
      result = Braintree::Transaction.create(
        :type => "sale",
        :merchant_account_id => SpecHelper::CardProcessorBRLMerchantAccountId,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
        :amount => "100.01",
        :installments => {
          :count => 12,
        },
      )

      expect(result.success?).to eq(true)
      expect(result.transaction.installment_count).to eq(12)
    end

    it "creates a transaction with a installments during capture" do
      result = Braintree::Transaction.create(
        :type => "sale",
        :merchant_account_id => SpecHelper::CardProcessorBRLMerchantAccountId,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
        :amount => "100.01",
        :installments => {
          :count => 12,
        },
        :options => {
          :submit_for_settlement => true,
        },
      )

      expect(result.success?).to eq(true)
      transaction = result.transaction
      expect(transaction.installment_count).to eq(12)

      installments = transaction.installments
      expect(installments.map(&:id)).to match_array((1..12).map { |i| "#{transaction.id}_INST_#{i}" })
      expect(installments.map(&:amount)).to match_array([BigDecimal("8.33")] * 11 + [BigDecimal("8.38")])
    end

    it "can refund a transaction with installments" do
      sale_result = Braintree::Transaction.create(
        :type => "sale",
        :merchant_account_id => SpecHelper::CardProcessorBRLMerchantAccountId,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "05/2009"
        },
        :amount => "100.01",
        :installments => {
          :count => 12,
        },
        :options => {
          :submit_for_settlement => true,
        },
      )

      expect(sale_result.success?).to eq(true)
      sale_transaction = sale_result.transaction

      refund_result = Braintree::Transaction.refund(sale_transaction.id, "49.99")

      expect(refund_result.success?).to eq(true)
      refund_transaction = refund_result.transaction
      installments = refund_transaction.refunded_installments

      (1..11).each do |i|
        installment = installments.find { |installment| installment.id == "#{sale_transaction.id}_INST_#{i}" }

        expect(installment.amount).to eq(BigDecimal("8.33"))
        expect(installment.adjustments.map(&:amount)).to match_array([BigDecimal("-4.16")])
        expect(installment.adjustments.map(&:kind)).to match_array([Braintree::Transaction::Installment::Adjustment::Kind::Refund])
      end

      installment = installments.find { |installment| installment.id == "#{sale_transaction.id}_INST_12" }

      expect(installment.amount).to eq(BigDecimal("8.38"))
      expect(installment.adjustments.map(&:amount)).to match_array([BigDecimal("-4.23")])
      expect(installment.adjustments.map(&:kind)).to match_array([Braintree::Transaction::Installment::Adjustment::Kind::Refund])
    end
  end

  describe "Manual Key Entry" do
    context "with correct encrypted payment reader card details" do
      it "returns a success response" do
        result = Braintree::Transaction.sale(
          :amount => "10.00",
          :credit_card => {
            :payment_reader_card_details => {
              :encrypted_card_data => "8F34DFB312DC79C24FD5320622F3E11682D79E6B0C0FD881",
              :key_serial_number => "FFFFFF02000572A00005",
            },
          },
        )

        expect(result).to be_success
      end
    end

    context "with invalid encrypted payment reader card details" do
      it "returns a failure response" do
        result = Braintree::Transaction.sale(
          :amount => "10.00",
          :credit_card => {
            :payment_reader_card_details => {
              :encrypted_card_data => "invalid",
              :key_serial_number => "invalid",
            },
          },
        )

        expect(result).not_to be_success
        expect(result.errors.for(:transaction).first.code)
          .to eq(Braintree::ErrorCodes::Transaction::PaymentInstrumentNotSupportedByMerchantAccount)
      end
    end
  end

  describe "Adjust Authorization" do
    let(:first_data_master_transaction_params) do
      {
        :merchant_account_id => SpecHelper::FakeFirstDataMerchantAccountId,
        :amount => "75.50",
        :credit_card => {
          :number => "5105105105105100",
          :expiration_date => "05/2012"
        }
      }
    end
    let(:first_data_visa_transaction_params) do
      {
        :merchant_account_id => SpecHelper::FakeFirstDataMerchantAccountId,
        :amount => "75.50",
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::Visa,
          :expiration_date => "06/2009"
        }
      }
    end
    context "successful authorization" do
      it "returns success response" do
        initial_transaction = Braintree::Transaction.sale(first_data_master_transaction_params)
        expect(initial_transaction.success?).to eq(true)

        adjustment_transaction = Braintree::Transaction.adjust_authorization(
          initial_transaction.transaction.id, "85.50"
        )

        expect(adjustment_transaction.success?).to eq(true)
        expect(adjustment_transaction.transaction.amount.should).to eq(BigDecimal("85.50"))
      end
    end

    context "unsuccessful authorization" do
      it "returns failure, when processor does not support multi auth adjustment" do
        initial_transaction = Braintree::Transaction.sale(
          :merchant_account_id => SpecHelper::DefaultMerchantAccountId,
          :amount => "75.50",
          :credit_card => {
            :number => Braintree::Test::CreditCardNumbers::Visa,
            :expiration_date => "06/2009"
          },
        )
        expect(initial_transaction.success?).to eq(true)

        adjustment_transaction = Braintree::Transaction.adjust_authorization(
          initial_transaction.transaction.id, "85.50"
        )


        expect(adjustment_transaction.success?).to eq(false)
        expect(adjustment_transaction.transaction.amount.should).to eq(BigDecimal("75.50"))
        expect(adjustment_transaction.errors.for(:transaction).on(:base).first.code).to eq(Braintree::ErrorCodes::Transaction::ProcessorDoesNotSupportAuthAdjustment)
      end

      it "returns failure response, when adjusted amount submitted is zero" do
        initial_transaction = Braintree::Transaction.sale(first_data_master_transaction_params)
        expect(initial_transaction.success?).to eq(true)

        adjustment_transaction = Braintree::Transaction.adjust_authorization(
          initial_transaction.transaction.id, "0.0"
        )

        expect(adjustment_transaction.success?).to eq(false)
        expect(adjustment_transaction.transaction.amount.should).to eq(BigDecimal("75.50"))
        expect(adjustment_transaction.errors.for(:authorization_adjustment).on(:amount).first.code).to eq(Braintree::ErrorCodes::Transaction::AdjustmentAmountMustBeGreaterThanZero)
      end

      it "returns failure response, when adjusted amount submitted same as authorized amount" do
        initial_transaction = Braintree::Transaction.sale(first_data_master_transaction_params)
        expect(initial_transaction.success?).to eq(true)

        adjustment_transaction = Braintree::Transaction.adjust_authorization(
          initial_transaction.transaction.id, "75.50"
        )

        expect(adjustment_transaction.success?).to eq(false)
        expect(adjustment_transaction.transaction.amount.should).to eq(BigDecimal("75.50"))
        expect(adjustment_transaction.errors.for(:authorization_adjustment).on(:base).first.code).to eq(Braintree::ErrorCodes::Transaction::NoNetAmountToPerformAuthAdjustment)
      end

      it "returns failure, when transaction status is not authorized" do
        additional_params = {:options => {:submit_for_settlement => true}}
        initial_transaction = Braintree::Transaction.sale(first_data_master_transaction_params.merge(additional_params))
        expect(initial_transaction.success?).to eq(true)

        adjustment_transaction = Braintree::Transaction.adjust_authorization(
          initial_transaction.transaction.id, "85.50"
        )

        expect(adjustment_transaction.success?).to eq(false)
        expect(adjustment_transaction.transaction.amount.should).to eq(BigDecimal("75.50"))
        expect(adjustment_transaction.errors.for(:transaction).on(:base).first.code).to eq(Braintree::ErrorCodes::Transaction::TransactionMustBeInStateAuthorized)
      end

      it "returns failure, when transaction authorization type final or undefined" do
        additional_params = {:transaction_source => "recurring"}
        initial_transaction = Braintree::Transaction.sale(first_data_master_transaction_params.merge(additional_params))
        expect(initial_transaction.success?).to eq(true)

        adjustment_transaction = Braintree::Transaction.adjust_authorization(
          initial_transaction.transaction.id, "85.50"
        )

        expect(adjustment_transaction.success?).to eq(false)
        expect(adjustment_transaction.transaction.amount.should).to eq(BigDecimal("75.50"))
        expect(adjustment_transaction.errors.for(:transaction).on(:base).first.code).to eq(Braintree::ErrorCodes::Transaction::TransactionIsNotEligibleForAdjustment)
      end

      it "returns failure, when processor does not support incremental auth" do
        initial_transaction = Braintree::Transaction.sale(first_data_visa_transaction_params)
        expect(initial_transaction.success?).to eq(true)

        adjustment_transaction = Braintree::Transaction.adjust_authorization(
          initial_transaction.transaction.id, "85.50"
        )

        expect(adjustment_transaction.success?).to eq(false)
        expect(adjustment_transaction.transaction.amount.should).to eq(BigDecimal("75.50"))
        expect(adjustment_transaction.errors.for(:transaction).on(:base).first.code).to eq(Braintree::ErrorCodes::Transaction::ProcessorDoesNotSupportIncrementalAuth)
      end

      it "returns failure, when processor does not support auth reversal" do
        initial_transaction = Braintree::Transaction.sale(first_data_visa_transaction_params)
        expect(initial_transaction.success?).to eq(true)

        adjustment_transaction = Braintree::Transaction.adjust_authorization(
          initial_transaction.transaction.id, "65.50"
        )

        expect(adjustment_transaction.success?).to eq(false)
        expect(adjustment_transaction.transaction.amount.should).to eq(BigDecimal("75.50"))
        expect(adjustment_transaction.errors.for(:transaction).on(:base).first.code).to eq(Braintree::ErrorCodes::Transaction::ProcessorDoesNotSupportPartialAuthReversal)
      end
    end
  end

  context "Merchant Advice Code" do
    it "exposes MAC and MAC text" do
      result = Braintree::Transaction.create(
        :type => "sale",
        :amount => Braintree::Test::TransactionAmounts::Decline,
        :credit_card => {
          :number => Braintree::Test::CreditCardNumbers::MasterCard,
          :expiration_date => DateTime.now.strftime("%m/%Y")
        },
      )
      expect(result.transaction.merchant_advice_code).to eq("01")
      expect(result.transaction.merchant_advice_code_text).to eq("New account information available")
    end
  end
end