braintree 1.0.1 → 1.1.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/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
|