braintree-rails 0.0.1
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/Gemfile +10 -0
- data/Gemfile.lock +34 -0
- data/README.md +2 -0
- data/Rakefile +4 -0
- data/braintree-rails.gemspec +18 -0
- data/lib/braintree-rails.rb +17 -0
- data/lib/braintree_rails/address.rb +75 -0
- data/lib/braintree_rails/addresses.rb +24 -0
- data/lib/braintree_rails/credit_card.rb +67 -0
- data/lib/braintree_rails/credit_cards.rb +24 -0
- data/lib/braintree_rails/customer.rb +37 -0
- data/lib/braintree_rails/exceptions.rb +2 -0
- data/lib/braintree_rails/model.rb +192 -0
- data/lib/env.rb +3 -0
- data/lib/tasks/test.rake +18 -0
- data/lib/test_env.rb +4 -0
- data/log/braintree_test.log +225696 -0
- data/test/config/braintree_auth.yml +4 -0
- data/test/config/braintree_auth.yml.example +4 -0
- data/test/fixtures/address.xml +19 -0
- data/test/fixtures/credit_card.xml +36 -0
- data/test/fixtures/credit_card_validation_error.xml +26 -0
- data/test/fixtures/customer.xml +90 -0
- data/test/integration/braintree_rails/address_integration_test.rb +72 -0
- data/test/integration/braintree_rails/credit_card_integration_test.rb +147 -0
- data/test/integration/braintree_rails/customer_integration_test.rb +41 -0
- data/test/integration/integration_test_helper.rb +13 -0
- data/test/test_helper.rb +28 -0
- data/test/unit/braintree_rails/address_test.rb +87 -0
- data/test/unit/braintree_rails/addresses_test.rb +38 -0
- data/test/unit/braintree_rails/credit_card_test.rb +240 -0
- data/test/unit/braintree_rails/credit_cards_test.rb +38 -0
- data/test/unit/braintree_rails/customer_test.rb +171 -0
- data/test/unit/unit_test_helper.rb +3 -0
- metadata +176 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
activemodel (3.2.8)
|
5
|
+
activesupport (= 3.2.8)
|
6
|
+
builder (~> 3.0.0)
|
7
|
+
activesupport (3.2.8)
|
8
|
+
i18n (~> 0.6)
|
9
|
+
multi_json (~> 1.0)
|
10
|
+
addressable (2.3.2)
|
11
|
+
ansi (1.4.3)
|
12
|
+
braintree (2.14.0)
|
13
|
+
builder (>= 2.0.0)
|
14
|
+
builder (3.0.3)
|
15
|
+
crack (0.3.1)
|
16
|
+
i18n (0.6.1)
|
17
|
+
minitest (3.4.0)
|
18
|
+
multi_json (1.3.6)
|
19
|
+
turn (0.9.6)
|
20
|
+
ansi
|
21
|
+
webmock (1.8.10)
|
22
|
+
addressable (>= 2.2.7)
|
23
|
+
crack (>= 0.1.7)
|
24
|
+
|
25
|
+
PLATFORMS
|
26
|
+
ruby
|
27
|
+
|
28
|
+
DEPENDENCIES
|
29
|
+
activemodel (~> 3.0)
|
30
|
+
activesupport (~> 3.0)
|
31
|
+
braintree (~> 2.14.0)
|
32
|
+
minitest
|
33
|
+
turn
|
34
|
+
webmock
|
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
Gem::Specification.new do |spec|
|
2
|
+
spec.name = 'braintree-rails'
|
3
|
+
spec.version = '0.0.1'
|
4
|
+
spec.summary = 'Provides ActiveModel compatible wrappers for Braintree models.'
|
5
|
+
spec.description = 'Provides ActiveModel compatible wrappers for Braintree models and more.'
|
6
|
+
spec.author = 'Lin Yang'
|
7
|
+
spec.email = 'github@linyang.me'
|
8
|
+
spec.license = 'MIT'
|
9
|
+
spec.files = Dir['**/*']
|
10
|
+
spec.test_files = Dir['test/**']
|
11
|
+
spec.homepage = 'https://github.com/lyang/braintree-rails'
|
12
|
+
spec.add_runtime_dependency 'braintree', '~> 2.14.0'
|
13
|
+
spec.add_runtime_dependency 'activemodel', '~> 3.0'
|
14
|
+
spec.add_runtime_dependency 'activesupport', '~> 3.0'
|
15
|
+
spec.add_development_dependency 'minitest'
|
16
|
+
spec.add_development_dependency 'webmock'
|
17
|
+
spec.add_development_dependency 'turn'
|
18
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'env'))
|
2
|
+
require 'active_model'
|
3
|
+
require 'active_support/core_ext/object/try'
|
4
|
+
require 'active_support/core_ext/hash/except'
|
5
|
+
require 'active_support/inflector'
|
6
|
+
require 'ostruct'
|
7
|
+
require 'delegate'
|
8
|
+
require 'braintree'
|
9
|
+
require 'braintree/exceptions'
|
10
|
+
|
11
|
+
require 'model'
|
12
|
+
require 'exceptions'
|
13
|
+
require 'address'
|
14
|
+
require 'addresses'
|
15
|
+
require 'credit_card'
|
16
|
+
require 'credit_cards'
|
17
|
+
require 'customer'
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module BraintreeRails
|
2
|
+
class Address < SimpleDelegator
|
3
|
+
Attributes = [:id, :customer_id, :first_name, :last_name, :company, :street_address, :extended_address, :locality, :country_name, :country_code_alpha2, :country_code_alpha3, :country_code_numeric, :region, :postal_code]
|
4
|
+
include Model
|
5
|
+
|
6
|
+
validates :first_name, :last_name, :company, :street_address, :extended_address, :locality, :region, :length => {:maximum => 255}
|
7
|
+
validates :country_code_alpha2, :inclusion => { :in => Braintree::Address::CountryNames.map {|country| country[1]} }
|
8
|
+
validates :postal_code, :street_address, :presence => true
|
9
|
+
validates :postal_code, :format => { :with => /^[- a-z0-9]+$/i}
|
10
|
+
|
11
|
+
def initialize(address = {})
|
12
|
+
address = ensure_address(address)
|
13
|
+
assign_attributes(extract_values(address))
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
17
|
+
def country_name=(val)
|
18
|
+
self.country_code_alpha2= Braintree::Address::CountryNames.find{|country| country[0] == val}.try(:[], 1)
|
19
|
+
@country_name = val
|
20
|
+
end
|
21
|
+
|
22
|
+
def country_code_alpha3=(val)
|
23
|
+
self.country_code_alpha2= Braintree::Address::CountryNames.find{|country| country[2] == val}.try(:[], 1)
|
24
|
+
@country_code_alpha3 = val
|
25
|
+
end
|
26
|
+
|
27
|
+
def country_code_numeric=(val)
|
28
|
+
self.country_code_alpha2= Braintree::Address::CountryNames.find{|country| country[3] == val}.try(:[], 1)
|
29
|
+
@country_code_numeric = val
|
30
|
+
end
|
31
|
+
|
32
|
+
def destroy!
|
33
|
+
if persisted?
|
34
|
+
self.class.braintree_model_class.delete(customer_id, id)
|
35
|
+
end
|
36
|
+
@persisted = false
|
37
|
+
freeze
|
38
|
+
end
|
39
|
+
|
40
|
+
protected
|
41
|
+
def ensure_address(address)
|
42
|
+
case address
|
43
|
+
when Braintree::Address
|
44
|
+
@persisted = true
|
45
|
+
address
|
46
|
+
when Hash
|
47
|
+
@persisted = false
|
48
|
+
OpenStruct.new(address)
|
49
|
+
else
|
50
|
+
@persisted = address.respond_to?(:persisted?) ? address.persisted? : false
|
51
|
+
address
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def update
|
56
|
+
with_update_braintree do
|
57
|
+
self.class.braintree_model_class.update(self.customer_id, self.id, self.attributes.except(:id, :customer_id))
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def update!
|
62
|
+
with_update_braintree do
|
63
|
+
self.class.braintree_model_class.update!(self.customer_id, self.id, self.attributes.except(:id, :customer_id))
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def attributes_to_exclude_from_update
|
68
|
+
[:id, :customer_id, :country_name, :country_code_alpha3, :country_code_numeric]
|
69
|
+
end
|
70
|
+
|
71
|
+
def attributes_to_exclude_from_create
|
72
|
+
[:country_name, :country_code_alpha3, :country_code_numeric]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module BraintreeRails
|
2
|
+
class Addresses < SimpleDelegator
|
3
|
+
def initialize(customer, addresses)
|
4
|
+
@customer = customer
|
5
|
+
super(Array(addresses).map{|address| Address.new(address)})
|
6
|
+
end
|
7
|
+
|
8
|
+
def find(id = nil, &block)
|
9
|
+
id.nil? ? super(&block) : super() { |a| a.id == id }
|
10
|
+
end
|
11
|
+
|
12
|
+
def build(params)
|
13
|
+
Address.new(params.merge(:customer_id => @customer.id))
|
14
|
+
end
|
15
|
+
|
16
|
+
def create(params)
|
17
|
+
build(params).tap { |address| address.save }
|
18
|
+
end
|
19
|
+
|
20
|
+
def create!(params)
|
21
|
+
build(params).tap { |address| address.save! }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module BraintreeRails
|
2
|
+
class CreditCard < SimpleDelegator
|
3
|
+
Attributes = [:customer_id, :number, :token, :cvv, :cardholder_name, :expiration_date, :expiration_month, :expiration_year, :billing_address].freeze
|
4
|
+
|
5
|
+
include Model
|
6
|
+
|
7
|
+
validates :customer_id, :presence => true, :length => {:maximum => 36}, :if => :new_record?
|
8
|
+
validates :number, :presence => true, :numericality => { :only_integer => true }, :length => {:minimum => 12, :maximum => 19}, :if => :new_record?
|
9
|
+
validates :cvv, :presence => true, :numericality => { :only_integer => true, :greater_than_or_equal_to => 100, :less_than_or_equal_to => 9999 }
|
10
|
+
validates :cardholder_name, :length => {:maximum => 255}
|
11
|
+
validates :expiration_month, :presence => true, :numericality => { :only_integer => true, :greater_than_or_equal_to => 1, :less_than_or_equal_to => 12 }
|
12
|
+
validates :expiration_year, :presence => true, :numericality => { :only_integer => true, :greater_than_or_equal_to => 1976, :less_than_or_equal_to => 2200 }
|
13
|
+
validates_each :billing_address do |record, attribute, value|
|
14
|
+
record.errors.add(attribute, "is not valid. #{value.errors.full_messages.join("\n")}") unless value.valid?
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(credit_card = {})
|
18
|
+
credit_card = ensure_credit_card(credit_card)
|
19
|
+
assign_attributes(extract_values(credit_card))
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
23
|
+
def id
|
24
|
+
token
|
25
|
+
end
|
26
|
+
|
27
|
+
def expiration_date=(date)
|
28
|
+
expiration_month, expiration_year = date.split('/')
|
29
|
+
self.expiration_month = expiration_month
|
30
|
+
self.expiration_year = expiration_year.gsub(/^(\d\d)$/, '20\1')
|
31
|
+
end
|
32
|
+
|
33
|
+
def expiration_date
|
34
|
+
expiration_month.present? ? "#{expiration_month}/#{expiration_year}" : nil
|
35
|
+
end
|
36
|
+
|
37
|
+
def billing_address=(val)
|
38
|
+
@billing_address = Address.new(val)
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
def ensure_credit_card(credit_card)
|
43
|
+
case credit_card
|
44
|
+
when String
|
45
|
+
@persisted = true
|
46
|
+
Braintree::CreditCard.find(credit_card)
|
47
|
+
when Braintree::CreditCard
|
48
|
+
@persisted = true
|
49
|
+
credit_card
|
50
|
+
when Hash
|
51
|
+
@persisted = false
|
52
|
+
OpenStruct.new(credit_card.reverse_merge(:billing_address => {}))
|
53
|
+
else
|
54
|
+
@persisted = credit_card.respond_to?(:persisted?) ? credit_card.persisted? : false
|
55
|
+
credit_card
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def attributes_to_exclude_from_update
|
60
|
+
[:token, :customer_id, :expiration_date]
|
61
|
+
end
|
62
|
+
|
63
|
+
def attributes_to_exclude_from_create
|
64
|
+
[:expiration_date]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module BraintreeRails
|
2
|
+
class CreditCards < SimpleDelegator
|
3
|
+
def initialize(customer, credit_cards)
|
4
|
+
@customer = customer
|
5
|
+
super(Array(credit_cards).map{|card| CreditCard.new(card)})
|
6
|
+
end
|
7
|
+
|
8
|
+
def find(token = nil, &block)
|
9
|
+
token.nil? ? super(&block) : super() { |c| c.token == token }
|
10
|
+
end
|
11
|
+
|
12
|
+
def build(params)
|
13
|
+
CreditCard.new(params.merge(:customer_id => @customer.id))
|
14
|
+
end
|
15
|
+
|
16
|
+
def create(params)
|
17
|
+
build(params).tap { |credit_card| credit_card.save }
|
18
|
+
end
|
19
|
+
|
20
|
+
def create!(params)
|
21
|
+
build(params).tap { |credit_card| credit_card.save! }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module BraintreeRails
|
2
|
+
class Customer < SimpleDelegator
|
3
|
+
Attributes = [:id, :first_name, :last_name, :email, :company, :website, :phone, :fax].freeze
|
4
|
+
include Model
|
5
|
+
|
6
|
+
validates :id, :format => {:with => /^[-_a-z0-9]*$/i}, :length => {:maximum => 36}, :exclusion => {:in => %w(all new)}
|
7
|
+
validates :first_name, :last_name, :company, :website, :phone, :fax, :length => {:maximum => 255}
|
8
|
+
|
9
|
+
attr_reader :addresses, :credit_cards
|
10
|
+
|
11
|
+
def initialize(customer = {})
|
12
|
+
customer = ensure_customer(customer)
|
13
|
+
assign_attributes(extract_values(customer))
|
14
|
+
@addresses = Addresses.new(self, customer.addresses)
|
15
|
+
@credit_cards = CreditCards.new(self, customer.credit_cards)
|
16
|
+
super
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
def ensure_customer(customer)
|
21
|
+
case customer
|
22
|
+
when String
|
23
|
+
@persisted = true
|
24
|
+
Braintree::Customer.find(customer)
|
25
|
+
when Braintree::Customer
|
26
|
+
@persisted = true
|
27
|
+
customer
|
28
|
+
when Hash
|
29
|
+
@persisted = false
|
30
|
+
OpenStruct.new(customer.reverse_merge(:addresses => [], :credit_cards => []))
|
31
|
+
else
|
32
|
+
@persisted = customer.respond_to?(:persisted?) ? customer.persisted? : false
|
33
|
+
customer
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,192 @@
|
|
1
|
+
module BraintreeRails
|
2
|
+
module Model
|
3
|
+
module ClassMethods
|
4
|
+
def self.extended(receiver)
|
5
|
+
receiver.class_eval do
|
6
|
+
extend ::ActiveModel::Naming
|
7
|
+
include ::ActiveModel::Validations
|
8
|
+
include ::ActiveModel::Serialization
|
9
|
+
attr_accessor(*self::Attributes)
|
10
|
+
|
11
|
+
class << receiver
|
12
|
+
alias :build :new
|
13
|
+
|
14
|
+
def create(params)
|
15
|
+
new(params).tap { |new_record| new_record.save }
|
16
|
+
end
|
17
|
+
|
18
|
+
def create!(params)
|
19
|
+
new(params).tap { |new_record| new_record.save! }
|
20
|
+
end
|
21
|
+
|
22
|
+
def braintree_model_class
|
23
|
+
"braintree/#{braintree_model_name}".camelize.constantize
|
24
|
+
end
|
25
|
+
|
26
|
+
def braintree_model_name
|
27
|
+
name.demodulize.underscore
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
module InstanceMethods
|
35
|
+
def persisted?
|
36
|
+
@persisted
|
37
|
+
end
|
38
|
+
|
39
|
+
def new_record?
|
40
|
+
!persisted?
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_key
|
44
|
+
persisted? ? [id] : nil
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_param
|
48
|
+
to_key.join("-")
|
49
|
+
end
|
50
|
+
|
51
|
+
def attributes
|
52
|
+
self.class::Attributes.inject({}) do |hash, attribute|
|
53
|
+
value = self.send(attribute)
|
54
|
+
hash[attribute] = value if value.present?
|
55
|
+
hash
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def save
|
60
|
+
create_or_update
|
61
|
+
rescue RecordInvalid
|
62
|
+
false
|
63
|
+
end
|
64
|
+
|
65
|
+
def save!
|
66
|
+
create_or_update || raise(RecordNotSaved)
|
67
|
+
end
|
68
|
+
|
69
|
+
def update_attributes(attributes)
|
70
|
+
assign_attributes(attributes)
|
71
|
+
save
|
72
|
+
end
|
73
|
+
|
74
|
+
def update_attributes!(attributes)
|
75
|
+
assign_attributes(attributes)
|
76
|
+
save!
|
77
|
+
end
|
78
|
+
|
79
|
+
def destroy
|
80
|
+
destroy!
|
81
|
+
rescue Braintree::NotFoundError
|
82
|
+
@persisted = false
|
83
|
+
freeze
|
84
|
+
end
|
85
|
+
alias :delete :destroy
|
86
|
+
|
87
|
+
def destroy!
|
88
|
+
if persisted?
|
89
|
+
self.class.braintree_model_class.delete(id)
|
90
|
+
end
|
91
|
+
@persisted = false
|
92
|
+
freeze
|
93
|
+
end
|
94
|
+
alias :delete! :destroy!
|
95
|
+
|
96
|
+
def attributes_for_update
|
97
|
+
attributes.except(*attributes_to_exclude_from_update).tap do |hash|
|
98
|
+
hash.each_pair do |key, value|
|
99
|
+
hash[key] = value.attributes_for_update if value.respond_to?(:attributes_for_update)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def attributes_for_create
|
105
|
+
attributes.except(*attributes_to_exclude_from_create).tap do |hash|
|
106
|
+
hash.each_pair do |key, value|
|
107
|
+
hash[key] = value.attributes_for_create if value.respond_to?(:attributes_for_create)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
def create_or_update
|
114
|
+
!!(new_record? ? create : update)
|
115
|
+
end
|
116
|
+
|
117
|
+
def create
|
118
|
+
with_update_braintree do
|
119
|
+
self.class.braintree_model_class.create(attributes_for_create)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def create!
|
124
|
+
with_update_braintree do
|
125
|
+
self.class.braintree_model_class.create!(attributes_for_create)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def update
|
130
|
+
with_update_braintree do
|
131
|
+
self.class.braintree_model_class.update(id, attributes_for_update)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def update!
|
136
|
+
with_update_braintree do
|
137
|
+
self.class.braintree_model_class.update!(id, attributes_for_update)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def attributes_to_exclude_from_update
|
142
|
+
[:id]
|
143
|
+
end
|
144
|
+
|
145
|
+
def attributes_to_exclude_from_create
|
146
|
+
[]
|
147
|
+
end
|
148
|
+
|
149
|
+
def extract_values(obj)
|
150
|
+
self.class::Attributes.inject({}) do |hash, attr|
|
151
|
+
hash[attr] = obj.send(attr) if obj.respond_to?(attr)
|
152
|
+
hash
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def assign_attributes(hash)
|
157
|
+
hash.each do |attribute, value|
|
158
|
+
send("#{attribute}=", value) if respond_to?("#{attribute}=")
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def add_errors(validation_errors)
|
163
|
+
validation_errors.each do |error|
|
164
|
+
if respond_to?(error.attribute)
|
165
|
+
self.errors.add error.attribute, error.message
|
166
|
+
else
|
167
|
+
self.error.add :base, error.message
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def with_update_braintree
|
173
|
+
raise RecordInvalid unless valid?
|
174
|
+
result = yield
|
175
|
+
if result.respond_to?(:success?) && !result.success?
|
176
|
+
add_errors(result.errors)
|
177
|
+
false
|
178
|
+
else
|
179
|
+
new_record = result.respond_to?(self.class.braintree_model_name) ? result.send(self.class.braintree_model_name) : result
|
180
|
+
assign_attributes(extract_values(new_record))
|
181
|
+
@persisted = true
|
182
|
+
self.__setobj__(new_record)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def self.included(receiver)
|
188
|
+
receiver.extend ClassMethods
|
189
|
+
receiver.send :include, InstanceMethods
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|