braintree 2.0.0 → 2.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.
@@ -13,9 +13,9 @@ The Braintree gem provides integration access to the Braintree Gateway.
13
13
  require "braintree"
14
14
 
15
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"
16
+ Braintree::Configuration.merchant_id = "your_merchant_id"
17
+ Braintree::Configuration.public_key = "your_public_key"
18
+ Braintree::Configuration.private_key = "your_private_key"
19
19
 
20
20
  result = Braintree::Transaction.sale(
21
21
  :amount => "1000.00",
@@ -41,6 +41,7 @@ require "braintree/successful_result.rb"
41
41
  require "braintree/test/credit_card_numbers.rb"
42
42
  require "braintree/test/transaction_amounts.rb"
43
43
  require "braintree/transaction.rb"
44
+ require "braintree/transaction_search.rb"
44
45
  require "braintree/transaction/address_details.rb"
45
46
  require "braintree/transaction/credit_card_details.rb"
46
47
  require "braintree/transaction/customer_details.rb"
@@ -14,8 +14,22 @@ module Braintree
14
14
  end
15
15
  end
16
16
 
17
- class TextNode < SearchNode
18
- operators :is, :is_not, :ends_with, :starts_with, :contains
17
+ class EqualityNode < SearchNode
18
+ operators :is, :is_not
19
+ end
20
+
21
+ class PartialMatchNode < EqualityNode
22
+ operators :ends_with, :starts_with
23
+ end
24
+
25
+ class TextNode < PartialMatchNode
26
+ operators :contains
27
+ end
28
+
29
+ class KeyValueNode < SearchNode
30
+ def is(value)
31
+ @parent.add_criteria(@node_name, value)
32
+ end
19
33
  end
20
34
 
21
35
  class MultipleValueNode < SearchNode
@@ -30,6 +44,10 @@ module Braintree
30
44
  @parent.add_criteria(@node_name, values)
31
45
  end
32
46
 
47
+ def is(value)
48
+ self.in(value)
49
+ end
50
+
33
51
  def initialize(name, parent, options)
34
52
  super(name, parent)
35
53
  @options = options
@@ -40,26 +58,70 @@ module Braintree
40
58
  end
41
59
  end
42
60
 
43
- def self.search_fields(*fields)
44
- fields.each do |field|
45
- define_method(field) do
46
- TextNode.new(field, self)
47
- end
61
+ class RangeNode < SearchNode
62
+ def between(min, max)
63
+ self >= min
64
+ self <= max
65
+ end
66
+
67
+ def >=(min)
68
+ @parent.add_criteria(@node_name, :min => min)
69
+ end
70
+
71
+ def <=(max)
72
+ @parent.add_criteria(@node_name, :max => max)
48
73
  end
49
74
  end
50
75
 
76
+ def self.search_fields(*fields)
77
+ _create_field_accessors(fields, TextNode)
78
+ end
79
+
80
+ def self.equality_fields(*fields)
81
+ _create_field_accessors(fields, EqualityNode)
82
+ end
83
+
84
+ def self.partial_match_fields(*fields)
85
+ _create_field_accessors(fields, PartialMatchNode)
86
+ end
87
+
51
88
  def self.multiple_value_field(field, options={})
52
89
  define_method(field) do
53
90
  MultipleValueNode.new(field, self, options)
54
91
  end
55
92
  end
56
93
 
94
+ def self.key_value_fields(*fields)
95
+ _create_field_accessors(fields, KeyValueNode)
96
+ end
97
+
98
+ def self.range_fields(*fields)
99
+ _create_field_accessors(fields, RangeNode)
100
+ end
101
+
102
+ def self.date_range_fields(*fields)
103
+ _create_field_accessors(fields, DateRangeNode)
104
+ end
105
+
106
+ def self._create_field_accessors(fields, node_class)
107
+ fields.each do |field|
108
+ define_method(field) do |*args|
109
+ raise "An operator is required" unless args.empty?
110
+ node_class.new(field, self)
111
+ end
112
+ end
113
+ end
114
+
57
115
  def initialize
58
116
  @criteria = {}
59
117
  end
60
118
 
61
119
  def add_criteria(key, value)
62
- @criteria[key] = value
120
+ if @criteria[key].is_a?(Hash)
121
+ @criteria[key].merge!(value)
122
+ else
123
+ @criteria[key] = value
124
+ end
63
125
  end
64
126
 
65
127
  def to_hash
@@ -6,6 +6,29 @@ module Braintree
6
6
  class CreditCard
7
7
  include BaseModule # :nodoc:
8
8
 
9
+ module CardType
10
+ AmEx = "American Express"
11
+ CarteBlanche = "Carte Blanche"
12
+ ChinaUnionPay = "China UnionPay"
13
+ DinersClubInternational = "Diners Club"
14
+ Discover = "Discover"
15
+ JCB = "JCB"
16
+ Laser = "Laser"
17
+ Maestro = "Maestro"
18
+ MasterCard = "MasterCard"
19
+ Solo = "Solo"
20
+ Switch = "Switch"
21
+ Visa = "Visa"
22
+ Unknown = "Unknown"
23
+
24
+ All = constants.map { |c| const_get(c) }
25
+ end
26
+
27
+ module CustomerLocation
28
+ International = "international"
29
+ US = "us"
30
+ end
31
+
9
32
  attr_reader :billing_address, :bin, :card_type, :cardholder_name, :created_at, :customer_id, :expiration_month,
10
33
  :expiration_year, :last_4, :subscriptions, :token, :updated_at
11
34
 
@@ -103,7 +126,7 @@ module Braintree
103
126
 
104
127
  def initialize(attributes) # :nodoc:
105
128
  _init attributes
106
- @subscriptions = (@subscriptions || []).map { |subscription_hash| Subscription.new(subscription_hash) }
129
+ @subscriptions = (@subscriptions || []).map { |subscription_hash| Subscription._new(subscription_hash) }
107
130
  end
108
131
 
109
132
  # Creates a credit transaction for this credit card.
@@ -196,7 +219,7 @@ module Braintree
196
219
  end
197
220
 
198
221
  def self._create_signature # :nodoc:
199
- _update_signature + [:customer_id]
222
+ _signature(:create)
200
223
  end
201
224
 
202
225
  def self._new(*args) # :nodoc:
@@ -226,11 +249,27 @@ module Braintree
226
249
  end
227
250
 
228
251
  def self._update_signature # :nodoc:
229
- [
252
+ _signature(:update)
253
+ end
254
+
255
+ def self._signature(type) # :nodoc:
256
+ billing_address_params = [:company, :country_name, :extended_address, :first_name, :last_name, :locality, :postal_code, :region, :street_address]
257
+ signature = [
230
258
  :cardholder_name, :cvv, :expiration_date, :expiration_month, :expiration_year, :number, :token,
231
259
  {:options => [:make_default, :verify_card]},
232
- {:billing_address => [:company, :country_name, :extended_address, :first_name, :last_name, :locality, :postal_code, :region, :street_address]}
260
+ {:billing_address => billing_address_params}
233
261
  ]
262
+
263
+ case type
264
+ when :create
265
+ signature << :customer_id
266
+ when :update
267
+ billing_address_params << {:options => [:update_existing]}
268
+ else
269
+ raise ArgumentError
270
+ end
271
+
272
+ return signature
234
273
  end
235
274
 
236
275
  def _init(attributes) # :nodoc:
@@ -11,6 +11,7 @@ module Braintree
11
11
  FirstNameIsTooLong = "81805"
12
12
  LastNameIsTooLong = "81806"
13
13
  LocalityIsTooLong = "81807"
14
+ PostalCodeInvalidCharacters = "81813"
14
15
  PostalCodeIsRequired = "81808"
15
16
  PostalCodeIsTooLong = "81809"
16
17
  RegionIsTooLong = "81810"
@@ -87,6 +88,7 @@ module Braintree
87
88
  CreditCardIsRequired = "91508"
88
89
  CustomerDefaultPaymentMethodCardTypeIsNotAccepted = "81509"
89
90
  CustomFieldIsInvalid = "91526"
91
+ CustomFieldIsTooLong = "81527"
90
92
  CustomerIdIsInvalid = "91510"
91
93
  CustomerDoesNotHaveCreditCard = "91511"
92
94
  HasAlreadyBeenRefunded = "91512"
@@ -95,10 +97,15 @@ module Braintree
95
97
  OrderIdIsTooLong = "91501"
96
98
  PaymentMethodConflict = "91515"
97
99
  PaymentMethodDoesNotBelongToCustomer = "91516"
100
+ PaymentMethodDoesNotBelongToSubscription = "91527"
98
101
  PaymentMethodTokenCardTypeIsNotAccepted = "91517"
99
102
  PaymentMethodTokenIsInvalid = "91518"
103
+ ProcessorAuthorizationCodeCannotBeSet = "91519"
104
+ ProcessorAuthorizationCodeIsInvalid = "81520"
100
105
  RefundAmountIsTooLarge = "91521"
101
106
  SettlementAmountIsTooLarge = "91522"
107
+ SubscriptionDoesNotBelongToCustomer = "91529"
108
+ SubscriptionIdIsInvalid = "91528"
102
109
  TypeIsInvalid = "91523"
103
110
  TypeIsRequired = "91524"
104
111
  module Options
@@ -47,7 +47,7 @@ module Braintree
47
47
  def self.cancel(subscription_id)
48
48
  response = Http.put "/subscriptions/#{subscription_id}/cancel"
49
49
  if response[:subscription]
50
- SuccessfulResult.new(:subscription => new(response[:subscription]))
50
+ SuccessfulResult.new(:subscription => _new(response[:subscription]))
51
51
  elsif response[:api_error_response]
52
52
  ErrorResult.new(response[:api_error_response])
53
53
  else
@@ -66,11 +66,21 @@ module Braintree
66
66
  # if the subscription cannot be found.
67
67
  def self.find(id)
68
68
  response = Http.get "/subscriptions/#{id}"
69
- new(response[:subscription])
69
+ _new(response[:subscription])
70
70
  rescue NotFoundError
71
71
  raise NotFoundError, "subscription with id #{id.inspect} not found"
72
72
  end
73
73
 
74
+ def self.retry_charge(subscription_id, amount=nil)
75
+ attributes = {
76
+ :amount => amount,
77
+ :subscription_id => subscription_id,
78
+ :type => Transaction::Type::Sale
79
+ }
80
+
81
+ Transaction.send(:_do_create, "/transactions", :transaction => attributes)
82
+ end
83
+
74
84
  # Allows searching on subscriptions. There are two types of fields that are searchable: text and
75
85
  # multiple value fields. Searchable text fields are:
76
86
  # - plan_id
@@ -93,7 +103,7 @@ module Braintree
93
103
 
94
104
  response = Http.post "/subscriptions/advanced_search?page=#{page}", {:search => search.to_hash}
95
105
  attributes = response[:subscriptions]
96
- attributes[:items] = Util.extract_attribute_as_array(attributes, :subscription).map { |attrs| new(attrs) }
106
+ attributes[:items] = Util.extract_attribute_as_array(attributes, :subscription).map { |attrs| _new(attrs) }
97
107
  ResourceCollection.new(attributes) { |page_number| Subscription.search(page_number, &block) }
98
108
  end
99
109
 
@@ -101,7 +111,7 @@ module Braintree
101
111
  Util.verify_keys(_update_signature, attributes)
102
112
  response = Http.put "/subscriptions/#{subscription_id}", :subscription => attributes
103
113
  if response[:subscription]
104
- SuccessfulResult.new(:subscription => new(response[:subscription]))
114
+ SuccessfulResult.new(:subscription => _new(response[:subscription]))
105
115
  elsif response[:api_error_response]
106
116
  ErrorResult.new(response[:api_error_response])
107
117
  else
@@ -130,9 +140,17 @@ module Braintree
130
140
 
131
141
  # True if <tt>other</tt> has the same id.
132
142
  def ==(other)
143
+ return false unless other.is_a?(Subscription)
133
144
  id == other.id
134
145
  end
135
146
 
147
+ class << self
148
+ protected :new
149
+ def _new(*args) # :nodoc:
150
+ self.new *args
151
+ end
152
+ end
153
+
136
154
  def self._do_create(url, params) # :nodoc:
137
155
  response = Http.post url, params
138
156
  if response[:subscription]
@@ -123,6 +123,11 @@ module Braintree
123
123
  class Transaction
124
124
  include BaseModule
125
125
 
126
+ module CreatedUsing
127
+ FullInformation = 'full_information'
128
+ Token = 'token'
129
+ end
130
+
126
131
  module Status
127
132
  Authorizing = 'authorizing'
128
133
  Authorized = 'authorized'
@@ -134,6 +139,14 @@ module Braintree
134
139
  SubmittedForSettlement = 'submitted_for_settlement'
135
140
  Unknown = 'unknown'
136
141
  Voided = 'voided'
142
+
143
+ All = constants.map { |c| const_get(c) }
144
+ end
145
+
146
+ module Source
147
+ Api = "api"
148
+ ControlPanel = "control_panel"
149
+ Recurring = "recurring"
137
150
  end
138
151
 
139
152
  module Type # :nodoc:
@@ -145,6 +158,7 @@ module Braintree
145
158
  attr_reader :amount, :created_at, :credit_card_details, :customer_details, :id
146
159
  attr_reader :custom_fields
147
160
  attr_reader :cvv_response_code
161
+ attr_reader :merchant_account_id
148
162
  attr_reader :order_id
149
163
  attr_reader :billing_details, :shipping_details
150
164
  # The authorization code from the processor.
@@ -209,13 +223,13 @@ module Braintree
209
223
  # Returns a ResourceCollection of transactions matching the search query.
210
224
  # If <tt>query</tt> is a string, the search will be a basic search.
211
225
  # If <tt>query</tt> is a hash, the search will be an advanced search.
212
- def self.search(query, options = {})
213
- if query.is_a?(String)
214
- _basic_search query, options
215
- elsif query.is_a?(Hash)
216
- _advanced_search query, options
226
+ def self.search(query = nil, page=1, &block)
227
+ if block_given?
228
+ _advanced_search page, &block
229
+ elsif query.is_a?(String)
230
+ _basic_search query, page
217
231
  else
218
- raise ArgumentError, "expected query to be a string or a hash"
232
+ raise ArgumentError, "expected search to be a string or a block"
219
233
  end
220
234
  end
221
235
 
@@ -256,8 +270,9 @@ module Braintree
256
270
  _init attributes
257
271
  end
258
272
 
259
- # True if <tt>other</tt> has the same id.
273
+ # True if <tt>other</tt> is a Braintree::Transaction with the same id.
260
274
  def ==(other)
275
+ return false unless other.is_a?(Transaction)
261
276
  id == other.id
262
277
  end
263
278
 
@@ -275,8 +290,8 @@ module Braintree
275
290
  end
276
291
 
277
292
  # Creates a credit transaction that refunds this transaction.
278
- def refund
279
- response = Http.post "/transactions/#{id}/refund"
293
+ def refund(amount = nil)
294
+ response = Http.post "/transactions/#{id}/refund", :transaction => {:amount => amount}
280
295
  if response[:transaction]
281
296
  # TODO: need response to return original_transaction so that we can update status, updated_at, etc.
282
297
  SuccessfulResult.new(:new_transaction => Transaction._new(response[:transaction]))
@@ -380,29 +395,31 @@ module Braintree
380
395
  end
381
396
  end
382
397
 
383
- def self._advanced_search(query, options) # :nodoc:
384
- page = options[:page] || 1
385
- response = Http.post "/transactions/advanced_search?page=#{Util.url_encode(page)}", :search => query
398
+ def self._advanced_search(page, &block) # :nodoc:
399
+ search = TransactionSearch.new
400
+ block.call(search)
401
+
402
+ response = Http.post "/transactions/advanced_search?page=#{page}", {:search => search.to_hash}
386
403
  attributes = response[:credit_card_transactions]
387
404
  attributes[:items] = Util.extract_attribute_as_array(attributes, :transaction).map { |attrs| _new(attrs) }
388
- ResourceCollection.new(attributes) { |page_number| Transaction.search(query, :page => page_number) }
405
+
406
+ ResourceCollection.new(attributes) { |page_number| Transaction.search(nil, page_number, &block) }
389
407
  end
390
408
 
391
409
  def self._attributes # :nodoc:
392
410
  [:amount, :created_at, :credit_card_details, :customer_details, :id, :status, :type, :updated_at]
393
411
  end
394
412
 
395
- def self._basic_search(query, options) # :nodoc:
396
- page = options[:page] || 1
413
+ def self._basic_search(query, page) # :nodoc:
397
414
  response = Http.get "/transactions/all/search?q=#{Util.url_encode(query)}&page=#{Util.url_encode(page)}"
398
415
  attributes = response[:credit_card_transactions]
399
416
  attributes[:items] = Util.extract_attribute_as_array(attributes, :transaction).map { |attrs| _new(attrs) }
400
- ResourceCollection.new(attributes) { |page_number| Transaction.search(query, :page => page_number) }
417
+ ResourceCollection.new(attributes) { |page_number| Transaction.search(query, page_number) }
401
418
  end
402
419
 
403
420
  def self._create_signature # :nodoc:
404
421
  [
405
- :amount, :customer_id, :order_id, :payment_method_token, :type,
422
+ :amount, :customer_id, :merchant_account_id, :order_id, :payment_method_token, :type,
406
423
  {:credit_card => [:token, :cardholder_name, :cvv, :expiration_date, :expiration_month, :expiration_year, :number]},
407
424
  {:customer => [:id, :company, :email, :fax, :first_name, :last_name, :phone, :website]},
408
425
  {:billing => [:first_name, :last_name, :company, :country_name, :extended_address, :locality, :postal_code, :region, :street_address]},
@@ -0,0 +1,63 @@
1
+ module Braintree
2
+ class TransactionSearch < AdvancedSearch
3
+ search_fields(
4
+ :billing_company,
5
+ :billing_country_name,
6
+ :billing_extended_address,
7
+ :billing_first_name,
8
+ :billing_last_name,
9
+ :billing_locality,
10
+ :billing_postal_code,
11
+ :billing_region,
12
+ :billing_street_address,
13
+ :credit_card_cardholder_name,
14
+ :currency,
15
+ :customer_company,
16
+ :customer_email,
17
+ :customer_fax,
18
+ :customer_first_name,
19
+ :customer_id,
20
+ :customer_last_name,
21
+ :customer_phone,
22
+ :customer_website,
23
+ :id,
24
+ :order_id,
25
+ :payment_method_token,
26
+ :processor_authorization_code,
27
+ :shipping_company,
28
+ :shipping_country_name,
29
+ :shipping_extended_address,
30
+ :shipping_first_name,
31
+ :shipping_last_name,
32
+ :shipping_locality,
33
+ :shipping_postal_code,
34
+ :shipping_region,
35
+ :shipping_street_address
36
+ )
37
+
38
+ equality_fields :credit_card_expiration_date
39
+ partial_match_fields :credit_card_number
40
+
41
+ multiple_value_field :created_using, :allows => [
42
+ Transaction::CreatedUsing::FullInformation,
43
+ Transaction::CreatedUsing::Token
44
+ ]
45
+ multiple_value_field :credit_card_card_type, :allows => CreditCard::CardType::All
46
+ multiple_value_field :credit_card_customer_location, :allows => [
47
+ CreditCard::CustomerLocation::International,
48
+ CreditCard::CustomerLocation::US
49
+ ]
50
+ multiple_value_field :merchant_account_id
51
+ multiple_value_field :status, :allows => Transaction::Status::All
52
+ multiple_value_field :source, :allows => [
53
+ Transaction::Source::Api,
54
+ Transaction::Source::ControlPanel,
55
+ Transaction::Source::Recurring
56
+ ]
57
+ multiple_value_field :type
58
+
59
+ key_value_fields :refund
60
+
61
+ range_fields :amount, :created_at
62
+ end
63
+ end