braintree 1.0.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/braintree.rb +36 -46
- data/lib/braintree/address.rb +14 -14
- data/lib/braintree/base_module.rb +3 -3
- data/lib/braintree/configuration.rb +16 -16
- data/lib/braintree/credit_card.rb +39 -30
- data/lib/braintree/credit_card_verification.rb +4 -3
- data/lib/braintree/customer.rb +22 -22
- data/lib/braintree/digest.rb +2 -8
- data/lib/braintree/error_codes.rb +16 -3
- data/lib/braintree/error_result.rb +5 -5
- data/lib/braintree/exceptions.rb +58 -0
- data/lib/braintree/http.rb +7 -7
- data/lib/braintree/paged_collection.rb +14 -14
- data/lib/braintree/ssl_expiration_check.rb +5 -1
- data/lib/braintree/subscription.rb +110 -0
- data/lib/braintree/successful_result.rb +3 -3
- data/lib/braintree/test/credit_card_numbers.rb +1 -1
- data/lib/braintree/test/transaction_amounts.rb +18 -0
- data/lib/braintree/transaction.rb +52 -25
- data/lib/braintree/transaction/address_details.rb +2 -2
- data/lib/braintree/transaction/credit_card_details.rb +12 -4
- data/lib/braintree/transaction/customer_details.rb +9 -1
- data/lib/braintree/transaction/status_details.rb +1 -1
- data/lib/braintree/transparent_redirect.rb +15 -15
- data/lib/braintree/util.rb +7 -7
- data/lib/braintree/validation_error.rb +1 -1
- data/lib/braintree/validation_error_collection.rb +6 -6
- data/lib/braintree/version.rb +3 -3
- data/lib/braintree/xml/generator.rb +2 -2
- data/lib/braintree/xml/libxml.rb +1 -1
- data/lib/braintree/xml/parser.rb +1 -1
- data/spec/integration/braintree/address_spec.rb +12 -12
- data/spec/integration/braintree/credit_card_spec.rb +189 -37
- data/spec/integration/braintree/customer_spec.rb +35 -35
- data/spec/integration/braintree/http_spec.rb +3 -3
- data/spec/integration/braintree/subscription_spec.rb +362 -0
- data/spec/integration/braintree/transaction_spec.rb +130 -58
- data/spec/integration/spec_helper.rb +1 -1
- data/spec/spec_helper.rb +4 -4
- data/spec/unit/braintree/address_spec.rb +6 -6
- data/spec/unit/braintree/base_module_spec.rb +18 -0
- data/spec/unit/braintree/configuration_spec.rb +10 -10
- data/spec/unit/braintree/credit_card_spec.rb +15 -15
- data/spec/unit/braintree/credit_card_verification_spec.rb +4 -2
- data/spec/unit/braintree/customer_spec.rb +8 -8
- data/spec/unit/braintree/digest_spec.rb +4 -0
- data/spec/unit/braintree/http_spec.rb +2 -2
- data/spec/unit/braintree/paged_collection_spec.rb +12 -12
- data/spec/unit/braintree/ssl_expiration_check_spec.rb +18 -9
- data/spec/unit/braintree/transaction/credit_card_details_spec.rb +15 -0
- data/spec/unit/braintree/transaction/customer_details_spec.rb +19 -0
- data/spec/unit/braintree/transaction_spec.rb +14 -14
- data/spec/unit/braintree/transparent_redirect_spec.rb +11 -11
- data/spec/unit/braintree/util_spec.rb +8 -8
- data/spec/unit/braintree/validation_error_collection_spec.rb +6 -6
- data/spec/unit/braintree/validation_error_spec.rb +2 -2
- data/spec/unit/braintree/xml/libxml_spec.rb +5 -5
- data/spec/unit/braintree/xml_spec.rb +16 -16
- data/spec/unit/braintree_spec.rb +11 -0
- metadata +8 -2
@@ -0,0 +1,110 @@
|
|
1
|
+
module Braintree
|
2
|
+
class Subscription
|
3
|
+
include BaseModule
|
4
|
+
|
5
|
+
module Status
|
6
|
+
Active = 'Active'
|
7
|
+
Canceled = 'Canceled'
|
8
|
+
PastDue = 'Past Due'
|
9
|
+
end
|
10
|
+
|
11
|
+
module TrialDurationUnit
|
12
|
+
Day = "day"
|
13
|
+
Month = "month"
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :price, :plan_id, :id, :status, :payment_method_token
|
17
|
+
attr_reader :first_billing_date, :next_billing_date, :billing_period_start_date, :billing_period_end_date
|
18
|
+
attr_reader :trial_period, :trial_duration, :trial_duration_unit
|
19
|
+
attr_reader :failure_count
|
20
|
+
attr_reader :transactions
|
21
|
+
|
22
|
+
def self.cancel(subscription_id)
|
23
|
+
response = Http.put "/subscriptions/#{subscription_id}/cancel"
|
24
|
+
if response[:subscription]
|
25
|
+
SuccessfulResult.new(:subscription => new(response[:subscription]))
|
26
|
+
elsif response[:api_error_response]
|
27
|
+
ErrorResult.new(response[:api_error_response])
|
28
|
+
else
|
29
|
+
raise UnexpectedError, "expected :subscription or :api_error_response"
|
30
|
+
end
|
31
|
+
rescue NotFoundError
|
32
|
+
raise NotFoundError, "subscription with id #{subscription_id.inspect} not found"
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.create(attributes)
|
36
|
+
Util.verify_keys(_create_signature, attributes)
|
37
|
+
_do_create "/subscriptions", :subscription => attributes
|
38
|
+
end
|
39
|
+
|
40
|
+
# Finds the subscription with the given id. Raises a Braintree::NotFoundError
|
41
|
+
# if the subscription cannot be found.
|
42
|
+
def self.find(id)
|
43
|
+
response = Http.get "/subscriptions/#{id}"
|
44
|
+
new(response[:subscription])
|
45
|
+
rescue NotFoundError
|
46
|
+
raise NotFoundError, "subscription with id #{id.inspect} not found"
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.update(subscription_id, attributes)
|
50
|
+
Util.verify_keys(_update_signature, attributes)
|
51
|
+
response = Http.put "/subscriptions/#{subscription_id}", :subscription => attributes
|
52
|
+
if response[:subscription]
|
53
|
+
SuccessfulResult.new(:subscription => new(response[:subscription]))
|
54
|
+
elsif response[:api_error_response]
|
55
|
+
ErrorResult.new(response[:api_error_response])
|
56
|
+
else
|
57
|
+
raise UnexpectedError, "expected :subscription or :api_error_response"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def self._create_signature # :nodoc:
|
62
|
+
[
|
63
|
+
:id,
|
64
|
+
:payment_method_token,
|
65
|
+
:plan_id,
|
66
|
+
:price,
|
67
|
+
:trial_duration,
|
68
|
+
:trial_duration_unit,
|
69
|
+
:trial_period
|
70
|
+
]
|
71
|
+
end
|
72
|
+
|
73
|
+
def initialize(attributes) # :nodoc:
|
74
|
+
_init attributes
|
75
|
+
transactions.map! {|attrs| Transaction._new(attrs) }
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
# True if <tt>other</tt> has the same id.
|
80
|
+
def ==(other)
|
81
|
+
id == other.id
|
82
|
+
end
|
83
|
+
|
84
|
+
def self._do_create(url, params) # :nodoc:
|
85
|
+
response = Http.post url, params
|
86
|
+
if response[:subscription]
|
87
|
+
SuccessfulResult.new(:subscription => new(response[:subscription]))
|
88
|
+
elsif response[:api_error_response]
|
89
|
+
ErrorResult.new(response[:api_error_response])
|
90
|
+
else
|
91
|
+
raise UnexpectedError, "expected :subscription or :api_error_response"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def _init(attributes) # :nodoc:
|
96
|
+
set_instance_variables_from_hash(attributes)
|
97
|
+
if self.price
|
98
|
+
instance_variable_set :@price, BigDecimal.new(self.price)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def self._update_signature # :nodoc:
|
103
|
+
[
|
104
|
+
:id,
|
105
|
+
:plan_id,
|
106
|
+
:price
|
107
|
+
]
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
module Braintree
|
1
|
+
module Braintree
|
2
2
|
# A SuccessfulResult will be returned from non-bang methods when
|
3
3
|
# validations pass. It will provide access to the created resource.
|
4
4
|
# For example, when creating a customer, SuccessfulResult will
|
@@ -13,7 +13,7 @@ module Braintree
|
|
13
13
|
# end
|
14
14
|
class SuccessfulResult
|
15
15
|
include BaseModule
|
16
|
-
|
16
|
+
|
17
17
|
def initialize(attributes = {}) # :nodoc:
|
18
18
|
@attrs = attributes.keys
|
19
19
|
singleton_class.class_eval do
|
@@ -22,7 +22,7 @@ module Braintree
|
|
22
22
|
value
|
23
23
|
end
|
24
24
|
end
|
25
|
-
end
|
25
|
+
end
|
26
26
|
end
|
27
27
|
|
28
28
|
def inspect # :nodoc:
|
@@ -6,5 +6,23 @@ module Braintree
|
|
6
6
|
Authorize = "1000.00"
|
7
7
|
Decline = "2000.00"
|
8
8
|
end
|
9
|
+
|
10
|
+
module Plans
|
11
|
+
TrialPlan = {
|
12
|
+
:description => "Plan for integration tests -- with trial",
|
13
|
+
:id => "integration_trial_plan",
|
14
|
+
:price => "43.21",
|
15
|
+
:trial_period => true,
|
16
|
+
:trial_duration => 2,
|
17
|
+
:trial_duration_unit => Subscription::TrialDurationUnit::Day
|
18
|
+
}
|
19
|
+
|
20
|
+
TriallessPlan = {
|
21
|
+
:description => "Plan for integration tests -- without a trial",
|
22
|
+
:id => "integration_trialless_plan",
|
23
|
+
:price => "12.34",
|
24
|
+
:trial_period => false
|
25
|
+
}
|
26
|
+
end
|
9
27
|
end
|
10
28
|
end
|
@@ -9,7 +9,7 @@ module Braintree
|
|
9
9
|
# :number => "5105105105105100",
|
10
10
|
# :expiration_date => "05/2012"
|
11
11
|
# }
|
12
|
-
# )
|
12
|
+
# )
|
13
13
|
#
|
14
14
|
# Full example:
|
15
15
|
#
|
@@ -118,12 +118,12 @@ module Braintree
|
|
118
118
|
# )
|
119
119
|
class Transaction
|
120
120
|
include BaseModule
|
121
|
-
|
121
|
+
|
122
122
|
module Type # :nodoc:
|
123
123
|
Credit = "credit" # :nodoc:
|
124
124
|
Sale = "sale" # :nodoc:
|
125
125
|
end
|
126
|
-
|
126
|
+
|
127
127
|
attr_reader :avs_error_response_code, :avs_postal_code_response_code, :avs_street_address_response_code
|
128
128
|
attr_reader :amount, :created_at, :credit_card_details, :customer_details, :id, :status
|
129
129
|
attr_reader :custom_fields
|
@@ -132,6 +132,8 @@ module Braintree
|
|
132
132
|
attr_reader :status_history
|
133
133
|
# The response code from the processor.
|
134
134
|
attr_reader :processor_response_code
|
135
|
+
# The response text from the processor.
|
136
|
+
attr_reader :processor_response_text
|
135
137
|
# Will either be "sale" or "credit"
|
136
138
|
attr_reader :type
|
137
139
|
attr_reader :updated_at
|
@@ -140,7 +142,7 @@ module Braintree
|
|
140
142
|
Util.verify_keys(_create_signature, attributes)
|
141
143
|
_do_create "/transactions", :transaction => attributes
|
142
144
|
end
|
143
|
-
|
145
|
+
|
144
146
|
def self.create!(attributes)
|
145
147
|
return_object_or_raise(:transaction) { create(attributes) }
|
146
148
|
end
|
@@ -154,12 +156,12 @@ module Braintree
|
|
154
156
|
def self.create_transaction_url
|
155
157
|
"#{Braintree::Configuration.base_merchant_url}/transactions/all/create_via_transparent_redirect_request"
|
156
158
|
end
|
157
|
-
|
158
|
-
# Creates a credit transaction.
|
159
|
+
|
160
|
+
# Creates a credit transaction.
|
159
161
|
def self.credit(attributes)
|
160
162
|
create(attributes.merge(:type => 'credit'))
|
161
163
|
end
|
162
|
-
|
164
|
+
|
163
165
|
def self.credit!(attributes)
|
164
166
|
return_object_or_raise(:transaction) { credit(attributes) }
|
165
167
|
end
|
@@ -177,11 +179,11 @@ module Braintree
|
|
177
179
|
def self.sale(attributes)
|
178
180
|
create(attributes.merge(:type => 'sale'))
|
179
181
|
end
|
180
|
-
|
182
|
+
|
181
183
|
def self.sale!(attributes)
|
182
184
|
return_object_or_raise(:transaction) { sale(attributes) }
|
183
185
|
end
|
184
|
-
|
186
|
+
|
185
187
|
# Returns a PagedCollection of transactions matching the search query.
|
186
188
|
# If <tt>query</tt> is a string, the search will be a basic search.
|
187
189
|
# If <tt>query</tt> is a hash, the search will be an advanced search.
|
@@ -231,21 +233,25 @@ module Braintree
|
|
231
233
|
def initialize(attributes) # :nodoc:
|
232
234
|
_init attributes
|
233
235
|
end
|
234
|
-
|
236
|
+
|
235
237
|
# True if <tt>other</tt> has the same id.
|
236
238
|
def ==(other)
|
237
239
|
id == other.id
|
238
240
|
end
|
239
|
-
|
241
|
+
|
240
242
|
def inspect # :nodoc:
|
241
243
|
first = [:id, :type, :amount, :status]
|
242
244
|
order = first + (self.class._attributes - first)
|
243
245
|
nice_attributes = order.map do |attr|
|
244
|
-
|
246
|
+
if attr == :amount
|
247
|
+
self.amount ? "amount: #{self.amount.to_s("F").inspect}" : "amount: nil"
|
248
|
+
else
|
249
|
+
"#{attr}: #{send(attr).inspect}"
|
250
|
+
end
|
245
251
|
end
|
246
252
|
"#<#{self.class} #{nice_attributes.join(', ')}>"
|
247
253
|
end
|
248
|
-
|
254
|
+
|
249
255
|
# Creates a credit transaction that refunds this transaction.
|
250
256
|
def refund
|
251
257
|
response = Http.post "/transactions/#{id}/refund"
|
@@ -258,7 +264,7 @@ module Braintree
|
|
258
264
|
raise UnexpectedError, "expected :transaction or :api_error_response"
|
259
265
|
end
|
260
266
|
end
|
261
|
-
|
267
|
+
|
262
268
|
# Returns true if the transaction has been refunded. False otherwise.
|
263
269
|
def refunded?
|
264
270
|
!@refund_id.nil?
|
@@ -280,7 +286,16 @@ module Braintree
|
|
280
286
|
def submit_for_settlement!(amount = nil)
|
281
287
|
return_object_or_raise(:transaction) { submit_for_settlement(amount) }
|
282
288
|
end
|
283
|
-
|
289
|
+
|
290
|
+
# If this transaction was stored in the vault, or created from vault records,
|
291
|
+
# vault_billing_address will return the associated Braintree::Address. Because the
|
292
|
+
# vault billing address can be updated after the transaction was created, the attributes
|
293
|
+
# on vault_billing_address may not match the attributes on billing_details.
|
294
|
+
def vault_billing_address
|
295
|
+
return nil if billing_details.id.nil?
|
296
|
+
Address.find(customer_details.id, billing_details.id)
|
297
|
+
end
|
298
|
+
|
284
299
|
# If this transaction was stored in the vault, or created from vault records,
|
285
300
|
# vault_credit_card will return the associated Braintree::CreditCard. Because the
|
286
301
|
# vault credit card can be updated after the transaction was created, the attributes
|
@@ -289,7 +304,7 @@ module Braintree
|
|
289
304
|
return nil if credit_card_details.token.nil?
|
290
305
|
CreditCard.find(credit_card_details.token)
|
291
306
|
end
|
292
|
-
|
307
|
+
|
293
308
|
# If this transaction was stored in the vault, or created from vault records,
|
294
309
|
# vault_customer will return the associated Braintree::Customer. Because the
|
295
310
|
# vault customer can be updated after the transaction was created, the attributes
|
@@ -298,7 +313,16 @@ module Braintree
|
|
298
313
|
return nil if customer_details.id.nil?
|
299
314
|
Customer.find(customer_details.id)
|
300
315
|
end
|
301
|
-
|
316
|
+
|
317
|
+
# If this transaction was stored in the vault, or created from vault records,
|
318
|
+
# vault_shipping_address will return the associated Braintree::Address. Because the
|
319
|
+
# vault shipping address can be updated after the transaction was created, the attributes
|
320
|
+
# on vault_shipping_address may not match the attributes on shipping_details.
|
321
|
+
def vault_shipping_address
|
322
|
+
return nil if shipping_details.id.nil?
|
323
|
+
Address.find(customer_details.id, shipping_details.id)
|
324
|
+
end
|
325
|
+
|
302
326
|
# Voids the transaction.
|
303
327
|
def void
|
304
328
|
response = Http.put "/transactions/#{id}/void"
|
@@ -320,9 +344,9 @@ module Braintree
|
|
320
344
|
protected :new
|
321
345
|
def _new(*args) # :nodoc:
|
322
346
|
self.new *args
|
323
|
-
end
|
347
|
+
end
|
324
348
|
end
|
325
|
-
|
349
|
+
|
326
350
|
def self._do_create(url, params) # :nodoc:
|
327
351
|
response = Http.post url, params
|
328
352
|
if response[:transaction]
|
@@ -333,19 +357,19 @@ module Braintree
|
|
333
357
|
raise UnexpectedError, "expected :transaction or :api_error_response"
|
334
358
|
end
|
335
359
|
end
|
336
|
-
|
360
|
+
|
337
361
|
def self._advanced_search(query, options) # :nodoc:
|
338
362
|
page = options[:page] || 1
|
339
363
|
response = Http.post "/transactions/advanced_search?page=#{Util.url_encode(page)}", :search => query
|
340
364
|
attributes = response[:credit_card_transactions]
|
341
365
|
attributes[:items] = Util.extract_attribute_as_array(attributes, :transaction).map { |attrs| _new(attrs) }
|
342
|
-
PagedCollection.new(attributes) { |page_number| Transaction.search(query, :page => page_number) }
|
366
|
+
PagedCollection.new(attributes) { |page_number| Transaction.search(query, :page => page_number) }
|
343
367
|
end
|
344
368
|
|
345
369
|
def self._attributes # :nodoc:
|
346
370
|
[:amount, :created_at, :credit_card_details, :customer_details, :id, :status, :type, :updated_at]
|
347
371
|
end
|
348
|
-
|
372
|
+
|
349
373
|
def self._basic_search(query, options) # :nodoc:
|
350
374
|
page = options[:page] || 1
|
351
375
|
response = Http.get "/transactions/all/search?q=#{Util.url_encode(query)}&page=#{Util.url_encode(page)}"
|
@@ -353,7 +377,7 @@ module Braintree
|
|
353
377
|
attributes[:items] = Util.extract_attribute_as_array(attributes, :transaction).map { |attrs| _new(attrs) }
|
354
378
|
PagedCollection.new(attributes) { |page_number| Transaction.search(query, :page => page_number) }
|
355
379
|
end
|
356
|
-
|
380
|
+
|
357
381
|
def self._create_signature # :nodoc:
|
358
382
|
[
|
359
383
|
:amount, :customer_id, :order_id, :payment_method_token, :type,
|
@@ -361,13 +385,16 @@ module Braintree
|
|
361
385
|
{:customer => [:id, :company, :email, :fax, :first_name, :last_name, :phone, :website]},
|
362
386
|
{:billing => [:first_name, :last_name, :company, :country_name, :extended_address, :locality, :postal_code, :region, :street_address]},
|
363
387
|
{:shipping => [:first_name, :last_name, :company, :country_name, :extended_address, :locality, :postal_code, :region, :street_address]},
|
364
|
-
{:options => [:store_in_vault, :submit_for_settlement, :add_billing_address_to_payment_method]},
|
388
|
+
{:options => [:store_in_vault, :submit_for_settlement, :add_billing_address_to_payment_method, :store_shipping_address_in_vault]},
|
365
389
|
{:custom_fields => :_any_key_}
|
366
390
|
]
|
367
391
|
end
|
368
|
-
|
392
|
+
|
369
393
|
def _init(attributes) # :nodoc:
|
370
394
|
set_instance_variables_from_hash(attributes)
|
395
|
+
if self.amount
|
396
|
+
instance_variable_set :@amount, BigDecimal.new(self.amount)
|
397
|
+
end
|
371
398
|
@credit_card_details = CreditCardDetails.new(@credit_card)
|
372
399
|
@customer_details = CustomerDetails.new(@customer)
|
373
400
|
@billing_details = AddressDetails.new(@billing)
|
@@ -2,8 +2,8 @@ module Braintree
|
|
2
2
|
class Transaction
|
3
3
|
class AddressDetails # :nodoc:
|
4
4
|
include BaseModule
|
5
|
-
|
6
|
-
attr_reader :first_name, :last_name, :company,
|
5
|
+
|
6
|
+
attr_reader :id, :first_name, :last_name, :company,
|
7
7
|
:street_address, :extended_address, :locality, :region,
|
8
8
|
:postal_code, :country_name
|
9
9
|
|
@@ -2,14 +2,22 @@ module Braintree
|
|
2
2
|
class Transaction
|
3
3
|
class CreditCardDetails # :nodoc:
|
4
4
|
include BaseModule
|
5
|
-
|
6
|
-
attr_reader :bin, :card_type, :expiration_month,
|
7
|
-
:expiration_year, :
|
5
|
+
|
6
|
+
attr_reader :bin, :card_type, :customer_location, :expiration_month,
|
7
|
+
:expiration_year, :last_4, :token
|
8
8
|
|
9
9
|
def initialize(attributes)
|
10
10
|
set_instance_variables_from_hash attributes unless attributes.nil?
|
11
11
|
end
|
12
|
-
|
12
|
+
|
13
|
+
def inspect
|
14
|
+
attr_order = [:token, :bin, :last_4, :card_type, :expiration_date, :customer_location]
|
15
|
+
formatted_attrs = attr_order.map do |attr|
|
16
|
+
"#{attr}: #{send(attr).inspect}"
|
17
|
+
end
|
18
|
+
"#<#{formatted_attrs.join(", ")}>"
|
19
|
+
end
|
20
|
+
|
13
21
|
def expiration_date
|
14
22
|
"#{expiration_month}/#{expiration_year}"
|
15
23
|
end
|
@@ -2,12 +2,20 @@ module Braintree
|
|
2
2
|
class Transaction
|
3
3
|
class CustomerDetails # :nodoc:
|
4
4
|
include BaseModule
|
5
|
-
|
5
|
+
|
6
6
|
attr_reader :company, :email, :fax, :first_name, :id, :last_name, :phone, :website
|
7
7
|
|
8
8
|
def initialize(attributes)
|
9
9
|
set_instance_variables_from_hash attributes unless attributes.nil?
|
10
10
|
end
|
11
|
+
|
12
|
+
def inspect
|
13
|
+
attr_order = [:id, :first_name, :last_name, :email, :company, :website, :phone, :fax]
|
14
|
+
formatted_attrs = attr_order.map do |attr|
|
15
|
+
"#{attr}: #{send(attr).inspect}"
|
16
|
+
end
|
17
|
+
"#<#{formatted_attrs.join(", ")}>"
|
18
|
+
end
|
11
19
|
end
|
12
20
|
end
|
13
21
|
end
|