braintree_query 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -39,6 +39,7 @@ examples)
39
39
  == TODO
40
40
  * rdoc
41
41
  * better way to do integration tests (i.e. hitting the real server)
42
+ * add better merchant defined field support
42
43
 
43
44
  == Note on Patches/Pull Requests
44
45
 
data/Rakefile CHANGED
@@ -5,8 +5,8 @@ begin
5
5
  require 'jeweler'
6
6
  Jeweler::Tasks.new do |gem|
7
7
  gem.name = "braintree_query"
8
- gem.summary = "Client for accessing Braintree Payment Solution's reporting API."
9
- gem.description = "Access Braintree Transactions and Vault records"
8
+ gem.summary = "Ruby client for accessing Braintree Payment Solution's reporting API"
9
+ gem.description = "Access Braintree Transactions and Customers"
10
10
  gem.email = "jeff@animoto.com"
11
11
  gem.homepage = "http://github.com/jjolma/braintree_query"
12
12
  gem.authors = ["Jeff Jolma"]
@@ -38,10 +38,17 @@ rescue LoadError
38
38
  end
39
39
  end
40
40
 
41
- task :test => :check_dependencies
42
-
41
+ task :test => :check_depentrdencies
43
42
  task :default => :test
44
43
 
44
+ begin
45
+ %w{sdoc sdoc-helpers rdiscount}.each { |name| gem name }
46
+ require 'sdoc_helpers'
47
+ rescue LoadError => ex
48
+ puts "sdoc support not enabled:"
49
+ puts ex.inspect
50
+ end
51
+
45
52
  require 'rake/rdoctask'
46
53
  Rake::RDocTask.new do |rdoc|
47
54
  version = File.exist?('VERSION') ? File.read('VERSION') : ""
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.0
1
+ 0.1.0
@@ -1,2 +1,4 @@
1
+ require 'braintree_query/base'
1
2
  require 'braintree_query/transaction'
3
+ require 'braintree_query/customer'
2
4
  require 'braintree_query/errors'
@@ -0,0 +1,69 @@
1
+ require 'ostruct'
2
+ require 'uri'
3
+ require 'net/https'
4
+ require 'xmlsimple'
5
+
6
+ module BraintreeQuery
7
+ class Base < OpenStruct
8
+ BASE_URL = "https://secure.braintreepaymentgateway.com/api/query.php"
9
+
10
+ #
11
+ # Issue a query against Braintree's query API
12
+ #
13
+ # Returns a hash of the response
14
+ #
15
+ def self.query(options)
16
+ raise ArgumentError, "username required" unless options[:username]
17
+ raise ArgumentError, "password required" unless options[:password]
18
+
19
+ query = options.map { |k,v| "#{k}=#{v}" }.join('&')
20
+ full_url = [BASE_URL, query].join '?'
21
+
22
+ uri = URI.parse(full_url)
23
+ server = Net::HTTP.new uri.host, uri.port
24
+ server.use_ssl = uri.scheme == 'https'
25
+ server.verify_mode = OpenSSL::SSL::VERIFY_NONE
26
+ response = server.post BASE_URL, query
27
+ response_hash = XmlSimple.xml_in(response.body, { 'NormaliseSpace' => 2 })
28
+ response_hash = massage(response_hash)
29
+
30
+ if response_hash && err_msg = response_hash['error_response']
31
+ if err_msg =~ /Invalid Username/
32
+ raise BraintreeQuery::CredentialError, err_msg
33
+ else
34
+ raise BraintreeQuery::Error, err_msg
35
+ end
36
+ end
37
+ response_hash
38
+ end
39
+
40
+ private
41
+
42
+ # massage the hash generated from XmlSimple into something more usable
43
+ # stolen and trimmed from rails' Hash.from_xml
44
+ def self.massage(value)
45
+ case value.class.to_s
46
+ when 'Hash'
47
+ if value.size == 0
48
+ nil
49
+ else
50
+ xml_value = value.inject({}) do |h,(k,v)|
51
+ h[k] = massage(v)
52
+ h
53
+ end
54
+ end
55
+ when 'Array'
56
+ value.map! { |i| massage(i) }
57
+ case value.length
58
+ when 0 then nil
59
+ when 1 then value.first
60
+ else value
61
+ end
62
+ when 'String'
63
+ value
64
+ else
65
+ raise "can't massage #{value.class.name} - #{value.inspect}"
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,31 @@
1
+ module BraintreeQuery
2
+ class Customer < BraintreeQuery::Base
3
+ class << self
4
+ def all(options={})
5
+ find_all options
6
+ end
7
+
8
+ def find(vault_id, options={})
9
+ find_all(options.merge(:customer_vault_id => vault_id)).first
10
+ end
11
+
12
+ def find_all(options)
13
+ response_hash = query(options.merge('report_type' => 'customer_vault'))
14
+ customers = []
15
+ if response_hash
16
+ customer_hashes = response_hash['customer_vault']
17
+ customer_hashes = customer_hashes['customer'] if customer_hashes
18
+
19
+ # handle the single result scenario
20
+ if customer_hashes.is_a?(Hash)
21
+ customer_hashes = [customer_hashes]
22
+ end
23
+ customer_hashes.each do |customer_hash|
24
+ customers << Customer.new(customer_hash)
25
+ end
26
+ end
27
+ customers
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,11 +1,5 @@
1
- require 'uri'
2
- require 'net/https'
3
- require 'xmlsimple'
4
- require 'ostruct'
5
-
6
1
  module BraintreeQuery
