From 3a883be4b9dabe833954e37eeb403cc8c5fbc52f Mon Sep 17 00:00:00 2001 From: Braintree Date: Mon, 25 Jul 2022 14:59:25 +0000 Subject: [PATCH] 4.8.0 --- CHANGELOG.md | 9 ++ lib/braintree.rb | 23 +++-- lib/braintree/exchange_rate.rb | 13 +++ lib/braintree/exchange_rate_quote.rb | 24 +++++ lib/braintree/exchange_rate_quote_gateway.rb | 35 +++++++ lib/braintree/exchange_rate_quote_input.rb | 21 ++++ lib/braintree/exchange_rate_quote_request.rb | 18 ++++ lib/braintree/exchange_rate_quote_response.rb | 18 ++++ lib/braintree/gateway.rb | 4 + lib/braintree/plan_gateway.rb | 6 +- lib/braintree/risk_data.rb | 4 +- lib/braintree/risk_data/liability_shift.rb | 22 +++++ lib/braintree/successful_result.rb | 3 +- lib/braintree/transaction.rb | 41 ++++---- lib/braintree/transaction_search.rb | 3 +- lib/braintree/version.rb | 2 +- .../braintree/exchange_rate_quote_spec.rb | 97 +++++++++++++++++++ .../braintree/graphql_client_spec.rb | 2 - .../braintree/transaction_search_spec.rb | 68 +++++++++++++ .../integration/braintree/transaction_spec.rb | 30 +++++- spec/integration/spec_helper.rb | 6 ++ .../exchange_rate_quote_input_spec.rb | 42 ++++++++ .../exchange_rate_quote_request_spec.rb | 82 ++++++++++++++++ .../exchange_rate_quote_response_spec.rb | 52 ++++++++++ .../braintree/exchange_rate_quote_spec.rb | 42 ++++++++ spec/unit/braintree/exchange_rate_spec.rb | 23 +++++ .../braintree/risk_data/liability_shift.rb | 26 +++++ spec/unit/braintree/risk_data_spec.rb | 40 ++++++-- 28 files changed, 708 insertions(+), 48 deletions(-) create mode 100644 lib/braintree/exchange_rate.rb create mode 100644 lib/braintree/exchange_rate_quote.rb create mode 100644 lib/braintree/exchange_rate_quote_gateway.rb create mode 100644 lib/braintree/exchange_rate_quote_input.rb create mode 100644 lib/braintree/exchange_rate_quote_request.rb create mode 100644 lib/braintree/exchange_rate_quote_response.rb create mode 100644 lib/braintree/risk_data/liability_shift.rb create mode 100644 spec/integration/braintree/exchange_rate_quote_spec.rb create mode 100644 spec/unit/braintree/exchange_rate_quote_input_spec.rb create mode 100644 spec/unit/braintree/exchange_rate_quote_request_spec.rb create mode 100644 spec/unit/braintree/exchange_rate_quote_response_spec.rb create mode 100644 spec/unit/braintree/exchange_rate_quote_spec.rb create mode 100644 spec/unit/braintree/exchange_rate_spec.rb create mode 100644 spec/unit/braintree/risk_data/liability_shift.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index d7d056b6..9dee2d98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,13 @@ # Changelog + +## 4.8.0 +* Add `ach_return_responses` to `Transaction` for search results that search for transaction that have ach return response related data. +* Add `ach_return_responses_created_at` range field to to `TransactionSearch` +* Add `reason_code` to TransactionSearch to search for reason codes on transaction that have ach return responses associated with them. +* Add 'ExchangeRateQuoteAPI' +* Add LiabilityShift class and `liability_shift` field to RiskData +* Replace generic errors with api_error_response + ## 4.7.0 * Add `AchReturnCode` to transaction object * Add `retried` boolean to `Transaction` diff --git a/lib/braintree.rb b/lib/braintree.rb index 6b6fb4ae..f2f5261f 100644 --- a/lib/braintree.rb +++ b/lib/braintree.rb @@ -69,6 +69,12 @@ require "braintree/error_codes" require "braintree/error_result" require "braintree/errors" +require "braintree/exchange_rate" +require "braintree/exchange_rate_quote" +require "braintree/exchange_rate_quote_gateway" +require "braintree/exchange_rate_quote_input" +require "braintree/exchange_rate_quote_response" +require "braintree/exchange_rate_quote_request" require "braintree/gateway" require "braintree/graphql_client" require "braintree/google_pay_card" @@ -102,6 +108,7 @@ require "braintree/plan_gateway" require "braintree/processor_response_types" require "braintree/risk_data" +require "braintree/risk_data/liability_shift" require "braintree/facilitated_details" require "braintree/facilitator_details" require "braintree/three_d_secure_info" @@ -130,27 +137,27 @@ require "braintree/test/nonce" require "braintree/test/transaction_amounts" require "braintree/testing_gateway" -require "braintree/transaction" -require "braintree/transaction_line_item" require "braintree/test_transaction" +require "braintree/transaction" require "braintree/transaction/address_details" require "braintree/transaction/apple_pay_details" require "braintree/transaction/credit_card_details" require "braintree/transaction/customer_details" require "braintree/transaction/disbursement_details" require "braintree/transaction/google_pay_details" +require "braintree/transaction/installment" +require "braintree/transaction/installment/adjustment" require "braintree/transaction/paypal_details" require "braintree/transaction/paypal_here_details" +require "braintree/transaction/samsung_pay_card_details" +require "braintree/transaction/status_details" require "braintree/transaction/subscription_details" +require "braintree/transaction/venmo_account_details" +require "braintree/transaction/visa_checkout_card_details" require "braintree/transaction_gateway" +require "braintree/transaction_line_item" require "braintree/transaction_line_item_gateway" require "braintree/transaction_search" -require "braintree/transaction/status_details" -require "braintree/transaction/venmo_account_details" -require "braintree/transaction/visa_checkout_card_details" -require "braintree/transaction/samsung_pay_card_details" -require "braintree/transaction/installment" -require "braintree/transaction/installment/adjustment" require "braintree/unknown_payment_method" require "braintree/disbursement" require "braintree/dispute_search" diff --git a/lib/braintree/exchange_rate.rb b/lib/braintree/exchange_rate.rb new file mode 100644 index 00000000..0b941833 --- /dev/null +++ b/lib/braintree/exchange_rate.rb @@ -0,0 +1,13 @@ +module Braintree + class ExchangeRate + include BaseModule # :nodoc: + + def initialize(gateway, attributes) # :nodoc: + set_instance_variables_from_hash(attributes) + end + + def self.generate(exchange_rate_quote_request) + Configuration.gateway.exchange_rate_quote.generate(exchange_rate_quote_request) + end + end +end diff --git a/lib/braintree/exchange_rate_quote.rb b/lib/braintree/exchange_rate_quote.rb new file mode 100644 index 00000000..7cecc7ab --- /dev/null +++ b/lib/braintree/exchange_rate_quote.rb @@ -0,0 +1,24 @@ +module Braintree + class ExchangeRateQuote + include BaseModule # :nodoc: + + attr_reader :attrs + attr_reader :base_amount + attr_reader :exchange_rate + attr_reader :expires_at + attr_reader :id + attr_reader :quote_amount + attr_reader :refreshes_at + attr_reader :trade_rate + + def initialize(attributes) # :nodoc: + @attrs = attributes.keys + set_instance_variables_from_hash(attributes) + end + + def inspect # :nodoc: + inspected_attributes = @attrs.map { |attr| "#{attr}:#{send(attr).inspect}" } + "#<#{self.class} #{inspected_attributes.join(" ")}>" + end + end +end diff --git a/lib/braintree/exchange_rate_quote_gateway.rb b/lib/braintree/exchange_rate_quote_gateway.rb new file mode 100644 index 00000000..fc76dbff --- /dev/null +++ b/lib/braintree/exchange_rate_quote_gateway.rb @@ -0,0 +1,35 @@ +module Braintree + class ExchangeRateQuoteGateway # :nodoc + def initialize(gateway) + @gateway = gateway + end + + DEFINITION = <<-GRAPHQL + mutation GenerateExchangeRateQuoteInput($input: GenerateExchangeRateQuoteInput!) { + generateExchangeRateQuote(input: $input) { + quotes { + id + baseAmount {value, currencyCode} + quoteAmount {value, currencyCode} + exchangeRate + tradeRate + expiresAt + refreshesAt + } + } + } + GRAPHQL + + def generate(params) + response = @gateway.config.graphql_client.query(DEFINITION, {input: params}) + + if response.has_key?(:data) && response[:data][:generateExchangeRateQuote] + response[:data][:generateExchangeRateQuote] + elsif response[:errors] + ErrorResult.new(@gateway, response[:errors]) + else + raise UnexpectedError, "expected :generateExchangeRateQuote or :api_error_response in GraphQL response" + end + end + end +end diff --git a/lib/braintree/exchange_rate_quote_input.rb b/lib/braintree/exchange_rate_quote_input.rb new file mode 100644 index 00000000..9c786126 --- /dev/null +++ b/lib/braintree/exchange_rate_quote_input.rb @@ -0,0 +1,21 @@ +module Braintree + class ExchangeRateQuoteInput + include BaseModule # :nodoc: + + attr_reader :attrs + attr_reader :base_currency + attr_reader :base_amount + attr_reader :markup + attr_reader :quote_currency + + def initialize(attributes) # :nodoc: + @attrs = attributes.keys + set_instance_variables_from_hash(attributes) + end + + def inspect # :nodoc: + inspected_attributes = @attrs.map { |attr| "#{attr}:#{send(attr).inspect}" } + "#<#{self.class} #{inspected_attributes.join(" ")}>" + end + end +end diff --git a/lib/braintree/exchange_rate_quote_request.rb b/lib/braintree/exchange_rate_quote_request.rb new file mode 100644 index 00000000..3547b36b --- /dev/null +++ b/lib/braintree/exchange_rate_quote_request.rb @@ -0,0 +1,18 @@ +module Braintree + class ExchangeRateQuoteRequest + include BaseModule # :nodoc: + + attr_reader :quotes + + def initialize(attributes) # :nodoc: + @attrs = attributes.keys + set_instance_variables_from_hash(attributes) + @quotes = (@quotes || []).map { |quote_hash| ExchangeRateQuoteInput.new(quote_hash) } + end + + def inspect # :nodoc: + inspected_attributes = @attrs.map { |attr| "#{attr}:#{send(attr).inspect}" } + "#<#{self.class} #{inspected_attributes.join(" ")}>" + end + end +end diff --git a/lib/braintree/exchange_rate_quote_response.rb b/lib/braintree/exchange_rate_quote_response.rb new file mode 100644 index 00000000..aad029de --- /dev/null +++ b/lib/braintree/exchange_rate_quote_response.rb @@ -0,0 +1,18 @@ +module Braintree + class ExchangeRateQuoteResponse + include BaseModule # :nodoc: + + attr_reader :quotes + + def initialize(attributes) # :nodoc: + @attrs = attributes.keys + set_instance_variables_from_hash(attributes) + @quotes = (@quotes || []).map { |quote_hash| ExchangeRateQuote.new(quote_hash) } + end + + def inspect # :nodoc: + inspected_attributes = @attrs.map { |attr| "#{attr}:#{send(attr).inspect}" } + "#<#{self.class} #{inspected_attributes.join(" ")}>" + end + end +end diff --git a/lib/braintree/gateway.rb b/lib/braintree/gateway.rb index e249e3d0..5910ef55 100644 --- a/lib/braintree/gateway.rb +++ b/lib/braintree/gateway.rb @@ -50,6 +50,10 @@ def document_upload DocumentUploadGateway.new(self) end + def exchange_rate_quote + ExchangeRateQuoteGateway.new(self) + end + def oauth OAuthGateway.new(self) end diff --git a/lib/braintree/plan_gateway.rb b/lib/braintree/plan_gateway.rb index 0b4d84ab..ee3f1e8a 100644 --- a/lib/braintree/plan_gateway.rb +++ b/lib/braintree/plan_gateway.rb @@ -106,10 +106,10 @@ def _do_create(path, params) # :nodoc: response = @config.http.post("#{@config.base_merchant_path}#{path}", params) if response[:plan] SuccessfulResult.new(:plan => Plan._new(@gateway, response[:plan])) - elsif response[:errors] - ErrorResult.new(@gateway, response[:errors]) + elsif response[:api_error_response] + ErrorResult.new(@gateway, response[:api_error_response]) else - raise UnexpectedError, "expected :plan or :errors" + raise UnexpectedError, "expected :plan or :api_error_response" end end end diff --git a/lib/braintree/risk_data.rb b/lib/braintree/risk_data.rb index c10c844d..86376b67 100644 --- a/lib/braintree/risk_data.rb +++ b/lib/braintree/risk_data.rb @@ -10,14 +10,16 @@ class RiskData # :nodoc: attr_reader :device_data_captured attr_reader :fraud_service_provider attr_reader :id + attr_reader :liability_shift attr_reader :transaction_risk_score def initialize(attributes) set_instance_variables_from_hash attributes unless attributes.nil? + @liability_shift = LiabilityShift.new(attributes[:liability_shift]) if attributes[:liability_shift] end def inspect - attr_order = [:id, :decision, :decision_reasons, :device_data_captured, :fraud_service_provider, :transaction_risk_score] + attr_order = [:id, :decision, :decision_reasons, :device_data_captured, :fraud_service_provider, :liability_shift, :transaction_risk_score] formatted_attrs = attr_order.map do |attr| "#{attr}: #{send(attr).inspect}" end diff --git a/lib/braintree/risk_data/liability_shift.rb b/lib/braintree/risk_data/liability_shift.rb new file mode 100644 index 00000000..effbe3a3 --- /dev/null +++ b/lib/braintree/risk_data/liability_shift.rb @@ -0,0 +1,22 @@ +module Braintree + class RiskData + class LiabilityShift + include BaseModule + + attr_reader :responsible_party + attr_reader :conditions + + def initialize(attributes) + set_instance_variables_from_hash attributes unless attributes.nil? + end + + def inspect + attr_order = [:responsible_party, :conditions] + formatted_attrs = attr_order.map do |attr| + "#{attr}: #{send(attr).inspect}" + end + "#" + end + end + end +end diff --git a/lib/braintree/successful_result.rb b/lib/braintree/successful_result.rb index cff4f900..d5085553 100644 --- a/lib/braintree/successful_result.rb +++ b/lib/braintree/successful_result.rb @@ -6,10 +6,12 @@ class SuccessfulResult attr_reader :apple_pay_options attr_reader :credentials attr_reader :credit_card + attr_reader :credit_card_verification attr_reader :customer attr_reader :disputes attr_reader :document_upload attr_reader :evidence + attr_reader :exchange_rate_quote_payload attr_reader :merchant attr_reader :merchant_account attr_reader :merchant_accounts @@ -22,7 +24,6 @@ class SuccessfulResult attr_reader :supported_networks attr_reader :transaction attr_reader :us_bank_account_verification - attr_reader :credit_card_verification def initialize(attributes = {}) # :nodoc: @attrs = attributes.keys diff --git a/lib/braintree/transaction.rb b/lib/braintree/transaction.rb index a2bdba0c..583c2827 100644 --- a/lib/braintree/transaction.rb +++ b/lib/braintree/transaction.rb @@ -170,6 +170,7 @@ module Status attr_reader :venmo_account_details attr_reader :visa_checkout_card_details attr_reader :voice_referral_number + attr_reader :ach_return_responses def self.adjust_authorization(*args) Configuration.gateway.transaction.adjust_authorization(*args) @@ -290,38 +291,40 @@ def self.void!(*args) def initialize(gateway, attributes) # :nodoc: @gateway = gateway set_instance_variables_from_hash(attributes) + @amount = Util.to_big_decimal(amount) + @apple_pay_details = ApplePayDetails.new(@apple_pay) + @billing_details = AddressDetails.new(@billing) @credit_card_details = CreditCardDetails.new(@credit_card) - @service_fee_amount = Util.to_big_decimal(service_fee_amount) - @subscription_details = SubscriptionDetails.new(@subscription) + @custom_fields = attributes[:custom_fields].is_a?(Hash) ? attributes[:custom_fields] : {} @customer_details = CustomerDetails.new(@customer) - @billing_details = AddressDetails.new(@billing) - @disbursement_details = DisbursementDetails.new(@disbursement_details) - @shipping_details = AddressDetails.new(@shipping) - @status_history = attributes[:status_history] ? attributes[:status_history].map { |s| StatusDetails.new(s) } : [] - @tax_amount = Util.to_big_decimal(tax_amount) @descriptor = Descriptor.new(@descriptor) + @disbursement_details = DisbursementDetails.new(@disbursement_details) + @google_pay_details = GooglePayDetails.new(@google_pay_card) @local_payment_details = LocalPaymentDetails.new(@local_payment) + @payment_instrument_type = attributes[:payment_instrument_type] @paypal_details = PayPalDetails.new(@paypal) @paypal_here_details = PayPalHereDetails.new(@paypal_here) - @apple_pay_details = ApplePayDetails.new(@apple_pay) - @google_pay_details = GooglePayDetails.new(@google_pay_card) + @samsung_pay_card_details = SamsungPayCardDetails.new(attributes[:samsung_pay_card]) + @sca_exemption_requested = attributes[:sca_exemption_requested] + @service_fee_amount = Util.to_big_decimal(service_fee_amount) + @shipping_details = AddressDetails.new(@shipping) + @status_history = attributes[:status_history] ? attributes[:status_history].map { |s| StatusDetails.new(s) } : [] + @subscription_details = SubscriptionDetails.new(@subscription) + @tax_amount = Util.to_big_decimal(tax_amount) @venmo_account_details = VenmoAccountDetails.new(@venmo_account) - disputes.map! { |attrs| Dispute._new(attrs) } if disputes - @custom_fields = attributes[:custom_fields].is_a?(Hash) ? attributes[:custom_fields] : {} - add_ons.map! { |attrs| AddOn._new(attrs) } if add_ons - discounts.map! { |attrs| Discount._new(attrs) } if discounts - @payment_instrument_type = attributes[:payment_instrument_type] - @risk_data = RiskData.new(attributes[:risk_data]) if attributes[:risk_data] + @visa_checkout_card_details = VisaCheckoutCardDetails.new(attributes[:visa_checkout_card]) + @facilitated_details = FacilitatedDetails.new(attributes[:facilitated_details]) if attributes[:facilitated_details] @facilitator_details = FacilitatorDetails.new(attributes[:facilitator_details]) if attributes[:facilitator_details] + @risk_data = RiskData.new(attributes[:risk_data]) if attributes[:risk_data] @three_d_secure_info = ThreeDSecureInfo.new(attributes[:three_d_secure_info]) if attributes[:three_d_secure_info] @us_bank_account_details = UsBankAccountDetails.new(attributes[:us_bank_account]) if attributes[:us_bank_account] - @visa_checkout_card_details = VisaCheckoutCardDetails.new(attributes[:visa_checkout_card]) - @samsung_pay_card_details = SamsungPayCardDetails.new(attributes[:samsung_pay_card]) - @sca_exemption_requested = attributes[:sca_exemption_requested] - authorization_adjustments.map! { |attrs| AuthorizationAdjustment._new(attrs) } if authorization_adjustments + add_ons.map! { |attrs| AddOn._new(attrs) } if add_ons + authorization_adjustments.map! { |attrs| AuthorizationAdjustment._new(attrs) } if authorization_adjustments + discounts.map! { |attrs| Discount._new(attrs) } if discounts + disputes.map! { |attrs| Dispute._new(attrs) } if disputes installments.map! { |attrs| Installment.new(attrs) } if installments refunded_installments.map! { |attrs| Installment.new(attrs) } if refunded_installments end diff --git a/lib/braintree/transaction_search.rb b/lib/braintree/transaction_search.rb index 79726d70..3e914899 100644 --- a/lib/braintree/transaction_search.rb +++ b/lib/braintree/transaction_search.rb @@ -61,12 +61,13 @@ class TransactionSearch < AdvancedSearch # :nodoc: multiple_value_field :source multiple_value_field :type, :allows => Transaction::Type::All multiple_value_field :store_ids + multiple_value_field :reason_code key_value_fields :refund range_fields :amount, :created_at, :authorization_expired_at, :authorized_at, :failed_at, :gateway_rejected_at, :processor_declined_at, :settled_at, :submitted_for_settlement_at, :voided_at, - :disbursement_date, :dispute_date + :disbursement_date, :dispute_date, :ach_return_responses_created_at end end diff --git a/lib/braintree/version.rb b/lib/braintree/version.rb index dbbcedb0..01c8dd54 100644 --- a/lib/braintree/version.rb +++ b/lib/braintree/version.rb @@ -1,7 +1,7 @@ module Braintree module Version Major = 4 - Minor = 7 + Minor = 8 Tiny = 0 String = "#{Major}.#{Minor}.#{Tiny}" diff --git a/spec/integration/braintree/exchange_rate_quote_spec.rb b/spec/integration/braintree/exchange_rate_quote_spec.rb new file mode 100644 index 00000000..00ec9971 --- /dev/null +++ b/spec/integration/braintree/exchange_rate_quote_spec.rb @@ -0,0 +1,97 @@ +require File.expand_path(File.dirname(__FILE__) + "/../../spec_helper") +require File.expand_path(File.dirname(__FILE__) + "/client_api/spec_helper") + +describe Braintree::ExchangeRateQuoteGateway do + let(:gateway) do + Braintree::Gateway.new( + :environment => :development, + :merchant_id => "integration_merchant_id", + :public_key => "integration_public_key", + :private_key => "integration_private_key", + ) + end + + describe "generate" do + def quote_input_request + gateway.exchange_rate_quote.generate({quotes: [quote_input]}) + end + + let(:quote_input) do + { + :baseCurrency => "EUR", + :quoteCurrency => "GBP", + :baseAmount => "20.00", + :markup => "4.00" + } + end + + it "generates exchange rate quotes" do + result = quote_input_request + quotes = result[:quotes] + + expect(quotes[0][:id]).not_to be_nil + expect(quotes[0][:baseAmount]).not_to be_nil + expect(quotes[0][:quoteAmount]).not_to be_nil + expect(quotes[0][:exchangeRate]).not_to be_nil + expect(quotes[0][:expiresAt]).not_to be_nil + expect(quotes[0][:refreshesAt]).not_to be_nil + + expect(quotes[1][:id]).not_to be_nil + expect(quotes[1][:baseAmount]).not_to be_nil + expect(quotes[1][:quoteAmount]).not_to be_nil + expect(quotes[1][:exchangeRate]).not_to be_nil + expect(quotes[1][:expiresAt]).not_to be_nil + expect(quotes[1][:refreshesAt]).not_to be_nil + end + + context "when base currency input param is not passed" do + let(:quote_input) do + { + :quoteCurrency => "GBP", + :baseAmount => "20.00", + :markup => "4.00" + } + end + let(:error_message) { "baseCurrency" } + + it "raises an UnexpectedError" do + expect do + quote_input_request + end.to raise_error(Braintree::UnexpectedError, /#{error_message}/) + end + end + + context "when quote currency input param is not passed" do + let(:quote_input) do + { + :baseCurrency => "GBP", + :baseAmount => "20.00", + :markup => "4.00" + } + end + let(:error_message) { "quoteCurrency" } + + it "raises an UnexpectedError" do + expect do + quote_input_request + end.to raise_error(Braintree::UnexpectedError, /#{error_message}/) + end + end + + context "when base and quote currency input params are not passed" do + let(:quote_input) do + { + :baseAmount => "20.00", + :markup => "4.00" + } + end + let(:error_message) { "baseCurrency" } + + it "raises an UnexpectedError" do + expect do + quote_input_request + end.to raise_error(Braintree::UnexpectedError, /#{error_message}/) + end + end + end +end diff --git a/spec/integration/braintree/graphql_client_spec.rb b/spec/integration/braintree/graphql_client_spec.rb index 2db9b1b8..730d9ab0 100644 --- a/spec/integration/braintree/graphql_client_spec.rb +++ b/spec/integration/braintree/graphql_client_spec.rb @@ -28,7 +28,6 @@ definition = <<-GRAPHQL mutation CreateClientToken($input: CreateClientTokenInput!) { createClientToken(input: $input) { - clientMutationId clientToken } } @@ -36,7 +35,6 @@ variables = { input: { - clientMutationId: "abc123", clientToken: { merchantAccountId: "ABC123" } diff --git a/spec/integration/braintree/transaction_search_spec.rb b/spec/integration/braintree/transaction_search_spec.rb index e4093d99..b524a4f2 100644 --- a/spec/integration/braintree/transaction_search_spec.rb +++ b/spec/integration/braintree/transaction_search_spec.rb @@ -174,6 +174,29 @@ collection.first.id.should == transaction_id end + it "searches on reason_code" do + transaction_id = "ach_txn_ret1" + reason_code = "R01" + + collection = Braintree::Transaction.search do |search| + search.reason_code.in reason_code + end + + collection.maximum_size.should == 1 + collection.first.id.should == transaction_id + collection.first.ach_return_responses.first[:reason_code].should == "R01" + end + + it "searches on reason_codes" do + reason_code = "any_reason_code" + + collection = Braintree::Transaction.search do |search| + search.reason_code.is reason_code + end + + collection.maximum_size.should == 2 + end + context "multiple value fields" do it "searches on created_using" do transaction = Braintree::Transaction.sale!( @@ -532,6 +555,29 @@ collection.maximum_size.should == 1 collection.first.id.should == transaction_id end + + it "searches on reason_codes for 2 items" do + reason_code = ["R01", "R02"] + + collection = Braintree::Transaction.search do |search| + search.reason_code.in reason_code + end + + collection.maximum_size.should == 2 + end + + it "searches on a reason_code" do + reason_code = ["R01"] + transaction_id = "ach_txn_ret1" + + collection = Braintree::Transaction.search do |search| + search.reason_code.in reason_code + end + + collection.maximum_size.should == 1 + collection.first.id.should == transaction_id + end + end context "invalid search" do @@ -736,6 +782,28 @@ end end + context "ach return response created at" do + it "it finds records within date range of the custom field" do + reason_code = "any_reason_code" + + date_search = Braintree::Transaction.search do |search| + search.ach_return_responses_created_at.between(DateTime.now - 1.0, DateTime.now + 1.0) + end + + date_search.maximum_size.should == 2 + end + + it "it does not find records not within date range of the custom field" do + reason_code = "any_reason_code" + + neg_date_search = Braintree::Transaction.search do |search| + search.ach_return_responses_created_at.between(DateTime.now + 1.0, DateTime.now - 1.0) + end + + neg_date_search.maximum_size.should == 0 + end + end + context "disbursement_date" do it "searches on disbursement_date in UTC, as a date" do disbursement_time = Date.parse("2013-04-10") diff --git a/spec/integration/braintree/transaction_spec.rb b/spec/integration/braintree/transaction_spec.rb index 353e5f6b..82ced649 100644 --- a/spec/integration/braintree/transaction_spec.rb +++ b/spec/integration/braintree/transaction_spec.rb @@ -133,16 +133,36 @@ :expiration_date => "05/2009" }, ) - result.transaction.risk_data.should be_a(Braintree::RiskData) - result.transaction.risk_data.id.should_not be_nil - result.transaction.risk_data.decision.should_not be_nil - result.transaction.risk_data.decision_reasons.should_not be_nil + 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( @@ -160,7 +180,7 @@ :customer_tenure => "20#{"0" * 500}" }, ) - result.success?.should == false + 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 diff --git a/spec/integration/spec_helper.rb b/spec/integration/spec_helper.rb index 187be220..95c0d48e 100644 --- a/spec/integration/spec_helper.rb +++ b/spec/integration/spec_helper.rb @@ -54,6 +54,12 @@ def with_fraud_protection_enterprise_merchant(&block) end end + def with_chargeback_protection_merchant(&block) + with_other_merchant("fraud_protection_effortless_chargeback_protection_merchant_id", "effortless_chargeback_protection_public_key", "effortless_chargeback_protection_private_key") do + block.call + end + end + def with_altpay_merchant(&block) with_other_merchant("altpay_merchant", "altpay_merchant_public_key", "altpay_merchant_private_key", &block) end diff --git a/spec/unit/braintree/exchange_rate_quote_input_spec.rb b/spec/unit/braintree/exchange_rate_quote_input_spec.rb new file mode 100644 index 00000000..be2edc2f --- /dev/null +++ b/spec/unit/braintree/exchange_rate_quote_input_spec.rb @@ -0,0 +1,42 @@ +require File.expand_path(File.dirname(__FILE__) + "/../spec_helper") + +describe Braintree::ExchangeRateQuoteInput do + let(:exchange_rate_quote_input) do + { + base_currency: "USD", + quote_currency: "EUR", + base_amount: "10.00", + markup: "2.00" + } + end + + describe "#initialize" do + it "initialize and sets the input keys to attrs variable" do + quote_input = described_class.new(exchange_rate_quote_input) + + expect(quote_input.attrs).to include(:base_currency) + expect(quote_input.attrs).to include(:quote_currency) + expect(quote_input.attrs).to include(:base_amount) + expect(quote_input.attrs).to include(:markup) + expect(quote_input.attrs.length).to eq(4) + end + end + + describe "inspect" do + it "includes the base_currency first" do + output = described_class.new(base_currency: "USD").inspect + + expect(output).to include("#") + end + + it "includes all quote input attributes" do + quote_input = described_class.new(exchange_rate_quote_input) + output = quote_input.inspect + + expect(output).to include("base_currency:\"USD\"") + expect(output).to include("quote_currency:\"EUR\"") + expect(output).to include("base_amount:\"10.00\"") + expect(output).to include("markup:\"2.00\"") + end + end +end diff --git a/spec/unit/braintree/exchange_rate_quote_request_spec.rb b/spec/unit/braintree/exchange_rate_quote_request_spec.rb new file mode 100644 index 00000000..3c9e92e4 --- /dev/null +++ b/spec/unit/braintree/exchange_rate_quote_request_spec.rb @@ -0,0 +1,82 @@ +require File.expand_path(File.dirname(__FILE__) + "/../spec_helper") + +describe Braintree::ExchangeRateQuoteRequest do + describe "#initialize" do + it "creates and validates the exchange rate quote request" do + req = Braintree::ExchangeRateQuoteRequest.new( + :quotes => [ + { + :base_currency => "USD", + :quote_currency => "EUR", + :base_amount => "10.00", + :markup => "2.00" + }, + { + :base_currency => "EUR", + :quote_currency => "GBP", + :base_amount => "20.00", + :markup => "4.00" + } + ], + ) + + expect(req.quotes[0].base_currency).to eq("USD") + expect(req.quotes[0].quote_currency).to eq("EUR") + expect(req.quotes[0].base_amount).to eq("10.00") + expect(req.quotes[0].markup).to eq("2.00") + + expect(req.quotes[1].base_currency).to eq("EUR") + expect(req.quotes[1].quote_currency).to eq("GBP") + expect(req.quotes[1].base_amount).to eq("20.00") + expect(req.quotes[1].markup).to eq("4.00") + end + + it "creates and validates the exchange rate quote request without amount and markup" do + req = Braintree::ExchangeRateQuoteRequest.new( + :quotes => [ + { + :base_currency => "USD", + :quote_currency => "EUR", + }, + { + :base_currency => "EUR", + :quote_currency => "GBP", + } + ], + ) + + expect(req.quotes[0].base_currency).to eq("USD") + expect(req.quotes[0].quote_currency).to eq("EUR") + expect(req.quotes[0].base_amount).to be_nil + expect(req.quotes[0].markup).to be_nil + + expect(req.quotes[1].base_currency).to eq("EUR") + expect(req.quotes[1].quote_currency).to eq("GBP") + expect(req.quotes[1].base_amount).to be_nil + expect(req.quotes[1].markup).to be_nil + end + + end + + describe "inspect" do + it "prints the attributes" do + exchange_rate_req = Braintree::ExchangeRateQuoteRequest.new( + :quotes => [ + { + :base_currency => "USD", + :quote_currency => "EUR", + :base_amount => "10.00", + :markup => "2.00" + }, + { + :base_currency => "EUR", + :quote_currency => "GBP", + :base_amount => "20.00", + :markup => "4.00" + } + ], + ) + expect(exchange_rate_req.inspect).to eq(%(#, #]>)) + end + end +end diff --git a/spec/unit/braintree/exchange_rate_quote_response_spec.rb b/spec/unit/braintree/exchange_rate_quote_response_spec.rb new file mode 100644 index 00000000..6dd1537c --- /dev/null +++ b/spec/unit/braintree/exchange_rate_quote_response_spec.rb @@ -0,0 +1,52 @@ +require File.expand_path(File.dirname(__FILE__) + "/../spec_helper") + +describe Braintree::ExchangeRateQuoteResponse do + describe "#initialize" do + it "creates and validated the exchange rate quote payload" do + quote_payload = Braintree::ExchangeRateQuoteResponse.new( + quotes: [ + { + :base_amount => "10.00", + :quote_amount => "9.03", + :exchange_rate => "0.90" + }, + { + :base_amount => "20.00", + :quote_amount => "18.06", + :exchange_rate => "0.90" + } + ], + ) + + quote_1 = quote_payload.quotes[0] + quote_2 = quote_payload.quotes[1] + + expect(quote_1.base_amount).to eq("10.00") + expect(quote_1.quote_amount).to eq("9.03") + expect(quote_1.exchange_rate).to eq("0.90") + + expect(quote_2.base_amount).to eq("20.00") + expect(quote_2.quote_amount).to eq("18.06") + expect(quote_1.exchange_rate).to eq("0.90") + end + end + + describe "inspect" do + it "prints the attributes" do + exchange_rate_quote_payload = Braintree::ExchangeRateQuoteResponse.new( + quotes: [ + { + :base_amount => "10.00", + :quote_amount => "9.03" + }, + { + :base_amount => "20.00", + :quote_amount => "18.06" + } + ], + ) + + expect(exchange_rate_quote_payload.inspect).to eq(%(#, #]>)) + end + end +end diff --git a/spec/unit/braintree/exchange_rate_quote_spec.rb b/spec/unit/braintree/exchange_rate_quote_spec.rb new file mode 100644 index 00000000..762c77d5 --- /dev/null +++ b/spec/unit/braintree/exchange_rate_quote_spec.rb @@ -0,0 +1,42 @@ +require File.expand_path(File.dirname(__FILE__) + "/../spec_helper") + +describe Braintree::ExchangeRateQuote do + let(:rate_quote) do + { + id: "1234", + base_amount: "10.00", + exchange_rate: "74", + quote_amount: "740" + } + end + + describe "#initialize" do + it "initialize and sets the input keys to attrs variable" do + quote = described_class.new(rate_quote) + + expect(quote.attrs).to include(:id) + expect(quote.attrs).to include(:base_amount) + expect(quote.attrs).to include(:exchange_rate) + expect(quote.attrs).to include(:quote_amount) + expect(quote.attrs.length).to eq(4) + end + end + + describe "inspect" do + it "includes the id first" do + output = described_class.new(id: "1234").inspect + + expect(output).to include("#") + end + + it "includes all quote attributes" do + quote = described_class.new(rate_quote) + output = quote.inspect + + expect(output).to include("id:\"1234\"") + expect(output).to include("base_amount:\"10.00\"") + expect(output).to include("exchange_rate:\"74\"") + expect(output).to include("quote_amount:\"740\"") + end + end +end diff --git a/spec/unit/braintree/exchange_rate_spec.rb b/spec/unit/braintree/exchange_rate_spec.rb new file mode 100644 index 00000000..bac9ef61 --- /dev/null +++ b/spec/unit/braintree/exchange_rate_spec.rb @@ -0,0 +1,23 @@ +require File.expand_path(File.dirname(__FILE__) + "/../spec_helper") + +describe ::Braintree::ExchangeRate do + let(:rate_quote) do + { + id: "1234", + base_amount: "10.00", + exchange_rate: "74", + quote_amount: "740" + } + end + + describe "#initialize" do + it "initialize and sets the attributes" do + exchange_rate = described_class.new(:gateway, rate_quote).inspect + + expect(exchange_rate).to include("@id=\"1234\"") + expect(exchange_rate).to include("@base_amount=\"10.00\"") + expect(exchange_rate).to include("@exchange_rate=\"74\"") + expect(exchange_rate).to include("@quote_amount=\"740\"") + end + end +end diff --git a/spec/unit/braintree/risk_data/liability_shift.rb b/spec/unit/braintree/risk_data/liability_shift.rb new file mode 100644 index 00000000..b5ea6104 --- /dev/null +++ b/spec/unit/braintree/risk_data/liability_shift.rb @@ -0,0 +1,26 @@ +require File.expand_path(File.dirname(__FILE__) + "/../../spec_helper") + +describe Braintree::Transaction::LiabilityShift do + describe "#initialize" do + it "sets responsible party and conditions" do + liability_shift = Braintree::Transaction::LiabilityShift.new( + :responsible_party => "paypal", + :conditions => ["unauthorized","item_not_received"], + ) + + expect(liability_shift.responsible_party).to eql "paypal" + expect(liability_shift.conditions.first).to eql "unauthorized" + end + end + + describe "inspect" do + it "prints the attributes" do + details = Braintree::Transaction::LiabilityShift.new( + :responsible_party => "paypal", + :conditions => ["unauthorized","item_not_received"], + ) + + expect(details.inspect).to eql %(#) + end + end +end diff --git a/spec/unit/braintree/risk_data_spec.rb b/spec/unit/braintree/risk_data_spec.rb index d355edbd..8c4c704c 100644 --- a/spec/unit/braintree/risk_data_spec.rb +++ b/spec/unit/braintree/risk_data_spec.rb @@ -4,12 +4,29 @@ describe "#initialize" do it "sets id, decision, device_data_captured, decision_reasons and transaction_risk_score" do risk_data = Braintree::RiskData.new(:id => "123", :decision => "YOU WON $1000 DOLLARS", :device_data_captured => true, :fraud_service_provider => "fraud_protection", :decision_reasons => ["reason"], :transaction_risk_score => "12") - risk_data.id.should == "123" - risk_data.decision.should == "YOU WON $1000 DOLLARS" - risk_data.device_data_captured.should be_truthy - risk_data.fraud_service_provider.should == "fraud_protection" - risk_data.decision_reasons.should == ["reason"] - risk_data.transaction_risk_score.should == "12" + expect(risk_data.id).to eql "123" + expect(risk_data.decision).to eql "YOU WON $1000 DOLLARS" + expect(risk_data.device_data_captured).to be_truthy + expect(risk_data.fraud_service_provider).to eql "fraud_protection" + expect(risk_data.decision_reasons).to eql ["reason"] + expect(risk_data.transaction_risk_score).to eql "12" + expect(risk_data.liability_shift).to be_nil + end + + it "sets liability shift info" do + risk_data = Braintree::RiskData.new( + :id => "123", + :decision => "YOU WON $1000 DOLLARS", + :device_data_captured => true, + :fraud_service_provider => "fraud_protection", + :decision_reasons => ["reason"], + :transaction_risk_score => "12", + :liability_shift => { + :responsible_party => "paypal", + :conditions => ["unauthorized"]}, + ) + expect(risk_data.liability_shift.responsible_party).to eql "paypal" + expect(risk_data.liability_shift.conditions).to eql ["unauthorized"] end end @@ -23,7 +40,16 @@ :fraud_service_provider => "fraud_protection", :transaction_risk_score => "12", ) - details.inspect.should == %(#) + expect(details.inspect).to eql %(#) + end + + it "prints liability shift attributes, too" do + details = Braintree::RiskData.new( + :liability_shift => { + :responsible_party => "paypal", + :conditions => ["unauthorized"]}, + ) + expect(details.inspect).to eql %(#, transaction_risk_score: nil>) end end end