braintree 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|