7
- class Transaction < OpenStruct
8
- BASE_URL = "https://secure.braintreepaymentgateway.com/api/query.php"
2
+ class Transaction < BraintreeQuery::Base
9
3
  class << self
10
4
  def all(options={})
11
5
  find_all options
@@ -16,26 +10,9 @@ module BraintreeQuery
16
10
  end
17
11
 
18
12
  def find_all(options)
19
- query = options.map { |k,v| "#{k}=#{v}" }.join('&')
20
- full_url = [BASE_URL, query].join '?'
21
-
22
- uri = URI.parse(full_url)
23
- server = Net::HTTP.new uri.host, uri.port
24
- server.use_ssl = uri.scheme == 'https'
25
- server.verify_mode = OpenSSL::SSL::VERIFY_NONE
26
- response = server.post BASE_URL, query
27
- response_hash = XmlSimple.xml_in(response.body, { 'NormaliseSpace' => 2 })
28
- response_hash = massage(response_hash)
29
-
13
+ response_hash = query(options)
30
14
  txns = []
31
15
  if response_hash
32
- if err_msg = response_hash['error_response']
33
- if err_msg =~ /Invalid Username/
34
- raise BraintreeQuery::CredentialError, err_msg
35
- else
36
- raise BraintreeQuery::Error, err_msg
37
- end
38
- end
39
16
  txn_hashes = response_hash['transaction']
40
17
 
41
18
  # handle the single result scenario
@@ -48,33 +25,6 @@ module BraintreeQuery
48
25
  end
49
26
  txns
50
27
  end
51
-
52
- # stolen and trimmed from rails' Hash.from_xml
53
- def massage(value)
54
- case value.class.to_s
55
- when 'Hash'
56
- if value.size == 0
57
- nil
58
- else
59
- xml_value = value.inject({}) do |h,(k,v)|
60
- h[k] = massage(v)
61
- h
62
- end
63
- end
64
- when 'Array'
65
- value.map! { |i| massage(i) }
66
- case value.length
67
- when 0 then nil
68
- when 1 then value.first
69
- else value
70
- end
71
- when 'String'
72
- value
73
- else
74
- raise "can't massage #{value.class.name} - #{value.inspect}"
75
- end
76
- end
77
-
78
28
  end
79
29
  end
80
30
  end
@@ -9,83 +9,132 @@ class TestBraintreeQuery < Test::Unit::TestCase
9
9
  FakeWeb.allow_net_connect = false
10
10
  end
11
11
 
12
- def test_find_within_unknown_txn_id
12
+ #
13
+ # Transaction
14
+ #
15
+ def test_find_transaction_with_unknown_txn_id
13
16
  FakeWeb.register_uri(:post, /.*/, :body => empty_transactions_xml)
