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
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2009 Braintree Payment Solutions
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
= Braintree Ruby Client Library
|
2
|
+
|
3
|
+
The Braintree gem provides integration access to the Braintree Gateway.
|
4
|
+
|
5
|
+
== Dependencies
|
6
|
+
|
7
|
+
* builder
|
8
|
+
* libxml-ruby
|
9
|
+
|
10
|
+
== Quick Start Example
|
11
|
+
|
12
|
+
require "rubygems"
|
13
|
+
require "braintree"
|
14
|
+
|
15
|
+
Braintree::Configuration.environment = :sandbox
|
16
|
+
Braintree::Configuration.merchant_id = "the_merchant_id"
|
17
|
+
Braintree::Configuration.public_key = "a_public_key"
|
18
|
+
Braintree::Configuration.private_key = "a_private_key"
|
19
|
+
|
20
|
+
transaction = Braintree::Transaction.sale!(
|
21
|
+
:amount => "100.00",
|
22
|
+
:credit_card => {
|
23
|
+
:number => "5105105105105100",
|
24
|
+
:expiration_date => "05/12"
|
25
|
+
}
|
26
|
+
)
|
27
|
+
puts "Transaction ID: #{transaction.id}"
|
28
|
+
puts "Status: #{transaction.status}"
|
29
|
+
|
30
|
+
== Bang Methods
|
31
|
+
|
32
|
+
Most methods have a bang and a non-bang version (e.g. <tt>Braintree::Customer.create</tt> and <tt>Braintree::Customer.create!</tt>).
|
33
|
+
The non-bang version will either return a +SuccessfulResult+ or an +ErrorResult+. The bang version will either return
|
34
|
+
the created or updated resource, or it will raise a ValidationsFailed exception.
|
35
|
+
|
36
|
+
Example of using non-bang method:
|
37
|
+
|
38
|
+
result = Braintree::Customer.create!(:first_name => "Josh")
|
39
|
+
if result.success?
|
40
|
+
puts "Created customer #{result.customer.id}
|
41
|
+
else
|
42
|
+
puts "Validations failed"
|
43
|
+
result.errors.for(:customer).each do |error|
|
44
|
+
puts error.message
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
Example of using bang method:
|
49
|
+
|
50
|
+
begin
|
51
|
+
customer = Braintree::Customer.create!(:first_name => "Josh")
|
52
|
+
puts "Created customer #{customer.id}
|
53
|
+
rescue Braintree::ValidationsFailed
|
54
|
+
puts "Validations failed"
|
55
|
+
end
|
56
|
+
|
57
|
+
We recommend using the bang methods when you assume that the data is valid and do not expect validations to fail.
|
58
|
+
Otherwise, we recommend using the non-bang methods.
|
59
|
+
|
60
|
+
== License
|
61
|
+
|
62
|
+
See the LICENSE file.
|
data/lib/braintree.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
require "cgi"
|
2
|
+
require "digest/sha1"
|
3
|
+
require "logger"
|
4
|
+
require "net/http"
|
5
|
+
require "net/https"
|
6
|
+
require "stringio"
|
7
|
+
require "time"
|
8
|
+
require "zlib"
|
9
|
+
|
10
|
+
require "builder"
|
11
|
+
require "libxml"
|
12
|
+
unless ::LibXML::XML.respond_to?(:default_keep_blanks=)
|
13
|
+
raise LoadError, "The version of libxml that you're using is not compatible with the Braintree gem."
|
14
|
+
end
|
15
|
+
|
16
|
+
module Braintree # :nodoc:
|
17
|
+
# Super class for all Braintree exceptions.
|
18
|
+
class BraintreeError < ::StandardError; end
|
19
|
+
|
20
|
+
# Raised when authentication fails. This may be caused by an incorrect <tt>Braintree::Configuration</tt>
|
21
|
+
class AuthenticationError < BraintreeError; end
|
22
|
+
|
23
|
+
# Raised when the API key being used is not authorized to perform the attempted action according
|
24
|
+
# to the roles assigned to the user who owns the API key.
|
25
|
+
class AuthorizationError < BraintreeError; end
|
26
|
+
|
27
|
+
# Raised when the Braintree gem is not completely configured. See <tt>Braintree::Configuration</tt>.
|
28
|
+
class ConfigurationError < BraintreeError
|
29
|
+
def initialize(setting, message) # :nodoc:
|
30
|
+
super "Braintree::Configuration.#{setting} #{message}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Raised when the gateway is down for maintenance.
|
35
|
+
class DownForMaintenanceError < BraintreeError; end
|
36
|
+
|
37
|
+
# Raised from methods that confirm transparent request requests
|
38
|
+
# when the given query string cannot be verified. This may indicate
|
39
|
+
# an attempted hack on the merchant's transparent redirect
|
40
|
+
# confirmation URL.
|
41
|
+
class ForgedQueryString < BraintreeError; end
|
42
|
+
|
43
|
+
# Raised when a record could not be found.
|
44
|
+
class NotFoundError < BraintreeError; end
|
45
|
+
|
46
|
+
# Raised when an unexpected server error occurs.
|
47
|
+
class ServerError < BraintreeError; end
|
48
|
+
|
49
|
+
# Raised when the SSL certificate fails verification.
|
50
|
+
class SSLCertificateError < BraintreeError; end
|
51
|
+
|
52
|
+
# Raised when an error occurs that the client library is not built to handle.
|
53
|
+
# This shouldn't happen.
|
54
|
+
class UnexpectedError < BraintreeError; end
|
55
|
+
|
56
|
+
# Raised from bang methods when validations fail.
|
57
|
+
class ValidationsFailed < BraintreeError; end
|
58
|
+
end
|
59
|
+
|
60
|
+
require "braintree/base_module"
|
61
|
+
|
62
|
+
Dir.glob("#{File.dirname(__FILE__)}/braintree/**/*.rb").sort.each do |file|
|
63
|
+
next if file =~ /base_module/
|
64
|
+
load file
|
65
|
+
end
|
66
|
+
Braintree::SSLExpirationCheck.check_dates
|
@@ -0,0 +1,122 @@
|
|
1
|
+
module Braintree
|
2
|
+
# An Address belongs to a Customer. It can be associated to a
|
3
|
+
# CreditCard as the billing address. It can also be used
|
4
|
+
# as the shipping address when creating a Transaction.
|
5
|
+
class Address
|
6
|
+
include BaseModule # :nodoc:
|
7
|
+
|
8
|
+
attr_reader :company, :country_name, :created_at, :customer_id, :extended_address, :first_name, :id,
|
9
|
+
:last_name, :locality, :postal_code, :region, :street_address, :updated_at
|
10
|
+
|
11
|
+
def self.create(attributes)
|
12
|
+
Util.verify_keys(_create_signature, attributes)
|
13
|
+
unless attributes[:customer_id]
|
14
|
+
raise ArgumentError, "Expected hash to contain a :customer_id"
|
15
|
+
end
|
16
|
+
unless attributes[:customer_id] =~ /\A[0-9A-Za-z_-]+\z/
|
17
|
+
raise ArgumentError, ":customer_id contains invalid characters"
|
18
|
+
end
|
19
|
+
response = Http.post "/customers/#{attributes.delete(:customer_id)}/addresses", :address => attributes
|
20
|
+
if response[:address]
|
21
|
+
SuccessfulResult.new(:address => new(response[:address]))
|
22
|
+
elsif response[:api_error_response]
|
23
|
+
ErrorResult.new(response[:api_error_response])
|
24
|
+
else
|
25
|
+
raise UnexpectedError, "expected :address or :api_error_response"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.create!(attributes)
|
30
|
+
return_object_or_raise(:address) { create(attributes) }
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.delete(customer_or_customer_id, address_id)
|
34
|
+
customer_id = _determine_customer_id(customer_or_customer_id)
|
35
|
+
Http.delete("/customers/#{customer_id}/addresses/#{address_id}")
|
36
|
+
SuccessfulResult.new
|
37
|
+
end
|
38
|
+
|
39
|
+
# Finds the address with the given +address_id+ that is associated to the given +customer_or_customer_id+.
|
40
|
+
# If the address cannot be found, a NotFoundError will be raised.
|
41
|
+
def self.find(customer_or_customer_id, address_id)
|
42
|
+
customer_id = _determine_customer_id(customer_or_customer_id)
|
43
|
+
response = Http.get("/customers/#{customer_id}/addresses/#{address_id}")
|
44
|
+
new(response[:address])
|
45
|
+
rescue NotFoundError
|
46
|
+
raise NotFoundError, "address for customer #{customer_id.inspect} with id #{address_id.inspect} not found"
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.update(customer_or_customer_id, address_id, attributes)
|
50
|
+
Util.verify_keys(_update_signature, attributes)
|
51
|
+
customer_id = _determine_customer_id(customer_or_customer_id)
|
52
|
+
response = Http.put "/customers/#{customer_id}/addresses/#{address_id}", :address => attributes
|
53
|
+
if response[:address]
|
54
|
+
SuccessfulResult.new(:address => new(response[:address]))
|
55
|
+
elsif response[:api_error_response]
|
56
|
+
ErrorResult.new(response[:api_error_response])
|
57
|
+
else
|
58
|
+
raise UnexpectedError, "expected :address or :api_error_response"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.update!(customer_or_customer_id, address_id, attributes)
|
63
|
+
return_object_or_raise(:address) { update(customer_or_customer_id, address_id, attributes) }
|
64
|
+
end
|
65
|
+
|
66
|
+
def initialize(attributes) # :nodoc:
|
67
|
+
set_instance_variables_from_hash(attributes)
|
68
|
+
end
|
69
|
+
|
70
|
+
def ==(other) # :nodoc:
|
71
|
+
return false unless other.is_a?(Address)
|
72
|
+
id == other.id && customer_id == other.customer_id
|
73
|
+
end
|
74
|
+
|
75
|
+
# Deletes the address.
|
76
|
+
def delete
|
77
|
+
Address.delete(customer_id, self.id)
|
78
|
+
end
|
79
|
+
|
80
|
+
def update(attributes)
|
81
|
+
Util.verify_keys(self.class._update_signature, attributes)
|
82
|
+
response = Http.put "/customers/#{customer_id}/addresses/#{id}", :address => attributes
|
83
|
+
if response[:address]
|
84
|
+
set_instance_variables_from_hash response[:address]
|
85
|
+
SuccessfulResult.new(:address => self)
|
86
|
+
elsif response[:api_error_response]
|
87
|
+
ErrorResult.new(response[:api_error_response])
|
88
|
+
else
|
89
|
+
raise UnexpectedError, "expected :address or :api_error_response"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def update!(attributes)
|
94
|
+
return_object_or_raise(:address) { update(attributes) }
|
95
|
+
end
|
96
|
+
|
97
|
+
class << self
|
98
|
+
protected :new
|
99
|
+
end
|
100
|
+
|
101
|
+
def self._create_signature # :nodoc:
|
102
|
+
[:company, :country_name, :customer_id, :extended_address, :first_name,
|
103
|
+
:last_name, :locality, :postal_code, :region, :street_address]
|
104
|
+
end
|
105
|
+
|
106
|
+
def self._determine_customer_id(customer_or_customer_id) # :nodoc:
|
107
|
+
customer_id = customer_or_customer_id.is_a?(Customer) ? customer_or_customer_id.id : customer_or_customer_id
|
108
|
+
unless customer_id =~ /\A[\w_-]+\z/
|
109
|
+
raise ArgumentError, "customer_id contains invalid characters"
|
110
|
+
end
|
111
|
+
customer_id
|
112
|
+
end
|
113
|
+
|
114
|
+
def self._new(*args) # :nodoc:
|
115
|
+
self.new *args
|
116
|
+
end
|
117
|
+
|
118
|
+
def self._update_signature # :nodoc:
|
119
|
+
_create_signature
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Braintree
|
2
|
+
module BaseModule # :nodoc: all
|
3
|
+
module Methods
|
4
|
+
def return_object_or_raise(object_to_return)
|
5
|
+
result = yield
|
6
|
+
if result.success?
|
7
|
+
result.send object_to_return
|
8
|
+
else
|
9
|
+
raise ValidationsFailed
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def set_instance_variables_from_hash(hash)
|
14
|
+
hash.each do |key, value|
|
15
|
+
instance_variable_set "@#{key}", value
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def singleton_class
|
20
|
+
class << self; self; end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.included(klass)
|
25
|
+
klass.extend Methods
|
26
|
+
end
|
27
|
+
include Methods
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Braintree
|
2
|
+
# The following configuration attributes need to be set to use the gem:
|
3
|
+
# * merchant_id
|
4
|
+
# * public_key
|
5
|
+
# * private_key
|
6
|
+
# * environment
|
7
|
+
#
|
8
|
+
# By default, the logger will log to +STDOUT+. The log level is set to info.
|
9
|
+
# The logger can be set to any Logger object.
|
10
|
+
module Configuration
|
11
|
+
API_VERSION = "1" # :nodoc:
|
12
|
+
|
13
|
+
class << self
|
14
|
+
attr_accessor :logger
|
15
|
+
attr_writer :merchant_id, :public_key, :private_key
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.expectant_reader(*attributes) # :nodoc:
|
19
|
+
attributes.each do |attribute|
|
20
|
+
(class << self; self; end).send(:define_method, attribute) do
|
21
|
+
attribute_value = instance_variable_get("@#{attribute}")
|
22
|
+
raise ConfigurationError.new(attribute.to_s, "needs to be set") unless attribute_value
|
23
|
+
attribute_value
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
expectant_reader :environment, :merchant_id, :public_key, :private_key
|
28
|
+
|
29
|
+
def self.base_merchant_url # :nodoc:
|
30
|
+
"#{protocol}://#{server}:#{port}#{base_merchant_path}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.base_merchant_path # :nodoc:
|
34
|
+
"/merchants/#{Braintree::Configuration.merchant_id}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.ca_file # :nodoc:
|
38
|
+
case environment
|
39
|
+
when :qa, :sandbox
|
40
|
+
File.expand_path(File.join(File.dirname(__FILE__), "..", "ssl", "valicert_ca.crt"))
|
41
|
+
when :production
|
42
|
+
File.expand_path(File.join(File.dirname(__FILE__), "..", "ssl", "securetrust_ca.crt"))
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Sets the Braintree environment to use. Valid values are <tt>:sandbox</tt> and <tt>:production</tt>
|
47
|
+
def self.environment=(env)
|
48
|
+
unless [:development, :qa, :sandbox, :production].include?(env)
|
49
|
+
raise ArgumentError, "#{env.inspect} is not a valid environment"
|
50
|
+
end
|
51
|
+
@environment = env
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.logger # :nodoc:
|
55
|
+
@logger ||= _default_logger
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.port # :nodoc:
|
59
|
+
case environment
|
60
|
+
when :development
|
61
|
+
3000
|
62
|
+
when :production, :qa, :sandbox
|
63
|
+
443
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.protocol # :nodoc:
|
68
|
+
ssl? ? "https" : "http"
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.server # :nodoc:
|
72
|
+
case environment
|
73
|
+
when :development
|
74
|
+
"localhost"
|
75
|
+
when :production
|
76
|
+
"www.braintreegateway.com"
|
77
|
+
when :qa
|
78
|
+
"qa.braintreegateway.com"
|
79
|
+
when :sandbox
|
80
|
+
"sandbox.braintreegateway.com"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.ssl? # :nodoc:
|
85
|
+
case environment
|
86
|
+
when :development
|
87
|
+
false
|
88
|
+
when :production, :qa, :sandbox
|
89
|
+
true
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def self._default_logger # :nodoc:
|
94
|
+
logger = Logger.new(STDOUT)
|
95
|
+
logger.level = Logger::INFO
|
96
|
+
logger
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,231 @@
|
|
1
|
+
module Braintree
|
2
|
+
class CreditCard
|
3
|
+
include BaseModule # :nodoc:
|
4
|
+
|
5
|
+
attr_reader :billing_address, :bin, :card_type, :cardholder_name, :created_at, :customer_id, :expiration_month,
|
6
|
+
:expiration_year, :last_4, :token, :updated_at
|
7
|
+
|
8
|
+
def self.create(attributes)
|
9
|
+
if attributes.has_key?(:expiration_date) && (attributes.has_key?(:expiration_month) || attributes.has_key?(:expiration_year))
|
10
|
+
raise ArgumentError.new("create with both expiration_month and expiration_year or only expiration_date")
|
11
|
+
end
|
12
|
+
Util.verify_keys(_create_signature, attributes)
|
13
|
+
_do_create("/payment_methods", :credit_card => attributes)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.create!(attributes)
|
17
|
+
return_object_or_raise(:credit_card) { create(attributes) }
|
18
|
+
end
|
19
|
+
|
20
|
+
# The transparent redirect URL to use to create a credit card.
|
21
|
+
def self.create_credit_card_url
|
22
|
+
"#{Braintree::Configuration.base_merchant_url}/payment_methods/all/create_via_transparent_redirect_request"
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.create_from_transparent_redirect(query_string)
|
26
|
+
params = TransparentRedirect.parse_and_validate_query_string query_string
|
27
|
+
_do_create("/payment_methods/all/confirm_transparent_redirect_request", :id => params[:id])
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.credit(token, transaction_attributes)
|
31
|
+
Transaction.credit(transaction_attributes.merge(
|
32
|
+
:payment_method_token => token
|
33
|
+
))
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.credit!(token, transaction_attributes)
|
37
|
+
return_object_or_raise(:transaction) { credit(token, transaction_attributes) }
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns a PagedCollection of expired credit cards.
|
41
|
+
def self.expired(options = {})
|
42
|
+
page_number = options[:page] || 1
|
43
|
+
response = Http.get("/payment_methods/all/expired?page=#{page_number}")
|
44
|
+
attributes = response[:payment_methods]
|
45
|
+
attributes[:items] = Util.extract_attribute_as_array(attributes, :credit_card).map do |payment_method_attributes|
|
46
|
+
new payment_method_attributes
|
47
|
+
end
|
48
|
+
PagedCollection.new(attributes) { |page_number| CreditCard.expired(:page => page_number) }
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns a PagedCollection of credit cards expiring between +start_date+ and +end_date+ inclusive.
|
52
|
+
# Only the month and year of the start and end dates are used.
|
53
|
+
def self.expiring_between(start_date, end_date, options = {})
|
54
|
+
page_number = options[:page] || 1
|
55
|
+
response = Http.get("/payment_methods/all/expiring?page=#{page_number}&start=#{start_date.strftime('%m%Y')}&end=#{end_date.strftime('%m%Y')}")
|
56
|
+
attributes = response[:payment_methods]
|
57
|
+
attributes[:items] = Util.extract_attribute_as_array(attributes, :credit_card).map do |payment_method_attributes|
|
58
|
+
new payment_method_attributes
|
59
|
+
end
|
60
|
+
PagedCollection.new(attributes) { |page_number| CreditCard.expiring_between(start_date, end_date, :page => page_number) }
|
61
|
+
end
|
62
|
+
|
63
|
+
# Finds the credit card with the given +token+. Raises a NotFoundError if it cannot be found.
|
64
|
+
def self.find(token)
|
65
|
+
response = Http.get "/payment_methods/#{token}"
|
66
|
+
new(response[:credit_card])
|
67
|
+
rescue NotFoundError
|
68
|
+
raise NotFoundError, "payment method with token #{token.inspect} not found"
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.sale(token, transaction_attributes)
|
72
|
+
Transaction.sale(transaction_attributes.merge(
|
73
|
+
:payment_method_token => token
|
74
|
+
))
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.sale!(token, transaction_attributes)
|
78
|
+
return_object_or_raise(:transaction) { sale(token, transaction_attributes) }
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.update_from_transparent_redirect(query_string)
|
82
|
+
params = TransparentRedirect.parse_and_validate_query_string query_string
|
83
|
+
_do_update(:post, "/payment_methods/all/confirm_transparent_redirect_request", :id => params[:id])
|
84
|
+
end
|
85
|
+
|
86
|
+
# The transparent redirect URL to use to update a credit card.
|
87
|
+
def self.update_credit_card_url
|
88
|
+
"#{Braintree::Configuration.base_merchant_url}/payment_methods/all/update_via_transparent_redirect_request"
|
89
|
+
end
|
90
|
+
|
91
|
+
def initialize(attributes) # :nodoc:
|
92
|
+
_init attributes
|
93
|
+
end
|
94
|
+
|
95
|
+
# Creates a credit transaction for this credit card.
|
96
|
+
def credit(transaction_attributes)
|
97
|
+
Transaction.credit(transaction_attributes.merge(
|
98
|
+
:payment_method_token => self.token
|
99
|
+
))
|
100
|
+
end
|
101
|
+
|
102
|
+
def credit!(transaction_attributes)
|
103
|
+
return_object_or_raise(:transaction) { credit(transaction_attributes) }
|
104
|
+
end
|
105
|
+
|
106
|
+
def delete
|
107
|
+
Http.delete("/payment_methods/#{token}")
|
108
|
+
end
|
109
|
+
|
110
|
+
# Returns true if this credit card is the customer's default.
|
111
|
+
def default?
|
112
|
+
@default
|
113
|
+
end
|
114
|
+
|
115
|
+
# Expiration date formatted as MM/YYYY
|
116
|
+
def expiration_date
|
117
|
+
"#{expiration_month}/#{expiration_year}"
|
118
|
+
end
|
119
|
+
|
120
|
+
# Returns true if the credit card is expired.
|
121
|
+
def expired?
|
122
|
+
if expiration_year.to_i == Time.now.year
|
123
|
+
return expiration_month.to_i < Time.now.month
|
124
|
+
end
|
125
|
+
expiration_year.to_i < Time.now.year
|
126
|
+
end
|
127
|
+
|
128
|
+
def inspect # :nodoc:
|
129
|
+
first = [:token]
|
130
|
+
order = first + (self.class._attributes - first)
|
131
|
+
nice_attributes = order.map do |attr|
|
132
|
+
"#{attr}: #{send(attr).inspect}"
|
133
|
+
end
|
134
|
+
"#<#{self.class} #{nice_attributes.join(', ')}>"
|
135
|
+
end
|
136
|
+
|
137
|
+
def masked_number
|
138
|
+
"#{bin}******#{last_4}"
|
139
|
+
end
|
140
|
+
|
141
|
+
# Creates a sale transaction for this credit card.
|
142
|
+
def sale(transaction_attributes)
|
143
|
+
CreditCard.sale(self.token, transaction_attributes)
|
144
|
+
end
|
145
|
+
|
146
|
+
def sale!(transaction_attributes)
|
147
|
+
return_object_or_raise(:transaction) { sale(transaction_attributes) }
|
148
|
+
end
|
149
|
+
|
150
|
+
def update(attributes)
|
151
|
+
Util.verify_keys(self.class._update_signature, attributes)
|
152
|
+
response = Http.put "/payment_methods/#{token}", :credit_card => attributes
|
153
|
+
if response[:credit_card]
|
154
|
+
_init response[:credit_card]
|
155
|
+
SuccessfulResult.new(:credit_card => self)
|
156
|
+
elsif response[:api_error_response]
|
157
|
+
ErrorResult.new(response[:api_error_response])
|
158
|
+
else
|
159
|
+
raise UnexpectedError, "expected :credit_card or :api_error_response"
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def update!(attributes)
|
164
|
+
return_object_or_raise(:credit_card) { update(attributes) }
|
165
|
+
end
|
166
|
+
|
167
|
+
# Returns true if +other+ is a +CreditCard+ with the same token.
|
168
|
+
def ==(other)
|
169
|
+
return false unless other.is_a?(CreditCard)
|
170
|
+
token == other.token
|
171
|
+
end
|
172
|
+
|
173
|
+
class << self
|
174
|
+
protected :new
|
175
|
+
end
|
176
|
+
|
177
|
+
def self._attributes # :nodoc:
|
178
|
+
[
|
179
|
+
:billing_address, :bin, :card_type, :cardholder_name, :created_at, :customer_id, :expiration_month,
|
180
|
+
:expiration_year, :last_4, :token, :updated_at
|
181
|
+
]
|
182
|
+
end
|
183
|
+
|
184
|
+
def self._create_signature # :nodoc:
|
185
|
+
[
|
186
|
+
:customer_id, :cardholder_name, :cvv, :number, :expiration_date, :token,
|
187
|
+
{:options => [:verify_card]},
|
188
|
+
{:billing_address => [:first_name, :last_name, :company, :country_name, :extended_address, :locality, :region, :postal_code, :street_address]}
|
189
|
+
]
|
190
|
+
end
|
191
|
+
|
192
|
+
def self._new(*args) # :nodoc:
|
193
|
+
self.new *args
|
194
|
+
end
|
195
|
+
|
196
|
+
def self._do_create(url, params) # :nodoc:
|
197
|
+
response = Http.post url, params
|
198
|
+
if response[:credit_card]
|
199
|
+
SuccessfulResult.new(:credit_card => new(response[:credit_card]))
|
200
|
+
elsif response[:api_error_response]
|
201
|
+
ErrorResult.new(response[:api_error_response])
|
202
|
+
else
|
203
|
+
raise UnexpectedError, "expected :credit_card or :api_error_response"
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def self._do_update(http_verb, url, params) # :nodoc:
|
208
|
+
response = Http.send http_verb, url, params
|
209
|
+
if response[:credit_card]
|
210
|
+
SuccessfulResult.new(:credit_card => new(response[:credit_card]))
|
211
|
+
elsif response[:api_error_response]
|
212
|
+
ErrorResult.new(response[:api_error_response])
|
213
|
+
else
|
214
|
+
raise UnexpectedError, "expected :credit_card or :api_error_response"
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def self._update_signature # :nodoc:
|
219
|
+
[
|
220
|
+
:cardholder_name, :cvv, :number, :expiration_date, :token,
|
221
|
+
{:options => [:verify_card]},
|
222
|
+
{:billing_address => [:first_name, :last_name, :company, :country_name, :extended_address, :locality, :region, :postal_code, :street_address]}
|
223
|
+
]
|
224
|
+
end
|
225
|
+
|
226
|
+
def _init(attributes) # :nodoc:
|
227
|
+
set_instance_variables_from_hash(attributes)
|
228
|
+
@billing_address = attributes[:billing_address] ? Address._new(attributes[:billing_address]) : nil
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|