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
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
|