14
- assert_nil BraintreeQuery::Transaction.find(12345)
17
+ assert_nil BraintreeQuery::Transaction.find(12345, :username => 'foo', :password => 'bar')
15
18
  end
16
19
 
17
- def test_find
20
+ def test_find_transaction
18
21
  FakeWeb.register_uri(:post, /.*/, :body => single_transaction_xml)
19
22
 
20
23
  txn_id = 1177101697
21
- txn = BraintreeQuery::Transaction.find(123)
24
+ txn = BraintreeQuery::Transaction.find(123, :username => 'foo', :password => 'bar')
22
25
  assert_not_nil txn
23
26
  assert_equal txn_id.to_s, txn.transaction_id
24
27
  assert_equal 'Eric', txn.first_name
25
28
  end
26
29
 
27
- def test_all
30
+ def test_all_transactions
28
31
  FakeWeb.register_uri(:post, /.*/, :body => multiple_transaction_xml)
29
32
 
30
- txns = BraintreeQuery::Transaction.all
33
+ txns = BraintreeQuery::Transaction.all(:username => 'foo', :password => 'bar')
31
34
  assert_not_nil txns
32
35
  assert_equal 2, txns.size
33
36
  assert_equal ['1176380853', '1176380089'].sort, txns.map(&:transaction_id).sort
34
37
  end
35
38
 
36
- def test_handles_credential_error
39
+ def test_transaction_handles_credential_error
37
40
  FakeWeb.register_uri(:post, /.*/, :body => error_xml)
38
41
  assert_raises BraintreeQuery::CredentialError do
39
- BraintreeQuery::Transaction.all
42
+ BraintreeQuery::Transaction.all(:username => 'foo', :password => 'bar')
40
43
  end
41
44
  end
42
45
 
43
- def test_handles_generic_error
46
+ def test_transaction_handles_generic_error
44
47
  FakeWeb.register_uri(:post, /.*/, :body => generic_error_xml)
45
48
  assert_raises BraintreeQuery::Error do
46
- BraintreeQuery::Transaction.all
49
+ BraintreeQuery::Transaction.all(:username => 'foo', :password => 'bar')
47
50
  end
48
51
  end
49
52
 
50
- # TODO handle the 'action' section of a transaction (can have multiple e.g. if refunded)
51
-
52
- # TODO best way to do integration tests that actually hit the server?
53
- # def test_integration_find
54
- # FakeWeb.allow_net_connect = true
55
- # txn_id = 1177101697
56
- # txn = BraintreeQuery::Transaction.find(1177101697, :username => 'testapi', :password => 'password1')
57
- # assert_equal txn_id.to_s, txn.transaction_id
58
- # end
59
- #
60
- # def test_integration_all_by_date_range
61
- # FakeWeb.allow_net_connect = true
62
- #
63
- # # 15 minute window with three transactions
64
- # start = ['2010', '01', '20', '15', '00'].join
65
- # finish = ['2010', '01', '20', '15', '18'].join
66
- # txns = BraintreeQuery::Transaction.all(:username => 'testapi',
67
- # :password => 'password1',
68
- # :start_date => start,
69
- # :end_date => finish)
70
- #
71
- # assert_equal 3, txns.size
72
- # assert_equal ["1177097583", "1177098339", "1177099345"].sort, txns.map(&:transaction_id).sort
73
- # end
74
53
  #
75
- # def test_integration_find_refunds
76
- # FakeWeb.allow_net_connect = true
54
+ # Customer
77
55
  #
