braintree 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +22 -0
- data/README.rdoc +62 -0
- data/lib/braintree.rb +66 -0
- data/lib/braintree/address.rb +122 -0
- data/lib/braintree/base_module.rb +29 -0
- data/lib/braintree/configuration.rb +99 -0
- data/lib/braintree/credit_card.rb +231 -0
- data/lib/braintree/credit_card_verification.rb +31 -0
- data/lib/braintree/customer.rb +231 -0
- data/lib/braintree/digest.rb +20 -0
- data/lib/braintree/error_codes.rb +95 -0
- data/lib/braintree/error_result.rb +39 -0
- data/lib/braintree/errors.rb +29 -0
- data/lib/braintree/http.rb +105 -0
- data/lib/braintree/paged_collection.rb +55 -0
- data/lib/braintree/ssl_expiration_check.rb +28 -0
- data/lib/braintree/successful_result.rb +38 -0
- data/lib/braintree/test/credit_card_numbers.rb +50 -0
- data/lib/braintree/test/transaction_amounts.rb +10 -0
- data/lib/braintree/transaction.rb +360 -0
- data/lib/braintree/transaction/address_details.rb +15 -0
- data/lib/braintree/transaction/credit_card_details.rb +22 -0
- data/lib/braintree/transaction/customer_details.rb +13 -0
- data/lib/braintree/transparent_redirect.rb +110 -0
- data/lib/braintree/util.rb +94 -0
- data/lib/braintree/validation_error.rb +15 -0
- data/lib/braintree/validation_error_collection.rb +80 -0
- data/lib/braintree/version.rb +9 -0
- data/lib/braintree/xml.rb +12 -0
- data/lib/braintree/xml/generator.rb +80 -0
- data/lib/braintree/xml/libxml.rb +69 -0
- data/lib/braintree/xml/parser.rb +93 -0
- data/lib/ssl/securetrust_ca.crt +44 -0
- data/lib/ssl/valicert_ca.crt +18 -0
- data/spec/integration/braintree/address_spec.rb +352 -0
- data/spec/integration/braintree/credit_card_spec.rb +676 -0
- data/spec/integration/braintree/customer_spec.rb +664 -0
- data/spec/integration/braintree/http_spec.rb +201 -0
- data/spec/integration/braintree/test/transaction_amounts_spec.rb +29 -0
- data/spec/integration/braintree/transaction_spec.rb +900 -0
- data/spec/integration/spec_helper.rb +38 -0
- data/spec/script/httpsd.rb +27 -0
- data/spec/spec_helper.rb +41 -0
- data/spec/unit/braintree/address_spec.rb +86 -0
- data/spec/unit/braintree/configuration_spec.rb +190 -0
- data/spec/unit/braintree/credit_card_spec.rb +137 -0
- data/spec/unit/braintree/credit_card_verification_spec.rb +17 -0
- data/spec/unit/braintree/customer_spec.rb +103 -0
- data/spec/unit/braintree/digest_spec.rb +28 -0
- data/spec/unit/braintree/error_result_spec.rb +42 -0
- data/spec/unit/braintree/errors_spec.rb +81 -0
- data/spec/unit/braintree/http_spec.rb +42 -0
- data/spec/unit/braintree/paged_collection_spec.rb +128 -0
- data/spec/unit/braintree/ssl_expiration_check_spec.rb +92 -0
- data/spec/unit/braintree/successful_result_spec.rb +27 -0
- data/spec/unit/braintree/transaction/credit_card_details_spec.rb +22 -0
- data/spec/unit/braintree/transaction_spec.rb +136 -0
- data/spec/unit/braintree/transparent_redirect_spec.rb +154 -0
- data/spec/unit/braintree/util_spec.rb +142 -0
- data/spec/unit/braintree/validation_error_collection_spec.rb +128 -0
- data/spec/unit/braintree/validation_error_spec.rb +19 -0
- data/spec/unit/braintree/xml/libxml_spec.rb +51 -0
- data/spec/unit/braintree/xml_spec.rb +122 -0
- data/spec/unit/spec_helper.rb +1 -0
- metadata +118 -0
@@ -0,0 +1,105 @@
|
|
1
|
+
module Braintree
|
2
|
+
module Http # :nodoc:
|
3
|
+
|
4
|
+
def self.delete(path)
|
5
|
+
response = _http_do Net::HTTP::Delete, path
|
6
|
+
if response.code.to_i == 200
|
7
|
+
true
|
8
|
+
else
|
9
|
+
Util.raise_exception_for_status_code(response.code)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.get(path)
|
14
|
+
response = _http_do Net::HTTP::Get, path
|
15
|
+
if response.code.to_i == 200
|
16
|
+
Xml.hash_from_xml(_body(response))
|
17
|
+
else
|
18
|
+
Util.raise_exception_for_status_code(response.code)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.post(path, params = nil)
|
23
|
+
response = _http_do Net::HTTP::Post, path, _build_xml(params)
|
24
|
+
if response.code.to_i == 200 || response.code.to_i == 201 || response.code.to_i == 422
|
25
|
+
Xml.hash_from_xml(_body(response))
|
26
|
+
else
|
27
|
+
Util.raise_exception_for_status_code(response.code)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.put(path, params = nil)
|
32
|
+
response = _http_do Net::HTTP::Put, path, _build_xml(params)
|
33
|
+
if response.code.to_i == 200 || response.code.to_i == 201 || response.code.to_i == 422
|
34
|
+
Xml.hash_from_xml(_body(response))
|
35
|
+
else
|
36
|
+
Util.raise_exception_for_status_code(response.code)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self._build_xml(params)
|
41
|
+
return nil if params.nil?
|
42
|
+
Braintree::Xml.hash_to_xml params
|
43
|
+
end
|
44
|
+
|
45
|
+
def self._http_do(http_verb, path, body = nil)
|
46
|
+
connection = Net::HTTP.new(Configuration.server, Configuration.port)
|
47
|
+
if Configuration.ssl?
|
48
|
+
connection.use_ssl = true
|
49
|
+
connection.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
50
|
+
connection.ca_file = Configuration.ca_file
|
51
|
+
connection.verify_callback = proc { |preverify_ok, ssl_context| _verify_ssl_certificate(preverify_ok, ssl_context) }
|
52
|
+
end
|
53
|
+
connection.start do |http|
|
54
|
+
request = http_verb.new("#{Configuration.base_merchant_path}#{path}")
|
55
|
+
request["Accept"] = "application/xml"
|
56
|
+
request["User-Agent"] = "Braintree Ruby Gem #{Braintree::Version::String}"
|
57
|
+
request["Accept-Encoding"] = "gzip"
|
58
|
+
request["X-ApiVersion"] = Configuration::API_VERSION
|
59
|
+
request.basic_auth Configuration.public_key, Configuration.private_key
|
60
|
+
Configuration.logger.debug "[Braintree] [#{_current_time}] #{request.method} #{path}"
|
61
|
+
if body
|
62
|
+
request["Content-Type"] = "application/xml"
|
63
|
+
request.body = body
|
64
|
+
Configuration.logger.debug _format_and_sanitize_body_for_log(body)
|
65
|
+
end
|
66
|
+
response = http.request(request)
|
67
|
+
Configuration.logger.info "[Braintree] [#{_current_time}] #{request.method} #{path} #{response.code}"
|
68
|
+
Configuration.logger.debug "[Braintree] [#{_current_time}] #{response.code} #{response.message}"
|
69
|
+
if Configuration.logger.level == Logger::DEBUG
|
70
|
+
Configuration.logger.debug _format_and_sanitize_body_for_log(_body(response))
|
71
|
+
end
|
72
|
+
response
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def self._body(response)
|
77
|
+
if response.header["Content-Encoding"] == "gzip"
|
78
|
+
Zlib::GzipReader.new(StringIO.new(response.body)).read
|
79
|
+
else
|
80
|
+
raise UnexpectedError, "expected a gzip'd response"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def self._current_time
|
85
|
+
Time.now.utc.strftime("%d/%b/%Y %H:%M:%S %Z")
|
86
|
+
end
|
87
|
+
|
88
|
+
def self._format_and_sanitize_body_for_log(input_xml)
|
89
|
+
formatted_xml = input_xml.gsub(/^/, "[Braintree] ")
|
90
|
+
formatted_xml = formatted_xml.gsub(/<number>(.{6}).+?(.{4})<\/number>/, '<number>\1******\2</number>')
|
91
|
+
formatted_xml = formatted_xml.gsub(/<cvv>.+?<\/cvv>/, '<cvv>***</cvv>')
|
92
|
+
formatted_xml
|
93
|
+
end
|
94
|
+
|
95
|
+
def self._verify_ssl_certificate(preverify_ok, ssl_context)
|
96
|
+
if preverify_ok != true || ssl_context.error != 0
|
97
|
+
err_msg = "SSL Verification failed -- Preverify: #{preverify_ok}, Error: #{ssl_context.error_string} (#{ssl_context.error})"
|
98
|
+
Configuration.logger.error err_msg
|
99
|
+
raise SSLCertificateError.new(err_msg)
|
100
|
+
end
|
101
|
+
true
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Braintree
|
2
|
+
class PagedCollection
|
3
|
+
include BaseModule
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
attr_reader :current_page_number, :items, :next_page_number, :page_size, :previous_page_number, :total_items
|
7
|
+
|
8
|
+
def initialize(attributes, &block) # :nodoc:
|
9
|
+
set_instance_variables_from_hash attributes
|
10
|
+
@paging_block = block
|
11
|
+
end
|
12
|
+
|
13
|
+
# Returns the item from the current page at the given +index+.
|
14
|
+
def [](index)
|
15
|
+
@items[index]
|
16
|
+
end
|
17
|
+
|
18
|
+
# Yields each item on the current page.
|
19
|
+
def each(&block)
|
20
|
+
@items.each(&block)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns the first item from the current page.
|
24
|
+
def first
|
25
|
+
@items.first
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns true if the page is the last page. False otherwise.
|
29
|
+
def last_page?
|
30
|
+
current_page_number == total_pages
|
31
|
+
end
|
32
|
+
|
33
|
+
# Retrieves the next page of records.
|
34
|
+
def next_page
|
35
|
+
if last_page?
|
36
|
+
return nil
|
37
|
+
end
|
38
|
+
@paging_block.call(next_page_number)
|
39
|
+
end
|
40
|
+
|
41
|
+
# The next page number. Returns +nil+ if on the last page.
|
42
|
+
def next_page_number
|
43
|
+
last_page? ? nil : current_page_number + 1
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns the total number of pages.
|
47
|
+
def total_pages
|
48
|
+
total = total_items / page_size
|
49
|
+
if total_items % page_size != 0
|
50
|
+
total += 1
|
51
|
+
end
|
52
|
+
total
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Braintree
|
2
|
+
module SSLExpirationCheck # :nodoc:
|
3
|
+
class << self
|
4
|
+
attr_reader :ssl_expiration_dates_checked
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.check_dates # :nodoc:
|
8
|
+
{
|
9
|
+
"QA" => qa_expiration_date,
|
10
|
+
"Sandbox" => sandbox_expiration_date
|
11
|
+
}.each do |host, expiration_date|
|
12
|
+
if Date.today + (3 * 30) > expiration_date
|
13
|
+
Configuration.logger.warn "[Braintree] The SSL Certificate for the #{host} environment will expire on #{expiration_date}. Please check for an updated client library."
|
14
|
+
end
|
15
|
+
end
|
16
|
+
@ssl_expiration_dates_checked = true
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
def self.sandbox_expiration_date # :nodoc:
|
21
|
+
Date.new(2010, 12, 1)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.qa_expiration_date # :nodoc:
|
25
|
+
Date.new(2010, 12, 1)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Braintree
|
2
|
+
# A SuccessfulResult will be returned from non-bang methods when
|
3
|
+
# validations pass. It will provide access to the created resource.
|
4
|
+
# For example, when creating a customer, SuccessfulResult will
|
5
|
+
# respond to +customer+ like so:
|
6
|
+
#
|
7
|
+
# result = Customer.create(:first_name => "John")
|
8
|
+
# if result.success?
|
9
|
+
# # have a SuccessfulResult
|
10
|
+
# puts "Created customer #{result.customer.id}
|
11
|
+
# else
|
12
|
+
# # have an ErrorResult
|
13
|
+
# end
|
14
|
+
class SuccessfulResult
|
15
|
+
include BaseModule
|
16
|
+
|
17
|
+
def initialize(attributes = {}) # :nodoc:
|
18
|
+
@attrs = attributes.keys
|
19
|
+
singleton_class.class_eval do
|
20
|
+
attributes.each do |key, value|
|
21
|
+
define_method key do
|
22
|
+
value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def inspect # :nodoc:
|
29
|
+
inspected_attributes = @attrs.map { |attr| "#{attr}:#{send(attr).inspect}" }
|
30
|
+
"#<#{self.class} #{inspected_attributes}>"
|
31
|
+
end
|
32
|
+
|
33
|
+
# Always returns true.
|
34
|
+
def success?
|
35
|
+
true
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Braintree
|
2
|
+
module Test # :nodoc:
|
3
|
+
# The constants contained in the Braintree::Test::CreditCardNumbers module provide
|
4
|
+
# credit card numbers that should be used when working in the sandbox environment. The sandbox
|
5
|
+
# will not accept any credit card numbers other than the ones listed below.
|
6
|
+
module CreditCardNumbers
|
7
|
+
AmExes = %w[
|
8
|
+
378282246310005
|
9
|
+
371449635398431
|
10
|
+
378734493671000
|
11
|
+
]
|
12
|
+
CarteBlanches = %w[30569309025904] # :nodoc:
|
13
|
+
DinersClubs = %w[38520000023237] # :nodoc:
|
14
|
+
|
15
|
+
Discovers = %w[
|
16
|
+
6011111111111117
|
17
|
+
6011000990139424
|
18
|
+
]
|
19
|
+
JCBs = %w[3530111333300000 3566002020360505] # :nodoc:
|
20
|
+
|
21
|
+
MasterCard = "5555555555554444"
|
22
|
+
MasterCardInternational = "5105105105105100" # :nodoc:
|
23
|
+
|
24
|
+
MasterCards = %w[5105105105105100 5555555555554444]
|
25
|
+
|
26
|
+
Visa = "4012888888881881"
|
27
|
+
VisaInternational = "4009348888881881" # :nodoc:
|
28
|
+
|
29
|
+
Visas = %w[
|
30
|
+
4009348888881881
|
31
|
+
4012888888881881
|
32
|
+
4111111111111111
|
33
|
+
4222222222222
|
34
|
+
]
|
35
|
+
Unknowns = %w[
|
36
|
+
1000000000000008
|
37
|
+
]
|
38
|
+
|
39
|
+
module FailsSandboxVerification
|
40
|
+
AmEx = "378734493671000"
|
41
|
+
Discover = "6011000990139424"
|
42
|
+
MasterCard = "5105105105105100"
|
43
|
+
Visa = "4222222222222"
|
44
|
+
Numbers = [AmEx, Discover, MasterCard, Visa]
|
45
|
+
end
|
46
|
+
|
47
|
+
All = AmExes + Discovers + MasterCards + Visas
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,360 @@
|
|
1
|
+
module Braintree
|
2
|
+
# == Creating a Transaction
|
3
|
+
#
|
4
|
+
# At minimum, an amount, credit card number, and credit card expiration date are required. Minimalistic
|
5
|
+
# example:
|
6
|
+
# Braintree::Transaction.sale!(
|
7
|
+
# :amount => "100.00",
|
8
|
+
# :credit_card => {
|
9
|
+
# :number => "5105105105105100",
|
10
|
+
# :expiration_date => "05/2012"
|
11
|
+
# }
|
12
|
+
# )
|
13
|
+
#
|
14
|
+
# Full example:
|
15
|
+
#
|
16
|
+
# Braintree::Transaction.sale!(
|
17
|
+
# :amount => "100.00",
|
18
|
+
# :order_id => "123",
|
19
|
+
# :credit_card => {
|
20
|
+
# # if :token is omitted, the gateway will generate a token
|
21
|
+
# :token => "credit_card_123",
|
22
|
+
# :number => "5105105105105100",
|
23
|
+
# :expiration_date => "05/2011",
|
24
|
+
# :cvv => "123"
|
25
|
+
# },
|
26
|
+
# :customer => {
|
27
|
+
# # if :id is omitted, the gateway will generate an id
|
28
|
+
# :id => "customer_123",
|
29
|
+
# :first_name => "Dan",
|
30
|
+
# :last_name => "Smith",
|
31
|
+
# :company => "Braintree Payment Solutions",
|
32
|
+
# :email => "dan@example.com",
|
33
|
+
# :phone => "419-555-1234",
|
34
|
+
# :fax => "419-555-1235",
|
35
|
+
# :website => "http://braintreepaymentsolutions.com"
|
36
|
+
# },
|
37
|
+
# :billing => {
|
38
|
+
# :first_name => "Carl",
|
39
|
+
# :last_name => "Jones",
|
40
|
+
# :company => "Braintree",
|
41
|
+
# :street_address => "123 E Main St",
|
42
|
+
# :extended_address => "Suite 403",
|
43
|
+
# :locality => "Chicago",
|
44
|
+
# :region => "IL",
|
45
|
+
# :postal_code => "60622",
|
46
|
+
# :country_name => "United States of America"
|
47
|
+
# },
|
48
|
+
# :shipping => {
|
49
|
+
# :first_name => "Andrew",
|
50
|
+
# :last_name => "Mason",
|
51
|
+
# :company => "Braintree",
|
52
|
+
# :street_address => "456 W Main St",
|
53
|
+
# :extended_address => "Apt 2F",
|
54
|
+
# :locality => "Bartlett",
|
55
|
+
# :region => "IL",
|
56
|
+
# :postal_code => "60103",
|
57
|
+
# :country_name => "United States of America"
|
58
|
+
# }
|
59
|
+
# )
|
60
|
+
#
|
61
|
+
# == Storing in the Vault
|
62
|
+
#
|
63
|
+
# The customer and credit card information used for
|
64
|
+
# a transaction can be stored in the vault by setting
|
65
|
+
# <tt>transaction[options][store_in_vault]</tt> to true.
|
66
|
+
#
|
67
|
+
# transaction = Braintree::Transaction.create!(
|
68
|
+
# :customer => {
|
69
|
+
# :first_name => "Adam",
|
70
|
+
# :last_name => "Williams"
|
71
|
+
# },
|
72
|
+
# :credit_card => {
|
73
|
+
# :number => "5105105105105100",
|
74
|
+
# :expiration_date => "05/2012"
|
75
|
+
# },
|
76
|
+
# :options => {
|
77
|
+
# :store_in_vault => true
|
78
|
+
# }
|
79
|
+
# )
|
80
|
+
# transaction.customer_details.id
|
81
|
+
# # => "865534"
|
82
|
+
# transaction.credit_card_details.token
|
83
|
+
# # => "6b6m"
|
84
|
+
#
|
85
|
+
# == Submitting for Settlement
|
86
|
+
#
|
87
|
+
# This can only be done when the transction's
|
88
|
+
# status is +authorized+. If +amount+ is not specified, the full authorized amount will be
|
89
|
+
# settled. If you would like to settle less than the full authorized amount, pass the
|
90
|
+
# desired amount. You cannot settle more than the authorized amount.
|
91
|
+
#
|
92
|
+
# A transaction can be submitted for settlement when created by setting
|
93
|
+
# transaction[options][submit_for_settlement] to true.
|
94
|
+
#
|
95
|
+
# transaction = Braintree::Transaction.sale!(
|
96
|
+
# :amount => "100.00",
|
97
|
+
# :credit_card => {
|
98
|
+
# :number => "5105105105105100",
|
99
|
+
# :expiration_date => "05/2012"
|
100
|
+
# },
|
101
|
+
# :options => {
|
102
|
+
# :submit_for_settlement => true
|
103
|
+
# }
|
104
|
+
# )
|
105
|
+
class Transaction
|
106
|
+
include BaseModule
|
107
|
+
|
108
|
+
module Type # :nodoc:
|
109
|
+
Credit = "credit" # :nodoc:
|
110
|
+
Sale = "sale" # :nodoc:
|
111
|
+
end
|
112
|
+
|
113
|
+
attr_reader :avs_error_response_code, :avs_postal_code_response_code, :avs_street_address_response_code
|
114
|
+
attr_reader :amount, :created_at, :credit_card_details, :customer_details, :id, :status
|
115
|
+
attr_reader :order_id
|
116
|
+
attr_reader :billing_details, :shipping_details
|
117
|
+
# The response code from the processor.
|
118
|
+
attr_reader :processor_response_code
|
119
|
+
# Will either be "sale" or "credit"
|
120
|
+
attr_reader :type
|
121
|
+
attr_reader :updated_at
|
122
|
+
|
123
|
+
def self.create(attributes)
|
124
|
+
Util.verify_keys(_create_signature, attributes)
|
125
|
+
_do_create "/transactions", :transaction => attributes
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.create!(attributes)
|
129
|
+
return_object_or_raise(:transaction) { create(attributes) }
|
130
|
+
end
|
131
|
+
|
132
|
+
def self.create_from_transparent_redirect(query_string)
|
133
|
+
params = TransparentRedirect.parse_and_validate_query_string query_string
|
134
|
+
_do_create("/transactions/all/confirm_transparent_redirect_request", :id => params[:id])
|
135
|
+
end
|
136
|
+
|
137
|
+
# The URL to use to create transactions via transparent redirect.
|
138
|
+
def self.create_transaction_url
|
139
|
+
"#{Braintree::Configuration.base_merchant_url}/transactions/all/create_via_transparent_redirect_request"
|
140
|
+
end
|
141
|
+
|
142
|
+
# Creates a credit transaction.
|
143
|
+
def self.credit(attributes)
|
144
|
+
create(attributes.merge(:type => 'credit'))
|
145
|
+
end
|
146
|
+
|
147
|
+
def self.credit!(attributes)
|
148
|
+
return_object_or_raise(:transaction) { credit(attributes) }
|
149
|
+
end
|
150
|
+
|
151
|
+
# Finds the transaction with the given id. Raises a Braintree::NotFoundError
|
152
|
+
# if the transaction cannot be found.
|
153
|
+
def self.find(id)
|
154
|
+
response = Http.get "/transactions/#{id}"
|
155
|
+
new(response[:transaction])
|
156
|
+
rescue NotFoundError
|
157
|
+
raise NotFoundError, "transaction with id #{id.inspect} not found"
|
158
|
+
end
|
159
|
+
|
160
|
+
# Creates a sale transaction.
|
161
|
+
def self.sale(attributes)
|
162
|
+
create(attributes.merge(:type => 'sale'))
|
163
|
+
end
|
164
|
+
|
165
|
+
def self.sale!(attributes)
|
166
|
+
return_object_or_raise(:transaction) { sale(attributes) }
|
167
|
+
end
|
168
|
+
|
169
|
+
# Returns a PagedCollection of transactions matching the search query.
|
170
|
+
# If <tt>query</tt> is a string, the search will be a basic search.
|
171
|
+
# If <tt>query</tt> is a hash, the search will be an advanced search.
|
172
|
+
def self.search(query, options = {})
|
173
|
+
if query.is_a?(String)
|
174
|
+
_basic_search query, options
|
175
|
+
elsif query.is_a?(Hash)
|
176
|
+
_advanced_search query, options
|
177
|
+
else
|
178
|
+
raise ArgumentError, "expected query to be a string or a hash"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# Submits transaction with +transaction_id+ for settlement.
|
183
|
+
def self.submit_for_settlement(transaction_id, amount = nil)
|
184
|
+
raise ArgumentError, "transaction_id is invalid" unless transaction_id =~ /\A[0-9a-z]+\z/
|
185
|
+
response = Http.put "/transactions/#{transaction_id}/submit_for_settlement", :transaction => {:amount => amount}
|
186
|
+
if response[:transaction]
|
187
|
+
SuccessfulResult.new(:transaction => new(response[:transaction]))
|
188
|
+
elsif response[:api_error_response]
|
189
|
+
ErrorResult.new(response[:api_error_response])
|
190
|
+
else
|
191
|
+
raise UnexpectedError, "expected :transaction or :response"
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def self.submit_for_settlement!(transaction_id, amount = nil)
|
196
|
+
return_object_or_raise(:transaction) { submit_for_settlement(transaction_id, amount) }
|
197
|
+
end
|
198
|
+
|
199
|
+
# Voids the transaction with the given <tt>transaction_id</tt>
|
200
|
+
def self.void(transaction_id)
|
201
|
+
response = Http.put "/transactions/#{transaction_id}/void"
|
202
|
+
if response[:transaction]
|
203
|
+
SuccessfulResult.new(:transaction => new(response[:transaction]))
|
204
|
+
elsif response[:api_error_response]
|
205
|
+
ErrorResult.new(response[:api_error_response])
|
206
|
+
else
|
207
|
+
raise UnexpectedError, "expected :transaction or :api_error_response"
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def self.void!(transaction_id)
|
212
|
+
return_object_or_raise(:transaction) { void(transaction_id) }
|
213
|
+
end
|
214
|
+
|
215
|
+
def initialize(attributes) # :nodoc:
|
216
|
+
_init attributes
|
217
|
+
end
|
218
|
+
|
219
|
+
# True if <tt>other</tt> has the same id.
|
220
|
+
def ==(other)
|
221
|
+
id == other.id
|
222
|
+
end
|
223
|
+
|
224
|
+
def inspect # :nodoc:
|
225
|
+
first = [:id, :type, :amount, :status]
|
226
|
+
order = first + (self.class._attributes - first)
|
227
|
+
nice_attributes = order.map do |attr|
|
228
|
+
"#{attr}: #{send(attr).inspect}"
|
229
|
+
end
|
230
|
+
"#<#{self.class} #{nice_attributes.join(', ')}>"
|
231
|
+
end
|
232
|
+
|
233
|
+
# Creates a credit transaction that refunds this transaction.
|
234
|
+
def refund
|
235
|
+
response = Http.post "/transactions/#{id}/refund"
|
236
|
+
if response[:transaction]
|
237
|
+
# TODO: need response to return original_transaction so that we can update status, updated_at, etc.
|
238
|
+
SuccessfulResult.new(:new_transaction => Transaction._new(response[:transaction]))
|
239
|
+
elsif response[:api_error_response]
|
240
|
+
ErrorResult.new(response[:api_error_response])
|
241
|
+
else
|
242
|
+
raise UnexpectedError, "expected :transaction or :api_error_response"
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
# Returns true if the transaction has been refunded. False otherwise.
|
247
|
+
def refunded?
|
248
|
+
!@refund_id.nil?
|
249
|
+
end
|
250
|
+
|
251
|
+
# Submits the transaction for settlement.
|
252
|
+
def submit_for_settlement(amount = nil)
|
253
|
+
response = Http.put "/transactions/#{id}/submit_for_settlement", :transaction => {:amount => amount}
|
254
|
+
if response[:transaction]
|
255
|
+
_init(response[:transaction])
|
256
|
+
SuccessfulResult.new :transaction => self
|
257
|
+
elsif response[:api_error_response]
|
258
|
+
ErrorResult.new(response[:api_error_response])
|
259
|
+
else
|
260
|
+
raise UnexpectedError, "expected transaction or api_error_response"
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
def submit_for_settlement!(amount = nil)
|
265
|
+
return_object_or_raise(:transaction) { submit_for_settlement(amount) }
|
266
|
+
end
|
267
|
+
|
268
|
+
# If this transaction was stored in the vault, or created from vault records,
|
269
|
+
# vault_credit_card will return the associated Braintree::CreditCard. Because the
|
270
|
+
# vault credit card can be updated after the transaction was created, the attributes
|
271
|
+
# on vault_credit_card may not match the attributes on credit_card_details.
|
272
|
+
def vault_credit_card
|
273
|
+
return nil if credit_card_details.token.nil?
|
274
|
+
CreditCard.find(credit_card_details.token)
|
275
|
+
end
|
276
|
+
|
277
|
+
# If this transaction was stored in the vault, or created from vault records,
|
278
|
+
# vault_customer will return the associated Braintree::Customer. Because the
|
279
|
+
# vault customer can be updated after the transaction was created, the attributes
|
280
|
+
# on vault_customer may not match the attributes on customer_details.
|
281
|
+
def vault_customer
|
282
|
+
return nil if customer_details.id.nil?
|
283
|
+
Customer.find(customer_details.id)
|
284
|
+
end
|
285
|
+
|
286
|
+
# Voids the transaction.
|
287
|
+
def void
|
288
|
+
response = Http.put "/transactions/#{id}/void"
|
289
|
+
if response[:transaction]
|
290
|
+
_init response[:transaction]
|
291
|
+
SuccessfulResult.new(:transaction => self)
|
292
|
+
elsif response[:api_error_response]
|
293
|
+
ErrorResult.new(response[:api_error_response])
|
294
|
+
else
|
295
|
+
raise UnexpectedError, "expected :transaction or :api_error_response"
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
def void!
|
300
|
+
return_object_or_raise(:transaction) { void }
|
301
|
+
end
|
302
|
+
|
303
|
+
class << self
|
304
|
+
protected :new
|
305
|
+
def _new(*args) # :nodoc:
|
306
|
+
self.new *args
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
def self._do_create(url, params) # :nodoc:
|
311
|
+
response = Http.post url, params
|
312
|
+
if response[:transaction]
|
313
|
+
SuccessfulResult.new(:transaction => new(response[:transaction]))
|
314
|
+
elsif response[:api_error_response]
|
315
|
+
ErrorResult.new(response[:api_error_response])
|
316
|
+
else
|
317
|
+
raise UnexpectedError, "expected :transaction or :api_error_response"
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
def self._advanced_search(query, options) # :nodoc:
|
322
|
+
page = options[:page] || 1
|
323
|
+
response = Http.post "/transactions/advanced_search?page=#{Util.url_encode(page)}", :search => query
|
324
|
+
attributes = response[:credit_card_transactions]
|
325
|
+
attributes[:items] = Util.extract_attribute_as_array(attributes, :transaction).map { |attrs| _new(attrs) }
|
326
|
+
PagedCollection.new(attributes) { |page_number| Transaction.search(query, :page => page_number) }
|
327
|
+
end
|
328
|
+
|
329
|
+
def self._attributes # :nodoc:
|
330
|
+
[:amount, :created_at, :credit_card_details, :customer_details, :id, :status, :type, :updated_at]
|
331
|
+
end
|
332
|
+
|
333
|
+
def self._basic_search(query, options) # :nodoc:
|
334
|
+
page = options[:page] || 1
|
335
|
+
response = Http.get "/transactions/all/search?q=#{Util.url_encode(query)}&page=#{Util.url_encode(page)}"
|
336
|
+
attributes = response[:credit_card_transactions]
|
337
|
+
attributes[:items] = Util.extract_attribute_as_array(attributes, :transaction).map { |attrs| _new(attrs) }
|
338
|
+
PagedCollection.new(attributes) { |page_number| Transaction.search(query, :page => page_number) }
|
339
|
+
end
|
340
|
+
|
341
|
+
def self._create_signature # :nodoc:
|
342
|
+
[
|
343
|
+
:amount, :customer_id, :order_id, :payment_method_token, :type,
|
344
|
+
{:credit_card => [:token, :cvv, :expiration_date, :number]},
|
345
|
+
{:customer => [:id, :company, :email, :fax, :first_name, :last_name, :phone, :website]},
|
346
|
+
{:billing => [:first_name, :last_name, :company, :country_name, :extended_address, :locality, :postal_code, :region, :street_address]},
|
347
|
+
{:shipping => [:first_name, :last_name, :company, :country_name, :extended_address, :locality, :postal_code, :region, :street_address]},
|
348
|
+
{:options => [:store_in_vault, :submit_for_settlement]}
|
349
|
+
]
|
350
|
+
end
|
351
|
+
|
352
|
+
def _init(attributes) # :nodoc:
|
353
|
+
set_instance_variables_from_hash(attributes)
|
354
|
+
@credit_card_details = CreditCardDetails.new(@credit_card)
|
355
|
+
@customer_details = CustomerDetails.new(@customer)
|
356
|
+
@billing_details = AddressDetails.new(@billing)
|
357
|
+
@shipping_details = AddressDetails.new(@shipping)
|
358
|
+
end
|
359
|
+
end
|
360
|
+
end
|