braintree_query 0.0.0 → 0.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/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