78
- # # 12 hour window with two refunds
79
- # start = ['2010', '01', '19', '00'].join
80
- # finish = ['2010', '01', '19', '12'].join
81
- # txns = BraintreeQuery::Transaction.all(:username => 'testapi',
82
- # :password => 'password1',
83
- # :start_date => start,
84
- # :end_date => finish,
85
- # :action_type => 'refund')
86
- # assert_equal 2, txns.size
87
- # assert_equal ["1176565207", "1176565239"].sort, txns.map(&:transaction_id).sort
88
- # end
56
+ def test_customer_find_with_unknown_cookie
57
+ FakeWeb.register_uri(:post, /.*/, :body => empty_customers_xml)
58
+ assert_nil BraintreeQuery::Customer.find(12345, :username => 'foo', :password => 'bar')
59
+ end
60
+
61
+ def test_find_customer
62
+ FakeWeb.register_uri(:post, /.*/, :body => single_customer_xml)
63
+
64
+
65
+ vault_id = '4008081888597345'
66
+ c = BraintreeQuery::Customer.find(vault_id, :username => 'foo', :password => 'bar')
67
+ assert_not_nil c
68
+
69
+ assert_equal vault_id.to_s, c.customer_vault_id
70
+ assert_equal '4xxxxxxxxxxx1111', c.cc_number
71
+ assert_equal '0111', c.cc_exp
72
+ end
73
+
74
+ def test_all_customers
75
+ FakeWeb.register_uri(:post, /.*/, :body => multiple_customers_xml)
76
+
77
+ customers = BraintreeQuery::Customer.all(:username => 'foo', :password => 'bar')
78
+ assert_not_nil customers
79
+ assert_equal 2, customers.size
80
+ assert_equal ['0111', '0112'].sort, customers.map(&:cc_exp).sort
81
+ end
82
+
83
+ def test_customer_handles_credential_error
84
+ FakeWeb.register_uri(:post, /.*/, :body => error_xml)
85
+ assert_raises BraintreeQuery::CredentialError do
86
+ BraintreeQuery::Customer.all(:username => 'foo', :password => 'bar')
87
+ end
88
+ end
89
+
90
+ def test_customer_handles_generic_error
91
+ FakeWeb.register_uri(:post, /.*/, :body => generic_error_xml)
92
+ assert_raises BraintreeQuery::Error do
93
+ BraintreeQuery::Customer.all(:username => 'foo', :password => 'bar')
94
+ end
95
+ end
96
+
97
+ # TODO handle the 'action' section of a transaction (can have multiple e.g. if refunded)
98
+
99
+ if false # integration
100
+ def test_integration_find
101
+ FakeWeb.allow_net_connect = true
102
+ txn_id = 1179747037
103
+ txn = BraintreeQuery::Transaction.find(txn_id, :username => 'testapi', :password => 'password1')
104
+ assert_equal txn_id.to_s, txn.transaction_id
105
+ end
106
+
107
+ def test_integration_all_by_date_range
108
+ FakeWeb.allow_net_connect = true
109
+
110
+ # 15 minute window with three transactions
111
+ # their admin interface shows in central time (-6 hours)
112
+ start = ['2010', '01', '27', '15', '00'].join
113
+ finish = ['2010', '01', '27', '15', '30'].join
114
+ txns = BraintreeQuery::Transaction.all(:username => 'testapi',
115
+ :password => 'password1',
116
+ :start_date => start,
117
+ :end_date => finish)
118
+
119
+ assert_equal 3, txns.size
120
+ assert_equal ["1179713614", "1179713932", '1179714121'].sort, txns.map(&:transaction_id).sort
121
+ end
122
+
123
+ def test_integration_find_refunds
124
+ FakeWeb.allow_net_connect = true
125
+
126
+ # window with two refunds
127
+ start = ['2010', '01', '27', '00'].join
128
+ finish = ['2010', '01', '27', '03'].join
129
+ txns = BraintreeQuery::Transaction.all(:username => 'testapi',
130
+ :password => 'password1',
131
+ :start_date => start,
132
+ :end_date => finish,
133
+ :action_type => 'refund')
134
+ assert_equal 2, txns.size
135
+ assert_equal ['1179527112', '1179527128'].sort, txns.map(&:transaction_id).sort
136
+ end
137
+ end # integration
89
138
 
90
139
  protected
91
140
  def empty_transactions_xml
@@ -96,6 +145,14 @@ class TestBraintreeQuery < Test::Unit::TestCase
96
145
  EOF
97
146
  end
98
147
 
