Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

2.1.0

  • Loading branch information...
commit d103526acfba4345427f87dc50fd08820a49f2f2 1 parent c96ee39
@braintreeps braintreeps authored
View
10 CHANGELOG.rdoc
@@ -1,3 +1,13 @@
+== 2.1.0
+
+* Added transaction advanced search
+* Added ability to partially refund transactions
+* Added ability to manually retry past-due subscriptions
+* Added new transaction error codes
+* Allow merchant account to be specified when creating transactions
+* Allow creating a transaction with a vault customer and new payment method
+* Allow existing billing address to be updated when updating credit card
+
== 2.0.0
* Updated success? on transaction responses to return false on declined transactions
View
6 README.rdoc
@@ -13,9 +13,9 @@ The Braintree gem provides integration access to the Braintree Gateway.
require "braintree"
Braintree::Configuration.environment = :sandbox
- Braintree::Configuration.merchant_id = "the_merchant_id"
- Braintree::Configuration.public_key = "a_public_key"
- Braintree::Configuration.private_key = "a_private_key"
+ Braintree::Configuration.merchant_id = "your_merchant_id"
+ Braintree::Configuration.public_key = "your_public_key"
+ Braintree::Configuration.private_key = "your_private_key"
result = Braintree::Transaction.sale(
:amount => "1000.00",
View
1  lib/braintree.rb
@@ -41,6 +41,7 @@ module Braintree
require "braintree/test/credit_card_numbers.rb"
require "braintree/test/transaction_amounts.rb"
require "braintree/transaction.rb"
+require "braintree/transaction_search.rb"
require "braintree/transaction/address_details.rb"
require "braintree/transaction/credit_card_details.rb"
require "braintree/transaction/customer_details.rb"
View
78 lib/braintree/advanced_search.rb
@@ -14,8 +14,22 @@ def initialize(name, parent)
end
end
- class TextNode < SearchNode
- operators :is, :is_not, :ends_with, :starts_with, :contains
+ class EqualityNode < SearchNode
+ operators :is, :is_not
+ end
+
+ class PartialMatchNode < EqualityNode
+ operators :ends_with, :starts_with
+ end
+
+ class TextNode < PartialMatchNode
+ operators :contains
+ end
+
+ class KeyValueNode < SearchNode
+ def is(value)
+ @parent.add_criteria(@node_name, value)
+ end
end
class MultipleValueNode < SearchNode
@@ -30,6 +44,10 @@ def in(*values)
@parent.add_criteria(@node_name, values)
end
+ def is(value)
+ self.in(value)
+ end
+
def initialize(name, parent, options)
super(name, parent)
@options = options
@@ -40,26 +58,70 @@ def allowed_values
end
end
- def self.search_fields(*fields)
- fields.each do |field|
- define_method(field) do
- TextNode.new(field, self)
- end
+ class RangeNode < SearchNode
+ def between(min, max)
+ self >= min
+ self <= max
+ end
+
+ def >=(min)
+ @parent.add_criteria(@node_name, :min => min)
+ end
+
+ def <=(max)
+ @parent.add_criteria(@node_name, :max => max)
end
end
+ def self.search_fields(*fields)
+ _create_field_accessors(fields, TextNode)
+ end
+
+ def self.equality_fields(*fields)
+ _create_field_accessors(fields, EqualityNode)
+ end
+
+ def self.partial_match_fields(*fields)
+ _create_field_accessors(fields, PartialMatchNode)
+ end
+
def self.multiple_value_field(field, options={})
define_method(field) do
MultipleValueNode.new(field, self, options)
end
end
+ def self.key_value_fields(*fields)
+ _create_field_accessors(fields, KeyValueNode)
+ end
+
+ def self.range_fields(*fields)
+ _create_field_accessors(fields, RangeNode)
+ end
+
+ def self.date_range_fields(*fields)
+ _create_field_accessors(fields, DateRangeNode)
+ end
+
+ def self._create_field_accessors(fields, node_class)
+ fields.each do |field|
+ define_method(field) do |*args|
+ raise "An operator is required" unless args.empty?
+ node_class.new(field, self)
+ end
+ end
+ end
+
def initialize
@criteria = {}
end
def add_criteria(key, value)
- @criteria[key] = value
+ if @criteria[key].is_a?(Hash)
+ @criteria[key].merge!(value)
+ else
+ @criteria[key] = value
+ end
end
def to_hash
View
47 lib/braintree/credit_card.rb
@@ -6,6 +6,29 @@ module Braintree
class CreditCard
include BaseModule # :nodoc:
+ module CardType
+ AmEx = "American Express"
+ CarteBlanche = "Carte Blanche"
+ ChinaUnionPay = "China UnionPay"
+ DinersClubInternational = "Diners Club"
+ Discover = "Discover"
+ JCB = "JCB"
+ Laser = "Laser"
+ Maestro = "Maestro"
+ MasterCard = "MasterCard"
+ Solo = "Solo"
+ Switch = "Switch"
+ Visa = "Visa"
+ Unknown = "Unknown"
+
+ All = constants.map { |c| const_get(c) }
+ end
+
+ module CustomerLocation
+ International = "international"
+ US = "us"
+ end
+
attr_reader :billing_address, :bin, :card_type, :cardholder_name, :created_at, :customer_id, :expiration_month,
:expiration_year, :last_4, :subscriptions, :token, :updated_at
@@ -103,7 +126,7 @@ def self.update_credit_card_url
def initialize(attributes) # :nodoc:
_init attributes
- @subscriptions = (@subscriptions || []).map { |subscription_hash| Subscription.new(subscription_hash) }
+ @subscriptions = (@subscriptions || []).map { |subscription_hash| Subscription._new(subscription_hash) }
end
# Creates a credit transaction for this credit card.
@@ -196,7 +219,7 @@ def self._attributes # :nodoc:
end
def self._create_signature # :nodoc:
- _update_signature + [:customer_id]
+ _signature(:create)
end
def self._new(*args) # :nodoc:
@@ -226,11 +249,27 @@ def self._do_update(http_verb, url, params) # :nodoc:
end
def self._update_signature # :nodoc:
- [
+ _signature(:update)
+ end
+
+ def self._signature(type) # :nodoc:
+ billing_address_params = [:company, :country_name, :extended_address, :first_name, :last_name, :locality, :postal_code, :region, :street_address]
+ signature = [
:cardholder_name, :cvv, :expiration_date, :expiration_month, :expiration_year, :number, :token,
{:options => [:make_default, :verify_card]},
- {:billing_address => [:company, :country_name, :extended_address, :first_name, :last_name, :locality, :postal_code, :region, :street_address]}
+ {:billing_address => billing_address_params}
]
+
+ case type
+ when :create
+ signature << :customer_id
+ when :update
+ billing_address_params << {:options => [:update_existing]}
+ else
+ raise ArgumentError
+ end
+
+ return signature
end
def _init(attributes) # :nodoc:
View
7 lib/braintree/error_codes.rb
@@ -11,6 +11,7 @@ module Address
FirstNameIsTooLong = "81805"
LastNameIsTooLong = "81806"
LocalityIsTooLong = "81807"
+ PostalCodeInvalidCharacters = "81813"
PostalCodeIsRequired = "81808"
PostalCodeIsTooLong = "81809"
RegionIsTooLong = "81810"
@@ -87,6 +88,7 @@ module Transaction
CreditCardIsRequired = "91508"
CustomerDefaultPaymentMethodCardTypeIsNotAccepted = "81509"
CustomFieldIsInvalid = "91526"
+ CustomFieldIsTooLong = "81527"
CustomerIdIsInvalid = "91510"
CustomerDoesNotHaveCreditCard = "91511"
HasAlreadyBeenRefunded = "91512"
@@ -95,10 +97,15 @@ module Transaction
OrderIdIsTooLong = "91501"
PaymentMethodConflict = "91515"
PaymentMethodDoesNotBelongToCustomer = "91516"
+ PaymentMethodDoesNotBelongToSubscription = "91527"
PaymentMethodTokenCardTypeIsNotAccepted = "91517"
PaymentMethodTokenIsInvalid = "91518"
+ ProcessorAuthorizationCodeCannotBeSet = "91519"
+ ProcessorAuthorizationCodeIsInvalid = "81520"
RefundAmountIsTooLarge = "91521"
SettlementAmountIsTooLarge = "91522"
+ SubscriptionDoesNotBelongToCustomer = "91529"
+ SubscriptionIdIsInvalid = "91528"
TypeIsInvalid = "91523"
TypeIsRequired = "91524"
module Options
View
26 lib/braintree/subscription.rb
@@ -47,7 +47,7 @@ module TrialDurationUnit
def self.cancel(subscription_id)
response = Http.put "/subscriptions/#{subscription_id}/cancel"
if response[:subscription]
- SuccessfulResult.new(:subscription => new(response[:subscription]))
+ SuccessfulResult.new(:subscription => _new(response[:subscription]))
elsif response[:api_error_response]
ErrorResult.new(response[:api_error_response])
else
@@ -66,11 +66,21 @@ def self.create(attributes)
# if the subscription cannot be found.
def self.find(id)
response = Http.get "/subscriptions/#{id}"
- new(response[:subscription])
+ _new(response[:subscription])
rescue NotFoundError
raise NotFoundError, "subscription with id #{id.inspect} not found"
end
+ def self.retry_charge(subscription_id, amount=nil)
+ attributes = {
+ :amount => amount,
+ :subscription_id => subscription_id,
+ :type => Transaction::Type::Sale
+ }
+
+ Transaction.send(:_do_create, "/transactions", :transaction => attributes)
+ end
+
# Allows searching on subscriptions. There are two types of fields that are searchable: text and
# multiple value fields. Searchable text fields are:
# - plan_id
@@ -93,7 +103,7 @@ def self.search(page=1, &block)
response = Http.post "/subscriptions/advanced_search?page=#{page}", {:search => search.to_hash}
attributes = response[:subscriptions]
- attributes[:items] = Util.extract_attribute_as_array(attributes, :subscription).map { |attrs| new(attrs) }
+ attributes[:items] = Util.extract_attribute_as_array(attributes, :subscription).map { |attrs| _new(attrs) }
ResourceCollection.new(attributes) { |page_number| Subscription.search(page_number, &block) }
end
@@ -101,7 +111,7 @@ def self.update(subscription_id, attributes)
Util.verify_keys(_update_signature, attributes)
response = Http.put "/subscriptions/#{subscription_id}", :subscription => attributes
if response[:subscription]
- SuccessfulResult.new(:subscription => new(response[:subscription]))
+ SuccessfulResult.new(:subscription => _new(response[:subscription]))
elsif response[:api_error_response]
ErrorResult.new(response[:api_error_response])
else
@@ -130,9 +140,17 @@ def initialize(attributes) # :nodoc:
# True if <tt>other</tt> has the same id.
def ==(other)
+ return false unless other.is_a?(Subscription)
id == other.id
end
+ class << self
+ protected :new
+ def _new(*args) # :nodoc:
+ self.new *args
+ end
+ end
+
def self._do_create(url, params) # :nodoc:
response = Http.post url, params
if response[:subscription]
View
51 lib/braintree/transaction.rb
@@ -123,6 +123,11 @@ module Braintree
class Transaction
include BaseModule
+ module CreatedUsing
+ FullInformation = 'full_information'
+ Token = 'token'
+ end
+
module Status
Authorizing = 'authorizing'
Authorized = 'authorized'
@@ -134,6 +139,14 @@ module Status
SubmittedForSettlement = 'submitted_for_settlement'
Unknown = 'unknown'
Voided = 'voided'
+
+ All = constants.map { |c| const_get(c) }
+ end
+
+ module Source
+ Api = "api"
+ ControlPanel = "control_panel"
+ Recurring = "recurring"
end
module Type # :nodoc:
@@ -145,6 +158,7 @@ module Type # :nodoc:
attr_reader :amount, :created_at, :credit_card_details, :customer_details, :id
attr_reader :custom_fields
attr_reader :cvv_response_code
+ attr_reader :merchant_account_id
attr_reader :order_id
attr_reader :billing_details, :shipping_details
# The authorization code from the processor.
@@ -209,13 +223,13 @@ def self.sale!(attributes)
# Returns a ResourceCollection of transactions matching the search query.
# If <tt>query</tt> is a string, the search will be a basic search.
# If <tt>query</tt> is a hash, the search will be an advanced search.
- def self.search(query, options = {})
- if query.is_a?(String)
- _basic_search query, options
- elsif query.is_a?(Hash)
- _advanced_search query, options
+ def self.search(query = nil, page=1, &block)
+ if block_given?
+ _advanced_search page, &block
+ elsif query.is_a?(String)
+ _basic_search query, page
else
- raise ArgumentError, "expected query to be a string or a hash"
+ raise ArgumentError, "expected search to be a string or a block"
end
end
@@ -256,8 +270,9 @@ def initialize(attributes) # :nodoc:
_init attributes
end
- # True if <tt>other</tt> has the same id.
+ # True if <tt>other</tt> is a Braintree::Transaction with the same id.
def ==(other)
+ return false unless other.is_a?(Transaction)
id == other.id
end
@@ -275,8 +290,8 @@ def inspect # :nodoc:
end
# Creates a credit transaction that refunds this transaction.
- def refund
- response = Http.post "/transactions/#{id}/refund"
+ def refund(amount = nil)
+ response = Http.post "/transactions/#{id}/refund", :transaction => {:amount => amount}
if response[:transaction]
# TODO: need response to return original_transaction so that we can update status, updated_at, etc.
SuccessfulResult.new(:new_transaction => Transaction._new(response[:transaction]))
@@ -380,29 +395,31 @@ def self._do_create(url, params) # :nodoc:
end
end
- def self._advanced_search(query, options) # :nodoc:
- page = options[:page] || 1
- response = Http.post "/transactions/advanced_search?page=#{Util.url_encode(page)}", :search => query
+ def self._advanced_search(page, &block) # :nodoc:
+ search = TransactionSearch.new
+ block.call(search)
+
+ response = Http.post "/transactions/advanced_search?page=#{page}", {:search => search.to_hash}
attributes = response[:credit_card_transactions]
attributes[:items] = Util.extract_attribute_as_array(attributes, :transaction).map { |attrs| _new(attrs) }
- ResourceCollection.new(attributes) { |page_number| Transaction.search(query, :page => page_number) }
+
+ ResourceCollection.new(attributes) { |page_number| Transaction.search(nil, page_number, &block) }
end
def self._attributes # :nodoc:
[:amount, :created_at, :credit_card_details, :customer_details, :id, :status, :type, :updated_at]
end
- def self._basic_search(query, options) # :nodoc:
- page = options[:page] || 1
+ def self._basic_search(query, page) # :nodoc:
response = Http.get "/transactions/all/search?q=#{Util.url_encode(query)}&page=#{Util.url_encode(page)}"
attributes = response[:credit_card_transactions]
attributes[:items] = Util.extract_attribute_as_array(attributes, :transaction).map { |attrs| _new(attrs) }
- ResourceCollection.new(attributes) { |page_number| Transaction.search(query, :page => page_number) }
+ ResourceCollection.new(attributes) { |page_number| Transaction.search(query, page_number) }
end
def self._create_signature # :nodoc:
[
- :amount, :customer_id, :order_id, :payment_method_token, :type,
+ :amount, :customer_id, :merchant_account_id, :order_id, :payment_method_token, :type,
{:credit_card => [:token, :cardholder_name, :cvv, :expiration_date, :expiration_month, :expiration_year, :number]},
{:customer => [:id, :company, :email, :fax, :first_name, :last_name, :phone, :website]},
{:billing => [:first_name, :last_name, :company, :country_name, :extended_address, :locality, :postal_code, :region, :street_address]},
View
63 lib/braintree/transaction_search.rb
@@ -0,0 +1,63 @@
+module Braintree
+ class TransactionSearch < AdvancedSearch
+ search_fields(
+ :billing_company,
+ :billing_country_name,
+ :billing_extended_address,
+ :billing_first_name,
+ :billing_last_name,
+ :billing_locality,
+ :billing_postal_code,
+ :billing_region,
+ :billing_street_address,
+ :credit_card_cardholder_name,
+ :currency,
+ :customer_company,
+ :customer_email,
+ :customer_fax,
+ :customer_first_name,
+ :customer_id,
+ :customer_last_name,
+ :customer_phone,
+ :customer_website,
+ :id,
+ :order_id,
+ :payment_method_token,
+ :processor_authorization_code,
+ :shipping_company,
+ :shipping_country_name,
+ :shipping_extended_address,
+ :shipping_first_name,
+ :shipping_last_name,
+ :shipping_locality,
+ :shipping_postal_code,
+ :shipping_region,
+ :shipping_street_address
+ )
+
+ equality_fields :credit_card_expiration_date
+ partial_match_fields :credit_card_number
+
+ multiple_value_field :created_using, :allows => [
+ Transaction::CreatedUsing::FullInformation,
+ Transaction::CreatedUsing::Token
+ ]
+ multiple_value_field :credit_card_card_type, :allows => CreditCard::CardType::All
+ multiple_value_field :credit_card_customer_location, :allows => [
+ CreditCard::CustomerLocation::International,
+ CreditCard::CustomerLocation::US
+ ]
+ multiple_value_field :merchant_account_id
+ multiple_value_field :status, :allows => Transaction::Status::All
+ multiple_value_field :source, :allows => [
+ Transaction::Source::Api,
+ Transaction::Source::ControlPanel,
+ Transaction::Source::Recurring
+ ]
+ multiple_value_field :type
+
+ key_value_fields :refund
+
+ range_fields :amount, :created_at
+ end
+end
View
1  lib/braintree/util.rb
@@ -1,6 +1,7 @@
module Braintree
module Util # :nodoc:
def self.extract_attribute_as_array(hash, attribute)
+ raise UnexpectedError.new("Unprocessable entity due to an invalid request") if hash.nil?
value = hash.has_key?(attribute) ? hash.delete(attribute) : []
value.is_a?(Array) ? value : [value]
end
View
2  lib/braintree/version.rb
@@ -1,7 +1,7 @@
module Braintree
module Version
Major = 2
- Minor = 0
+ Minor = 1
Tiny = 0
String = "#{Major}.#{Minor}.#{Tiny}"
View
20 lib/braintree/xml/generator.rb
@@ -11,9 +11,22 @@ module Generator # :nodoc:
"DateTime" => "datetime",
"Time" => "datetime",
}
+
+ XML_FORMATTING_NAMES = {
+ "BigDecimal" => "bigdecimal",
+ "Symbol" => "symbol"
+ }.merge(XML_TYPE_NAMES)
+
XML_FORMATTING = {
- "symbol" => Proc.new { |symbol| symbol.to_s },
- "datetime" => Proc.new { |time| time.xmlschema },
+ "symbol" => Proc.new { |symbol| symbol.to_s },
+ "datetime" => Proc.new { |time| time.xmlschema },
+ "bigdecimal" => Proc.new do |bigdecimal|
+ str = bigdecimal.to_s('F')
+ if str =~ /\.\d$/
+ str += "0"
+ end
+ str
+ end
}
def self.hash_to_xml(hash)
@@ -49,8 +62,9 @@ def self._convert_to_xml(hash_to_convert, options = {})
attributes[:nil] = true
end
+ formatting_name = XML_FORMATTING_NAMES[value.class.name]
options[:builder].tag!(_xml_escape(key),
- XML_FORMATTING[type_name] ? XML_FORMATTING[type_name].call(value) : value,
+ XML_FORMATTING[formatting_name] ? XML_FORMATTING[formatting_name].call(value) : value,
attributes
)
end
View
47 spec/integration/braintree/credit_card_spec.rb
@@ -326,6 +326,53 @@
updated_credit_card.cardholder_name.should == "New Holder"
end
+ context "billing address" do
+ it "creates a new billing address by default" do
+ customer = Braintree::Customer.create!
+ credit_card = Braintree::CreditCard.create!(
+ :customer_id => customer.id,
+ :number => Braintree::Test::CreditCardNumbers::Visa,
+ :expiration_date => "05/2012",
+ :billing_address => {
+ :street_address => "123 Nigeria Ave"
+ }
+ )
+ update_result = Braintree::CreditCard.update(credit_card.token,
+ :billing_address => {
+ :region => "IL"
+ }
+ )
+ update_result.success?.should == true
+ updated_credit_card = update_result.credit_card
+ updated_credit_card.billing_address.region.should == "IL"
+ updated_credit_card.billing_address.street_address.should == nil
+ updated_credit_card.billing_address.id.should_not == credit_card.billing_address.id
+ end
+
+ it "updates the billing address if option is specified" do
+ customer = Braintree::Customer.create!
+ credit_card = Braintree::CreditCard.create!(
+ :customer_id => customer.id,
+ :number => Braintree::Test::CreditCardNumbers::Visa,
+ :expiration_date => "05/2012",
+ :billing_address => {
+ :street_address => "123 Nigeria Ave"
+ }
+ )
+ update_result = Braintree::CreditCard.update(credit_card.token,
+ :billing_address => {
+ :region => "IL",
+ :options => {:update_existing => true}
+ }
+ )
+ update_result.success?.should == true
+ updated_credit_card = update_result.credit_card
+ updated_credit_card.billing_address.region.should == "IL"
+ updated_credit_card.billing_address.street_address.should == "123 Nigeria Ave"
+ updated_credit_card.billing_address.id.should == credit_card.billing_address.id
+ end
+ end
+
it "can pass expiration_month and expiration_year" do
customer = Braintree::Customer.create!
credit_card = Braintree::CreditCard.create!(
View
47 spec/integration/braintree/subscription_spec.rb
@@ -17,9 +17,6 @@
:trial_period => false
}
- DefaultMerchantAccountId = "sandbox_credit_card"
- NonDefaultMerchantAccountId = "sandbox_credit_card_non_default"
-
before(:each) do
@credit_card = Braintree::Customer.create!(
:credit_card => {
@@ -72,18 +69,18 @@
)
result.success?.should == true
- result.subscription.merchant_account_id.should == DefaultMerchantAccountId
+ result.subscription.merchant_account_id.should == SpecHelper::DefaultMerchantAccountId
end
it "allows setting the merchant_account_id" do
result = Braintree::Subscription.create(
:payment_method_token => @credit_card.token,
:plan_id => TriallessPlan[:id],
- :merchant_account_id => NonDefaultMerchantAccountId
+ :merchant_account_id => SpecHelper::NonDefaultMerchantAccountId
)
result.success?.should == true
- result.subscription.merchant_account_id.should == NonDefaultMerchantAccountId
+ result.subscription.merchant_account_id.should == SpecHelper::NonDefaultMerchantAccountId
end
end
@@ -263,11 +260,11 @@
context "merchant_account_id" do
it "allows changing the merchant_account_id" do
result = Braintree::Subscription.update(@subscription.id,
- :merchant_account_id => NonDefaultMerchantAccountId
+ :merchant_account_id => SpecHelper::NonDefaultMerchantAccountId
)
result.success?.should == true
- result.subscription.merchant_account_id.should == NonDefaultMerchantAccountId
+ result.subscription.merchant_account_id.should == SpecHelper::NonDefaultMerchantAccountId
end
end
@@ -616,4 +613,38 @@
end
end
end
+
+ describe "self.retry_charge" do
+ it "is successful with only subscription id" do
+ subscription = Braintree::Subscription.search do |search|
+ search.status.in Braintree::Subscription::Status::PastDue
+ end.first
+
+ result = Braintree::Subscription.retry_charge(subscription.id)
+
+ result.success?.should == true
+ transaction = result.transaction
+
+ transaction.amount.should == subscription.price
+ transaction.processor_authorization_code.should_not be_nil
+ transaction.type.should == Braintree::Transaction::Type::Sale
+ transaction.status.should == Braintree::Transaction::Status::Authorized
+ end
+
+ it "is successful with subscription id and amount" do
+ subscription = Braintree::Subscription.search do |search|
+ search.status.in Braintree::Subscription::Status::PastDue
+ end.first
+
+ result = Braintree::Subscription.retry_charge(subscription.id, Braintree::Test::TransactionAmounts::Authorize)
+
+ result.success?.should == true
+ transaction = result.transaction
+
+ transaction.amount.should == BigDecimal.new(Braintree::Test::TransactionAmounts::Authorize)
+ transaction.processor_authorization_code.should_not be_nil
+ transaction.type.should == Braintree::Transaction::Type::Sale
+ transaction.status.should == Braintree::Transaction::Status::Authorized
+ end
+ end
end
View
734 spec/integration/braintree/transaction_search_spec.rb
@@ -0,0 +1,734 @@
+require File.dirname(__FILE__) + "/../spec_helper"
+
+describe Braintree::Transaction, "search" do
+ context "advanced" do
+ it "correctly returns a result with no matches" do
+ collection = Braintree::Transaction.search do |search|
+ search.billing_first_name.is "thisnameisnotreal"
+ end
+
+ collection._approximate_size.should == 0
+ end
+
+ it "returns one result for text field search" do
+ first_name = "Tim#{rand(10000)}"
+ token = "creditcard#{rand(10000)}"
+ customer_id = "customer#{rand(10000)}"
+
+ transaction = Braintree::Transaction.sale!(
+ :amount => Braintree::Test::TransactionAmounts::Authorize,
+ :credit_card => {
+ :number => Braintree::Test::CreditCardNumbers::Visa,
+ :expiration_date => "05/2012",
+ :cardholder_name => "Tom Smith",
+ :token => token,
+ },
+ :billing => {
+ :company => "Braintree",
+ :country_name => "United States of America",
+ :extended_address => "Suite 123",
+ :first_name => first_name,
+ :last_name => "Smith",
+ :locality => "Chicago",
+ :postal_code => "12345",
+ :region => "IL",
+ :street_address => "123 Main St"
+ },
+ :customer => {
+ :company => "Braintree",
+ :email => "smith@example.com",
+ :fax => "5551231234",
+ :first_name => "Tom",
+ :id => customer_id,
+ :last_name => "Smith",
+ :phone => "5551231234",
+ :website => "http://example.com",
+ },
+ :options => {
+ :store_in_vault => true
+ },
+ :order_id => "myorder",
+ :shipping => {
+ :company => "Braintree P.S.",
+ :country_name => "Mexico",
+ :extended_address => "Apt 456",
+ :first_name => "Thomas",
+ :last_name => "Smithy",
+ :locality => "Braintree",
+ :postal_code => "54321",
+ :region => "MA",
+ :street_address => "456 Road"
+ }
+ )
+
+ search_criteria = {
+ :billing_company => "Braintree",
+ :billing_country_name => "United States of America",
+ :billing_extended_address => "Suite 123",
+ :billing_first_name => first_name,
+ :billing_last_name => "Smith",
+ :billing_locality => "Chicago",
+ :billing_postal_code => "12345",
+ :billing_region => "IL",
+ :billing_street_address => "123 Main St",
+ :credit_card_cardholder_name => "Tom Smith",
+ :credit_card_expiration_date => "05/2012",
+ :credit_card_number => Braintree::Test::CreditCardNumbers::Visa,
+ :customer_company => "Braintree",
+ :customer_email => "smith@example.com",
+ :customer_fax => "5551231234",
+ :customer_first_name => "Tom",
+ :customer_id => customer_id,
+ :customer_last_name => "Smith",
+ :customer_phone => "5551231234",
+ :customer_website => "http://example.com",
+ :order_id => "myorder",
+ :payment_method_token => token,
+ :processor_authorization_code => transaction.processor_authorization_code,
+ :shipping_company => "Braintree P.S.",
+ :shipping_country_name => "Mexico",
+ :shipping_extended_address => "Apt 456",
+ :shipping_first_name => "Thomas",
+ :shipping_last_name => "Smithy",
+ :shipping_locality => "Braintree",
+ :shipping_postal_code => "54321",
+ :shipping_region => "MA",
+ :shipping_street_address => "456 Road"
+ }
+
+ search_criteria.each do |criterion, value|
+ collection = Braintree::Transaction.search do |search|
+ search.id.is transaction.id
+ search.send(criterion).is value
+ end
+ collection._approximate_size.should == 1
+ collection.first.id.should == transaction.id
+
+ collection = Braintree::Transaction.search do |search|
+ search.id.is transaction.id
+ search.send(criterion).is("invalid_attribute")
+ end
+ collection.should be_empty
+ end
+ end
+
+ it "searches all fields at once" do
+ first_name = "Tim#{rand(10000)}"
+ token = "creditcard#{rand(10000)}"
+ customer_id = "customer#{rand(10000)}"
+
+ transaction = Braintree::Transaction.sale!(
+ :amount => Braintree::Test::TransactionAmounts::Authorize,
+ :credit_card => {
+ :number => Braintree::Test::CreditCardNumbers::Visa,
+ :expiration_date => "05/2012",
+ :cardholder_name => "Tom Smith",
+ :token => token,
+ },
+ :billing => {
+ :company => "Braintree",
+ :country_name => "United States of America",
+ :extended_address => "Suite 123",
+ :first_name => first_name,
+ :last_name => "Smith",
+ :locality => "Chicago",
+ :postal_code => "12345",
+ :region => "IL",
+ :street_address => "123 Main St"
+ },
+ :customer => {
+ :company => "Braintree",
+ :email => "smith@example.com",
+ :fax => "5551231234",
+ :first_name => "Tom",
+ :id => customer_id,
+ :last_name => "Smith",
+ :phone => "5551231234",
+ :website => "http://example.com",
+ },
+ :options => {
+ :store_in_vault => true
+ },
+ :order_id => "myorder",
+ :shipping => {
+ :company => "Braintree P.S.",
+ :country_name => "Mexico",
+ :extended_address => "Apt 456",
+ :first_name => "Thomas",
+ :last_name => "Smithy",
+ :locality => "Braintree",
+ :postal_code => "54321",
+ :region => "MA",
+ :street_address => "456 Road"
+ })
+
+ collection = Braintree::Transaction.search do |search|
+ search.billing_company.is "Braintree"
+ search.billing_country_name.is "United States of America"
+ search.billing_extended_address.is "Suite 123"
+ search.billing_first_name.is first_name
+ search.billing_last_name.is "Smith"
+ search.billing_locality.is "Chicago"
+ search.billing_postal_code.is "12345"
+ search.billing_region.is "IL"
+ search.billing_street_address.is "123 Main St"
+ search.credit_card_cardholder_name.is "Tom Smith"
+ search.credit_card_expiration_date.is "05/2012"
+ search.credit_card_number.is Braintree::Test::CreditCardNumbers::Visa
+ search.customer_company.is "Braintree"
+ search.customer_email.is "smith@example.com"
+ search.customer_fax.is "5551231234"
+ search.customer_first_name.is "Tom"
+ search.customer_id.is customer_id
+ search.customer_last_name.is "Smith"
+ search.customer_phone.is "5551231234"
+ search.customer_website.is "http://example.com"
+ search.order_id.is "myorder"
+ search.payment_method_token.is token
+ search.processor_authorization_code.is transaction.processor_authorization_code
+ search.shipping_company.is "Braintree P.S."
+ search.shipping_country_name.is "Mexico"
+ search.shipping_extended_address.is "Apt 456"
+ search.shipping_first_name.is "Thomas"
+ search.shipping_last_name.is "Smithy"
+ search.shipping_locality.is "Braintree"
+ search.shipping_postal_code.is "54321"
+ search.shipping_region.is "MA"
+ search.shipping_street_address.is "456 Road"
+ search.id.is transaction.id
+ end
+
+ collection._approximate_size.should == 1
+ collection.first.id.should == transaction.id
+ end
+
+ context "multiple value fields" do
+ it "searches on created_using" do
+ transaction = Braintree::Transaction.sale!(
+ :amount => Braintree::Test::TransactionAmounts::Authorize,
+ :credit_card => {
+ :number => Braintree::Test::CreditCardNumbers::Visa,
+ :expiration_date => "05/12"
+ }
+ )
+
+ collection = Braintree::Transaction.search do |search|
+ search.id.is transaction.id
+ search.created_using.is Braintree::Transaction::CreatedUsing::FullInformation
+ end
+
+ collection._approximate_size.should == 1
+
+ collection = Braintree::Transaction.search do |search|
+ search.id.is transaction.id
+ search.created_using.in Braintree::Transaction::CreatedUsing::FullInformation, Braintree::Transaction::CreatedUsing::Token
+ end
+
+ collection._approximate_size.should == 1
+
+ collection = Braintree::Transaction.search do |search|
+ search.id.is transaction.id
+ search.created_using.is Braintree::Transaction::CreatedUsing::Token
+ end
+
+ collection._approximate_size.should == 0
+ end
+
+ it "searches on credit_card_customer_location" do
+ transaction = Braintree::Transaction.sale!(
+ :amount => Braintree::Test::TransactionAmounts::Authorize,
+ :credit_card => {
+ :number => Braintree::Test::CreditCardNumbers::Visa,
+ :expiration_date => "05/12"
+ }
+ )
+
+ collection = Braintree::Transaction.search do |search|
+ search.id.is transaction.id
+ search.credit_card_customer_location.is Braintree::CreditCard::CustomerLocation::US
+ end
+
+ collection._approximate_size.should == 1
+
+ collection = Braintree::Transaction.search do |search|
+ search.id.is transaction.id
+ search.credit_card_customer_location.in Braintree::CreditCard::CustomerLocation::US, Braintree::CreditCard::CustomerLocation::International
+ end
+
+ collection._approximate_size.should == 1
+
+ collection = Braintree::Transaction.search do |search|
+ search.id.is transaction.id
+ search.credit_card_customer_location.is Braintree::CreditCard::CustomerLocation::International
+ end
+
+ collection._approximate_size.should == 0
+ end
+
+ it "searches on merchant_account_id" do
+ transaction = Braintree::Transaction.sale!(
+ :amount => Braintree::Test::TransactionAmounts::Authorize,
+ :credit_card => {
+ :number => Braintree::Test::CreditCardNumbers::Visa,
+ :expiration_date => "05/12"
+ }
+ )
+
+ collection = Braintree::Transaction.search do |search|
+ search.id.is transaction.id
+ search.merchant_account_id.is transaction.merchant_account_id
+ end
+
+ collection._approximate_size.should == 1
+
+ collection = Braintree::Transaction.search do |search|
+ search.id.is transaction.id
+ search.merchant_account_id.in transaction.merchant_account_id, "bogus_merchant_account_id"
+ end
+
+ collection._approximate_size.should == 1
+
+ collection = Braintree::Transaction.search do |search|
+ search.id.is transaction.id
+ search.merchant_account_id.is "bogus_merchant_account_id"
+ end
+
+ collection._approximate_size.should == 0
+ end
+
+ it "searches on credit_card_card_type" do
+ transaction = Braintree::Transaction.sale!(
+ :amount => Braintree::Test::TransactionAmounts::Authorize,
+ :credit_card => {
+ :number => Braintree::Test::CreditCardNumbers::Visa,
+ :expiration_date => "05/12"
+ }
+ )
+
+ collection = Braintree::Transaction.search do |search|
+ search.id.is transaction.id
+ search.credit_card_card_type.is Braintree::CreditCard::CardType::Visa
+ end
+
+ collection._approximate_size.should == 1
+
+ collection = Braintree::Transaction.search do |search|
+ search.id.is transaction.id
+ search.credit_card_card_type.is transaction.credit_card_details.card_type
+ end
+
+ collection._approximate_size.should == 1
+
+ collection = Braintree::Transaction.search do |search|
+ search.id.is transaction.id
+ search.credit_card_card_type.in Braintree::CreditCard::CardType::Visa, Braintree::CreditCard::CardType::MasterCard
+ end
+
+ collection._approximate_size.should == 1
+
+ collection = Braintree::Transaction.search do |search|
+ search.id.is transaction.id
+ search.credit_card_card_type.is Braintree::CreditCard::CardType::MasterCard
+ end
+
+ collection._approximate_size.should == 0
+ end
+
+ it "searches on status" do
+ transaction = Braintree::Transaction.sale!(
+ :amount => Braintree::Test::TransactionAmounts::Authorize,
+ :credit_card => {
+ :number => Braintree::Test::CreditCardNumbers::Visa,
+ :expiration_date => "05/12"
+ }
+ )
+
+ collection = Braintree::Transaction.search do |search|
+ search.id.is transaction.id
+ search.status.is Braintree::Transaction::Status::Authorized
+ end
+
+ collection._approximate_size.should == 1
+
+ collection = Braintree::Transaction.search do |search|
+ search.id.is transaction.id
+ search.status.in Braintree::Transaction::Status::Authorized, Braintree::Transaction::Status::ProcessorDeclined
+ end
+
+ collection._approximate_size.should == 1
+
+ collection = Braintree::Transaction.search do |search|
+ search.id.is transaction.id
+ search.status.is Braintree::Transaction::Status::ProcessorDeclined
+ end
+
+ collection._approximate_size.should == 0
+ end
+
+ it "searches on source" do
+ transaction = Braintree::Transaction.sale!(
+ :amount => Braintree::Test::TransactionAmounts::Authorize,
+ :credit_card => {
+ :number => Braintree::Test::CreditCardNumbers::Visa,
+ :expiration_date => "05/12"
+ }
+ )
+
+ collection = Braintree::Transaction.search do |search|
+ search.id.is transaction.id
+ search.source.is Braintree::Transaction::Source::Api
+ end
+
+ collection._approximate_size.should == 1
+
+ collection = Braintree::Transaction.search do |search|
+ search.id.is transaction.id
+ search.source.in Braintree::Transaction::Source::Api, Braintree::Transaction::Source::ControlPanel
+ end
+
+ collection._approximate_size.should == 1
+
+ collection = Braintree::Transaction.search do |search|
+ search.id.is transaction.id
+ search.source.is Braintree::Transaction::Source::ControlPanel
+ end
+
+ collection._approximate_size.should == 0
+ end
+
+ it "searches on type" do
+ cardholder_name = "refunds#{rand(10000)}"
+ credit_transaction = Braintree::Transaction.credit!(
+ :amount => Braintree::Test::TransactionAmounts::Authorize,
+ :credit_card => {
+ :cardholder_name => cardholder_name,
+ :number => Braintree::Test::CreditCardNumbers::Visa,
+ :expiration_date => "05/12"
+ }
+ )
+
+ transaction = Braintree::Transaction.sale!(
+ :amount => Braintree::Test::TransactionAmounts::Authorize,
+ :credit_card => {
+ :cardholder_name => cardholder_name,
+ :number => Braintree::Test::CreditCardNumbers::Visa,
+ :expiration_date => "05/12"
+ },
+ :options => { :submit_for_settlement => true }
+ )
+ Braintree::Http.put "/transactions/#{transaction.id}/settle"
+
+ refund_transaction = transaction.refund.new_transaction
+
+ collection = Braintree::Transaction.search do |search|
+ search.credit_card_cardholder_name.is cardholder_name
+ search.type.is Braintree::Transaction::Type::Credit
+ end
+
+ collection._approximate_size.should == 2
+
+ collection = Braintree::Transaction.search do |search|
+ search.credit_card_cardholder_name.is cardholder_name
+ search.type.is Braintree::Transaction::Type::Credit
+ search.refund.is true
+ end
+
+ collection._approximate_size.should == 1
+ collection.first.id.should == refund_transaction.id
+
+ collection = Braintree::Transaction.search do |search|
+ search.credit_card_cardholder_name.is cardholder_name
+ search.type.is Braintree::Transaction::Type::Credit
+ search.refund.is false
+ end
+
+ collection._approximate_size.should == 1
+ collection.first.id.should == credit_transaction.id
+ end
+ end
+
+ context "range fields" do
+ context "amount" do
+ it "searches on amount" do
+ transaction = Braintree::Transaction.sale!(
+ :amount => "1000.00",
+ :credit_card => {
+ :number => Braintree::Test::CreditCardNumbers::Visa,
+ :expiration_date => "05/12"
+ }
+ )
+
+ collection = Braintree::Transaction.search do |search|
+ search.id.is transaction.id
+ search.amount.between "500.00", "1500.00"
+ end
+
+ collection._approximate_size.should == 1
+ collection.first.id.should == transaction.id
+
+ collection = Braintree::Transaction.search do |search|
+ search.id.is transaction.id
+ search.amount >= "500.00"
+ end
+
+ collection._approximate_size.should == 1
+ collection.first.id.should == transaction.id
+
+ collection = Braintree::Transaction.search do |search|
+ search.id.is transaction.id
+ search.amount <= "1500.00"
+ end
+
+ collection._approximate_size.should == 1
+ collection.first.id.should == transaction.id
+
+ collection = Braintree::Transaction.search do |search|
+ search.id.is transaction.id
+ search.amount.between "500.00", "900.00"
+ end
+
+ collection._approximate_size.should == 0
+ end
+
+ it "can also take BigDecimal for amount" do
+ transaction = Braintree::Transaction.sale!(
+ :amount => BigDecimal.new("1000.00"),
+ :credit_card => {
+ :number => Braintree::Test::CreditCardNumbers::Visa,
+ :expiration_date => "05/12"
+ }
+ )
+
+ collection = Braintree::Transaction.search do |search|
+ search.id.is transaction.id
+ search.amount <= BigDecimal.new("1000.00")
+ end
+
+ collection._approximate_size.should == 1
+ end
+ end
+
+ it "searches on created_at in UTC" do
+ transaction = Braintree::Transaction.sale!(
+ :amount => Braintree::Test::TransactionAmounts::Authorize,
+ :credit_card => {
+ :number => Braintree::Test::CreditCardNumbers::Visa,
+ :expiration_date => "05/12"
+ }
+ )
+
+ created_at = transaction.created_at
+ created_at.should be_utc
+
+ collection = Braintree::Transaction.search do |search|
+ search.id.is transaction.id
+ search.created_at.between(
+ created_at - 60,
+ created_at + 60
+ )
+ end
+
+ collection._approximate_size.should == 1
+ collection.first.id.should == transaction.id
+
+ collection = Braintree::Transaction.search do |search|
+ search.id.is transaction.id
+ search.created_at >= created_at - 1
+ end
+
+ collection._approximate_size.should == 1
+ collection.first.id.should == transaction.id
+
+ collection = Braintree::Transaction.search do |search|
+ search.id.is transaction.id
+ search.created_at <= created_at + 1
+ end
+
+ collection._approximate_size.should == 1
+ collection.first.id.should == transaction.id
+
+ collection = Braintree::Transaction.search do |search|
+ search.id.is transaction.id
+ search.created_at.between(
+ created_at - 300,
+ created_at - 100
+ )
+ end
+
+ collection._approximate_size.should == 0
+ end
+
+ it "searches on created_at in local time" do
+ transaction = Braintree::Transaction.sale!(
+ :amount => Braintree::Test::TransactionAmounts::Authorize,
+ :credit_card => {
+ :number => Braintree::Test::CreditCardNumbers::Visa,
+ :expiration_date => "05/12"
+ }
+ )
+
+ now = Time.now
+
+ collection = Braintree::Transaction.search do |search|
+ search.id.is transaction.id
+ search.created_at.between(
+ now - 60,
+ now + 60
+ )
+ end
+
+ collection._approximate_size.should == 1
+ collection.first.id.should == transaction.id
+
+ collection = Braintree::Transaction.search do |search|
+ search.id.is transaction.id
+ search.created_at >= now - 60
+ end
+
+ collection._approximate_size.should == 1
+ collection.first.id.should == transaction.id
+
+ collection = Braintree::Transaction.search do |search|
+ search.id.is transaction.id
+ search.created_at <= now + 60
+ end
+
+ collection._approximate_size.should == 1
+ collection.first.id.should == transaction.id
+
+ collection = Braintree::Transaction.search do |search|
+ search.id.is transaction.id
+ search.created_at.between(
+ now - 300,
+ now - 100
+ )
+ end
+
+ collection._approximate_size.should == 0
+ end
+ end
+
+ it "returns multiple results" do
+ collection = Braintree::Transaction.search do |search|
+ search.credit_card_number.starts_with "411"
+ end
+ collection._approximate_size.should > 100
+
+ transaction_ids = collection.map {|t| t.id }.uniq.compact
+ transaction_ids.size.should == collection._approximate_size
+ end
+
+ context "text node operations" do
+ before(:each) do
+ @transaction = Braintree::Transaction.sale!(
+ :amount => Braintree::Test::TransactionAmounts::Authorize,
+ :credit_card => {
+ :number => Braintree::Test::CreditCardNumbers::Visa,
+ :expiration_date => "05/2012",
+ :cardholder_name => "Tom Smith"
+ }
+ )
+ end
+
+ it "is" do
+ collection = Braintree::Transaction.search do |search|
+ search.id.is @transaction.id
+ search.credit_card_cardholder_name.is "Tom Smith"
+ end
+
+ collection._approximate_size.should == 1
+ collection.first.id.should == @transaction.id
+
+ collection = Braintree::Transaction.search do |search|
+ search.id.is @transaction.id
+ search.credit_card_cardholder_name.is "Invalid"
+ end
+
+ collection._approximate_size.should == 0
+ end
+
+ it "is_not" do
+ collection = Braintree::Transaction.search do |search|
+ search.id.is @transaction.id
+ search.credit_card_cardholder_name.is_not "Anybody Else"
+ end
+
+ collection._approximate_size.should == 1
+ collection.first.id.should == @transaction.id
+
+ collection = Braintree::Transaction.search do |search|
+ search.id.is @transaction.id
+ search.credit_card_cardholder_name.is_not "Tom Smith"
+ end
+
+ collection._approximate_size.should == 0
+ end
+
+ it "ends_with" do
+ collection = Braintree::Transaction.search do |search|
+ search.id.is @transaction.id
+ search.credit_card_cardholder_name.ends_with "m Smith"
+ end
+
+ collection._approximate_size.should == 1
+ collection.first.id.should == @transaction.id
+
+ collection = Braintree::Transaction.search do |search|
+ search.id.is @transaction.id
+ search.credit_card_cardholder_name.ends_with "Tom S"
+ end
+
+ collection._approximate_size.should == 0
+ end
+
+ it "starts_with" do
+ collection = Braintree::Transaction.search do |search|
+ search.id.is @transaction.id
+ search.credit_card_cardholder_name.starts_with "Tom S"
+ end
+
+ collection._approximate_size.should == 1
+ collection.first.id.should == @transaction.id
+
+ collection = Braintree::Transaction.search do |search|
+ search.id.is @transaction.id
+ search.credit_card_cardholder_name.starts_with "m Smith"
+ end
+
+ collection._approximate_size.should == 0
+ end
+
+ it "contains" do
+ collection = Braintree::Transaction.search do |search|
+ search.id.is @transaction.id
+ search.credit_card_cardholder_name.contains "m Sm"
+ end
+
+ collection._approximate_size.should == 1
+ collection.first.id.should == @transaction.id
+
+ collection = Braintree::Transaction.search do |search|
+ search.id.is @transaction.id
+ search.credit_card_cardholder_name.contains "Anybody Else"
+ end
+
+ collection._approximate_size.should == 0
+ end
+ end
+ end
+
+ context "basic" do
+ it "returns transactions matching the given search terms" do
+ transactions = Braintree::Transaction.search "1111"
+ transactions._approximate_size.should > 0
+ end
+
+ it "can iterate over the entire collection" do
+ transactions = Braintree::Transaction.search "411111"
+ transactions._approximate_size.should > 100
+
+ transaction_ids = transactions.map {|t| t.id }.uniq.compact
+ transaction_ids.size.should == transactions._approximate_size
+ end
+ end
+end
View
211 spec/integration/braintree/transaction_spec.rb
@@ -199,6 +199,51 @@
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"
+ end
+ end
end
describe "self.create!" do
@@ -301,8 +346,8 @@
transaction.amount.should == BigDecimal.new("100.00")
transaction.order_id.should == "123"
transaction.processor_response_code.should == "1000"
- transaction.created_at.between?(Time.now - 5, Time.now).should == true
- transaction.updated_at.between?(Time.now - 5, Time.now).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"
@@ -339,6 +384,31 @@
transaction.shipping_details.country_name.should == "United States of America"
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",
@@ -660,6 +730,31 @@
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
end
describe "self.credit!" do
@@ -924,6 +1019,23 @@
end
describe "refund" do
+ context "partial refunds" do
+ it "allows partial refunds" do
+ transaction = create_transaction_to_refund
+ result = transaction.refund(transaction.amount / 2)
+ result.success?.should == true
+ result.new_transaction.type.should == "credit"
+ end
+
+ it "does not all multiple refunds" do
+ transaction = create_transaction_to_refund
+ transaction.refund(transaction.amount / 2)
+ result = transaction.refund(BigDecimal.new("1"))
+ result.success?.should == false
+ result.errors.for(:transaction).on(:base)[0].code.should == Braintree::ErrorCodes::Transaction::HasAlreadyBeenRefunded
+ end
+ end
+
it "returns a successful result if successful" do
transaction = create_transaction_to_refund
transaction.status.should == Braintree::Transaction::Status::Settled
@@ -1044,101 +1156,6 @@
end
end
- describe "search" do
- describe "advanced" do
- it "pretty much works in one big fell swoop/hash" do
- result = Braintree::Transaction.create(
- :type => "sale",
- :amount => Braintree::Test::TransactionAmounts::Authorize,
- :credit_card => {
- :number => Braintree::Test::CreditCardNumbers::Visa,
- :expiration_date => "05/2009"
- },
- :order_id => "123",
- :customer => {
- :company => "Apple",
- :fax => "312-555-1234",
- :first_name => "Steve",
- :last_name => "Jobs",
- :phone => "614-555-1234",
- :website => "http://www.apple.com",
- },
- :billing => {
- :country_name => "United States of America",
- :extended_address => "Apt 1F",
- :locality => "Chicago",
- :postal_code => "60622",
- :region => "Illinois",
- :street_address => "1234 W North Ave",
- },
- :shipping => {
- :country_name => "United States of America",
- :extended_address => "Apt 123",
- :locality => "Bartlett",
- :postal_code => "60004",
- :region => "Illinois",
- :street_address => "123 Main St",
- }
- )
- result.success?.should == true
- expected_transaction = result.transaction
- search_criteria = {
- :billing_country_name => {:is => "United States of America"},
- :billing_extended_address => {:is => "Apt 1F"},
- :billing_locality => {:is => "Chicago"},
- :billing_postal_code => {:is => "60622"},
- :billing_region => {:is => "Illinois"},
- :billing_street_address => {:is => "1234 W North Ave"},
- :credit_card_number => {:is => Braintree::Test::CreditCardNumbers::Visa},
- :customer_company => {:is => "Apple"},
- :customer_fax => {:is => "312-555-1234"},
- :customer_first_name => {:is => "Steve"},
- :customer_last_name => {:is => "Jobs"},
- :customer_phone => {:is => "614-555-1234"},
- :customer_website => {:is => "http://www.apple.com"},
- :expiration_date => {:is => "05/2009"},
- :order_id => {:is => "123"},
- :shipping_country_name => {:is => "United States of America"},
- :shipping_extended_address => {:is => "Apt 123"},
- :shipping_locality => {:is => "Bartlett"},
- :shipping_postal_code => {:is => "60004"},
- :shipping_region => {:is => "Illinois"},
- :shipping_street_address => {:is => "123 Main St"},
- }
- search_results = Braintree::Transaction.search(search_criteria)
- search_results.should include(expected_transaction)
- end
-
- it "properly parses the xml if only one transaction is found" do
- transaction = Braintree::Transaction.create!(
- :type => "sale",
- :amount => Braintree::Test::TransactionAmounts::Authorize,
- :credit_card => {
- :number => Braintree::Test::CreditCardNumbers::Visa,
- :expiration_date => "05/2009"
- }
- )
- search_results = Braintree::Transaction.search(:transaction_id => {:is => transaction.id})
- search_results.first.should == transaction
- end
- end
-
- describe "basic" do
- it "returns transactions matching the given search terms" do
- transactions = Braintree::Transaction.search "1111"
- transactions._approximate_size.should > 0
- end
-
- it "can iterate over the entire collection" do
- transactions = Braintree::Transaction.search "411111"
- transactions._approximate_size.should > 100
-
- transaction_ids = transactions.map {|t| t.id }.uniq.compact
- transaction_ids.size.should == transactions._approximate_size
- end
- end
- end
-
describe "status_history" do
it "returns an array of StatusDetail" do
transaction = Braintree::Transaction.sale!(
View
4 spec/spec_helper.rb
@@ -17,6 +17,10 @@
Braintree::Configuration.logger.level = Logger::INFO
module SpecHelper
+
+ DefaultMerchantAccountId = "sandbox_credit_card"
+ NonDefaultMerchantAccountId = "sandbox_credit_card_non_default"
+
def self.stub_time_dot_now(desired_time)
Time.class_eval do
class << self
View
50 spec/unit/braintree/credit_card_spec.rb
@@ -10,14 +10,56 @@
end
describe "self.create_signature" do
- it "should include customer_id" do
- Braintree::CreditCard._create_signature.should include(:customer_id)
+ it "should be what we expect" do
+ Braintree::CreditCard._create_signature.should == [
+ :cardholder_name,
+ :cvv,
+ :expiration_date,
+ :expiration_month,
+ :expiration_year,
+ :number,
+ :token,
+ {:options => [:make_default, :verify_card]},
+ {:billing_address => [
+ :company,
+ :country_name,
+ :extended_address,
+ :first_name,
+ :last_name,
+ :locality,
+ :postal_code,
+ :region,
+ :street_address
+ ]},
+ :customer_id
+ ]
end
end
describe "self.update_signature" do
- it "should not include customer_id" do
- Braintree::CreditCard._update_signature.should_not include(:customer_id)
+ it "should be what we expect" do
+ Braintree::CreditCard._update_signature.should == [
+ :cardholder_name,
+ :cvv,
+ :expiration_date,
+ :expiration_month,
+ :expiration_year,
+ :number,
+ :token,
+ {:options => [:make_default, :verify_card]},
+ {:billing_address => [
+ :company,
+ :country_name,
+ :extended_address,
+ :first_name,
+ :last_name,
+ :locality,
+ :postal_code,
+ :region,
+ :street_address,
+ {:options => [:update_existing]}
+ ]}
+ ]
end
end
View
25 spec/unit/braintree/subscription_spec.rb
@@ -3,13 +3,13 @@
describe Braintree::Subscription do
context "price" do
it "accepts price as either a String or a BigDecimal" do
- Braintree::Subscription.new(:price => "12.34", :transactions => []).price.should == BigDecimal.new("12.34")
- Braintree::Subscription.new(:price => BigDecimal.new("12.34"), :transactions => []).price.should == BigDecimal.new("12.34")
+ Braintree::Subscription._new(:price => "12.34", :transactions => []).price.should == BigDecimal.new("12.34")
+ Braintree::Subscription._new(:price => BigDecimal.new("12.34"), :transactions => []).price.should == BigDecimal.new("12.34")
end
it "blows up if price is not a string or BigDecimal" do
expect {
- Braintree::Subscription.new(:price => 12.34, :transactions => [])
+ Braintree::Subscription._new(:price => 12.34, :transactions => [])
}.to raise_error(/Argument must be a String or BigDecimal/)
end
end
@@ -23,4 +23,23 @@
end.should raise_error(ArgumentError)
end
end
+
+ describe "==" do
+ it "returns true for subscriptions with the same id" do
+ subscription1 = Braintree::Subscription._new(:id => "123", :transactions => [])
+ subscription2 = Braintree::Subscription._new(:id => "123", :transactions => [])
+ subscription1.should == subscription2
+ end
+
+ it "returns false for subscriptions with different ids" do
+ subscription1 = Braintree::Subscription._new(:id => "123", :transactions => [])
+ subscription2 = Braintree::Subscription._new(:id => "not_123", :transactions => [])
+ subscription1.should_not == subscription2
+ end
+
+ it "returns false if not comparing to a subscription" do
+ subscription = Braintree::Subscription._new(:id => "123", :transactions => [])
+ subscription.should_not == "not a subscription"
+ end
+ end
end
View
24 spec/unit/braintree/transaction_search_spec.rb
@@ -0,0 +1,24 @@
+require File.dirname(__FILE__) + "/../spec_helper"
+
+describe Braintree::TransactionSearch do
+ it "overrides previous 'is' with new 'is' for the same field" do
+ search = Braintree::TransactionSearch.new
+ search.billing_company.is "one"
+ search.billing_company.is "two"
+ search.to_hash.should == {:billing_company => {:is => "two"}}
+ end
+
+ it "overrides previous 'in' with new 'in' for the same field" do
+ search = Braintree::TransactionSearch.new
+ search.status.in Braintree::Transaction::Status::Authorized
+ search.status.in Braintree::Transaction::Status::SubmittedForSettlement
+ search.to_hash.should == {:status => [Braintree::Transaction::Status::SubmittedForSettlement]}
+ end
+
+ it "raises if the operator 'is' is left off" do
+ search = Braintree::TransactionSearch.new
+ expect do
+ search.billing_company "one"
+ end.to raise_error(RuntimeError, "An operator is required")
+ end
+end
View
15 spec/unit/braintree/transaction_spec.rb
@@ -127,7 +127,7 @@
end
describe "==" do
- it "returns true when it should" do
+ it "returns true for transactions with the same id" do
first = Braintree::Transaction._new(:id => 123)
second = Braintree::Transaction._new(:id => 123)
@@ -135,13 +135,24 @@
second.should == first
end
- it "returns false when it should" do
+ it "returns false for transactions with different ids" do
first = Braintree::Transaction._new(:id => 123)
second = Braintree::Transaction._new(:id => 124)
first.should_not == second
second.should_not == first
end
+
+ it "returns false when comparing to nil" do
+ Braintree::Transaction._new({}).should_not == nil
+ end
+
+ it "returns false when comparing to non-transactions" do
+ same_id_different_object = Object.new
+ def same_id_different_object.id; 123; end
+ transaction = Braintree::Transaction._new(:id => 123)
+ transaction.should_not == same_id_different_object
+ end
end
describe "new" do
View
8 spec/unit/braintree/util_spec.rb
@@ -120,7 +120,7 @@
end
describe "self.extract_attribute_as_array" do
- it "delets the attribute from the hash" do
+ it "deletes the attribute from the hash" do
hash = {:foo => ["x"], :bar => :baz}
Braintree::Util.extract_attribute_as_array(hash, :foo)
hash.should == {:bar => :baz}
@@ -143,6 +143,12 @@
result = Braintree::Util.extract_attribute_as_array(hash, :quz)
result.should == []
end
+
+ it "raises an UnexpectedError if nil data is provided" do
+ expect do
+ Braintree::Util.extract_attribute_as_array(nil, :abc)
+ end.to raise_error(Braintree::UnexpectedError, /Unprocessable entity due to an invalid request/)
+ end
end
describe "self.hash_to_query_string" do
View
22 spec/unit/braintree/xml_spec.rb
@@ -99,6 +99,28 @@ def verify_to_xml_and_back(hash)
verify_to_xml_and_back hash
end
+ context "BigDecimal" do
+ it "works for BigDecimals" do
+ hash = {:root => {:foo => BigDecimal.new("123.45")}}
+ Braintree::Xml.hash_to_xml(hash).should include("<foo>123.45</foo>")
+ end
+
+ it "works for BigDecimals with fewer than 2 digits" do
+ hash = {:root => {:foo => BigDecimal.new("1000.0")}}
+ Braintree::Xml.hash_to_xml(hash).should include("<foo>1000.00</foo>")
+ end
+
+ it "works for BigDecimals with more than 2 digits" do
+ hash = {:root => {:foo => BigDecimal.new("12.345")}}
+ Braintree::Xml.hash_to_xml(hash).should include("<foo>12.345</foo>")
+ end
+ end
+
+ it "works for symbols" do
+ hash = {:root => {:foo => :bar}}
+ Braintree::Xml.hash_to_xml(hash).should include("<foo>bar</foo>")
+ end
+
it "type casts booleans" do
hash = {:root => {:string_true => "true", :bool_true => true, :bool_false => false, :string_false => "false"}}
verify_to_xml_and_back hash

0 comments on commit d103526

Please sign in to comment.
Something went wrong with that request. Please try again.