148
+ def empty_customers_xml
149
+ <<-EOF
150
+ <?xml version="1.0" encoding="UTF-8"?>
151
+ <nm_response>
152
+ </nm_response>
153
+ EOF
154
+ end
155
+
99
156
  def error_xml
100
157
  <<-EOF
101
158
  <?xml version="1.0" encoding="UTF-8"?>
@@ -351,6 +408,156 @@ class TestBraintreeQuery < Test::Unit::TestCase
351
408
  </nm_response>
352
409
  EOF
353
410
  end
411
+
412
+ def single_customer_xml
413
+ <<-EOF
414
+ <nm_response>
415
+ <customer_vault>
416
+ <customer id=" 4008081888597345">
417
+ <first_name>GGH</first_name>
418
+ <last_name>null</last_name>
419
+ <address_1>5645 Main Street</address_1>
420
+ <address_2></address_2>
421
+ <company></company>
422
+ <city>Jacksonville</city>
423
+ <state>FL</state>
424
+ <postal_code>32209</postal_code>
425
+ <country>US</country>
426
+ <email></email>
427
+ <phone>null</phone>
428
+ <fax></fax>
429
+ <cell_phone></cell_phone>
430
+ <customertaxid></customertaxid>
431
+ <website></website>
432
+ <shipping_first_name></shipping_first_name>
433
+ <shipping_last_name></shipping_last_name>
434
+ <shipping_address_1></shipping_address_1>
435
+ <shipping_address_2></shipping_address_2>
436
+ <shipping_company></shipping_company>
437
+ <shipping_city></shipping_city>
438
+ <shipping_state></shipping_state>
439
+ <shipping_postal_code></shipping_postal_code>
440
+ <shipping_country></shipping_country>
441
+ <shipping_email></shipping_email>
442
+ <shipping_carrier></shipping_carrier>
443
+ <tracking_number></tracking_number>
444
+ <shipping_date></shipping_date>
445
+ <shipping></shipping>
446
+ <cc_number>4xxxxxxxxxxx1111</cc_number>
447
+ <cc_hash>b096459008ccf92840b826f531353860</cc_hash>
448
+ <cc_exp>0111</cc_exp>
449
+ <check_account></check_account>
450
+ <check_hash></check_hash>
451
+ <check_aba></check_aba>
452
+ <check_name></check_name>
453
+ <account_holder_type></account_holder_type>
454
+ <account_type></account_type>
455
+ <sec_code></sec_code>
456
+ <processor_id></processor_id>
457
+ <cc_bin>405501</cc_bin>
458
+ <customer_vault_id>4008081888597345</customer_vault_id>
459
+ </customer>
460
+ </customer_vault>
461
+ </nm_response>
462
+ EOF
463
+ end
464
+
465
+ def multiple_customers_xml
466
+ <<-EOF
467
+ <nm_response>
468
+ <customer_vault>
469
+ <customer id=" 4008081888597345">
470
+ <first_name>GGH</first_name>
471
+ <last_name>null</last_name>
472
+ <address_1>5645 Main Street</address_1>
473
+ <address_2></address_2>
474
+ <company></company>
475
+ <city>Jacksonville</city>
476
+ <state>FL</state>
477
+ <postal_code>32209</postal_code>
478
+ <country>US</country>
479
+ <email></email>
480
+ <phone>null</phone>
481
+ <fax></fax>
482
+ <cell_phone></cell_phone>
483
+ <customertaxid></customertaxid>
484
+ <website></website>
485
+ <shipping_first_name></shipping_first_name>
486
+ <shipping_last_name></shipping_last_name>
487
+ <shipping_address_1></shipping_address_1>
488
+ <shipping_address_2></shipping_address_2>
489
+ <shipping_company></shipping_company>
490
+ <shipping_city></shipping_city>
491
+ <shipping_state></shipping_state>
492
+ <shipping_postal_code></shipping_postal_code>
493
+ <shipping_country></shipping_country>
494
+ <shipping_email></shipping_email>
495
+ <shipping_carrier></shipping_carrier>
496
+ <tracking_number></tracking_number>
497
+ <shipping_date></shipping_date>
498
+ <shipping></shipping>
499
+ <cc_number>4xxxxxxxxxxx1111</cc_number>
500
+ <cc_hash>b096459008ccf92840b826f531353860</cc_hash>
501
+ <cc_exp>0111</cc_exp>
502
+ <check_account></check_account>
503
+ <check_hash></check_hash>
504
+ <check_aba></check_aba>
505
+ <check_name></check_name>
506
+ <account_holder_type></account_holder_type>
507
+ <account_type></account_type>
508
+ <sec_code></sec_code>
509
+ <processor_id></processor_id>
510
+ <cc_bin>405501</cc_bin>
511
+ <customer_vault_id> 4008081888597345</customer_vault_id>
512
+ </customer>
513
+ <customer id=" 4008081888597346">
514
+ <first_name>GGH</first_name>
515
+ <last_name>null</last_name>
516
+ <address_1>5645 Main Street</address_1>
517
+ <address_2></address_2>
518
+ <company></company>
519
+ <city>Jacksonville</city>
520
+ <state>FL</state>
521
+ <postal_code>32209</postal_code>
522
+ <country>US</country>
523
+ <email></email>
524
+ <phone>null</phone>
525
+ <fax></fax>
526
+ <cell_phone></cell_phone>
527
+ <customertaxid></customertaxid>
528
+ <website></website>
529
+ <shipping_first_name></shipping_first_name>
530
+ <shipping_last_name></shipping_last_name>
531
+ <shipping_address_1></shipping_address_1>
532
+ <shipping_address_2></shipping_address_2>
533
+ <shipping_company></shipping_company>
534
+ <shipping_city></shipping_city>
535
+ <shipping_state></shipping_state>
536
+ <shipping_postal_code></shipping_postal_code>
537
+ <shipping_country></shipping_country>
538
+ <shipping_email></shipping_email>
539
+ <shipping_carrier></shipping_carrier>
540
+ <tracking_number></tracking_number>
541
+ <shipping_date></shipping_date>
542
+ <shipping></shipping>
543
+ <cc_number>4xxxxxxxxxxx1111</cc_number>
544
+ <cc_hash>b096459008ccf92840b826f531353860</cc_hash>
545
+ <cc_exp>0112</cc_exp>
546
+ <check_account></check_account>
547
+ <check_hash></check_hash>
548
+ <check_aba></check_aba>
549
+ <check_name></check_name>
550
+ <account_holder_type></account_holder_type>
551
+ <account_type></account_type>
552
+ <sec_code></sec_code>
553
+ <processor_id></processor_id>
554
+ <cc_bin>405501</cc_bin>
555
+ <customer_vault_id> 4008081888597346</customer_vault_id>
556
+ </customer>
557
+ </customer_vault>
558
+ </nm_response>
559
+ EOF
560
+ end
354
561
  end
355
562
 
356
563
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: braintree_query
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeff Jolma
@@ -9,11 +9,11 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2010-01-20 00:00:00 -05:00
12
+ date: 2010-01-29 00:00:00 -05:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
16
- description: Access Braintree Transactions and Vault records
16
+ description: Access Braintree Transactions and Customers
17
17
  email: jeff@animoto.com
18
18
  executables: []
19
19
 
@@ -28,6 +28,8 @@ files:
28
28
  - Rakefile
29
29
  - VERSION
30
30
  - lib/braintree_query.rb
31
+ - lib/braintree_query/base.rb
32
+ - lib/braintree_query/customer.rb
31
33
  - lib/braintree_query/errors.rb
32
34
  - lib/braintree_query/transaction.rb
33
35
  - test/helper.rb
@@ -59,7 +61,7 @@ rubyforge_project:
59
61
  rubygems_version: 1.3.5
60
62
  signing_key:
61
63
  specification_version: 3
62
- summary: Client for accessing Braintree Payment Solution's reporting API.
64
+ summary: Ruby client for accessing Braintree Payment Solution's reporting API
63
65
  test_files:
64
66
  - test/helper.rb
65
67
  - test/test_braintree_query.rb