trustcommerce 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2006 Zack Chandler <zackchandler@depixelate.com>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,147 @@
1
+ = TrustCommerce Subscription Library
2
+
3
+ [TrustCommerce](http://www.trustcommerce.com) is a payment gateway providing credit card
4
+ processing and recurring / subscription billing services.
5
+
6
+ This library provides a simple interface to create, edit, delete, and query subscriptions
7
+ using TrustCommerce.
8
+
9
+ == Background
10
+
11
+ TrustCommerce's recurring / subscription billing solution is implemented through a service
12
+ called [Citadel](http://www.trustcommerce.com/citadel.php). A Citadel-enabled account is
13
+ required to use the subscription-based features implemented by this library.
14
+
15
+ === Citadel Basics
16
+ * Citadel stores customer profiles which can include credit card information and billing frequency.
17
+ * Citadel will automatically bill customers on their respective schedules.
18
+ * Citadel identifies each customer by a Billing ID (six-character alphanumeric string).
19
+ * A customer's profile, credit card, and billing frequency can be modified using the Billing ID.
20
+
21
+ == Installation
22
+
23
+ The simple way:
24
+ $ sudo gem install trustcommerce
25
+
26
+ Directly from repository:
27
+ $ svn co svn://rubyforge.org/var/svn/trustcommerce/trunk trustcommerce
28
+
29
+ It is highly recommended to download and install the
30
+ [TCLink ruby extension](http://www.trustcommerce.com/tclink.php).
31
+ This extension provides failover capability and enhanced security features.
32
+ If this library is not installed, standard POST over SSL will be used.
33
+
34
+ == Configuration
35
+
36
+ When you signup for a TrustCommerce account you are issued a custid and a password.
37
+ These are your credentials when using the TrustCommerce API.
38
+
39
+ TrustCommerce.custid = '123456'
40
+ TrustCommerce.password = 'topsecret'
41
+
42
+ # optional - sets Vault password for use in query() calls
43
+ TrustCommerce.vault_password = 'supersecure'
44
+
45
+ The password that TrustCommerce issues never changes or expires when used through the TCLink
46
+ extension. However if you choose to use SSL over HTTP instead (the fallback option if the TCLink
47
+ library is not installed), be aware that you need to set the password to your Vault password.
48
+ Likewise, if your application uses the query() method you must set the vault_password.
49
+ The reason is that TrustCommerce currently routes these query() calls through the vault
50
+ and therefore your password must be set accordingly. To make matters more complicated,
51
+ TrustCommerce currently forces you to change the Vault password every 90 days.
52
+
53
+
54
+ == Examples
55
+
56
+ === Creating a subscription
57
+
58
+ # Bill Jennifer $12.00 monthly
59
+ response = TrustCommerce::Subscription.create(
60
+ :cc => '4111111111111111',
61
+ :exp => '0412',
62
+ :name => 'Jennifer Smith',
63
+ :amount => 1200,
64
+ :cycle => '1m'
65
+ )
66
+
67
+ if response['status'] == 'approved'
68
+ puts "Subscription created with Billing ID: #{response['billingid']}"
69
+ else
70
+ puts "An error occurred: #{response['error']}"
71
+ end
72
+
73
+
74
+ === Update a subscription
75
+
76
+ # Update subscription to use new credit card
77
+ response = TrustCommerce::Subscription.update(
78
+ :billingid => 'ABC123',
79
+ :cc => '5411111111111115',
80
+ :exp => '0412'
81
+ )
82
+
83
+ if response['status'] == 'accepted'
84
+ puts 'Subscription updated.'
85
+ else
86
+ puts "An error occurred: #{response['error']}"
87
+ end
88
+
89
+
90
+ === Delete a subscription
91
+
92
+ # Delete subscription
93
+ response = TrustCommerce::Subscription.delete(
94
+ :billingid => 'ABC123'
95
+ )
96
+
97
+ if response['status'] == 'accepted'
98
+ puts 'Subscription removed from active use.'
99
+ else
100
+ puts 'An error occurred.'
101
+ end
102
+
103
+
104
+ === Query a subscription
105
+
106
+ # Get all sale transactions for a subscription in CSV format
107
+ response = TrustCommerce::Subscription.query(
108
+ :querytype => 'transaction',
109
+ :action => 'sale',
110
+ :billingid => 'ABC123'
111
+ )
112
+
113
+
114
+ === Process a one-time charge
115
+
116
+ # Process one-time sale against existing subscription
117
+ response = TrustCommerce::Subscription.charge(
118
+ :billingid => 'ABC123',
119
+ :amount => 1995
120
+ )
121
+
122
+
123
+ === Credit a transaction
124
+
125
+ # Process one-time credit against existing transaction
126
+ response = TrustCommerce::Subscription.credit(
127
+ :transid => '001-0000111101',
128
+ :amount => 1995
129
+ )
130
+
131
+
132
+ == Running the tests
133
+
134
+ The following special environment variables must be set up prior to running tests:
135
+
136
+ $ export TC_USERNAME=123456
137
+ $ export TC_PASSWORD=password
138
+ $ export TC_VAULT_PASSWORD=password
139
+
140
+ Run tests via rake:
141
+
142
+ $ rake test
143
+
144
+ Run tests via ruby:
145
+
146
+ $ ruby test/trustcommerce_test.rb
147
+
data/Rakefile ADDED
@@ -0,0 +1,198 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require 'rake/rdoctask'
5
+ require 'rake/packagetask'
6
+ require 'rake/gempackagetask'
7
+ require 'erb'
8
+
9
+ require File.dirname(__FILE__) + '/lib/trustcommerce'
10
+ require File.dirname(__FILE__) + '/lib/version'
11
+
12
+ task :default => :test
13
+
14
+ Rake::TestTask.new(:test) { |t|
15
+ t.libs << 'test'
16
+ t.test_files = Dir.glob('test/*_test.rb')
17
+ t.verbose = true
18
+ }
19
+
20
+ namespace :doc do
21
+
22
+ Rake::RDocTask.new do |rdoc|
23
+ rdoc.rdoc_dir = 'doc'
24
+ rdoc.title = "TrustCommerce Subscription Library"
25
+ rdoc.options << '--line-numbers' << '--inline-source'
26
+ rdoc.rdoc_files.include('README')
27
+ rdoc.rdoc_files.include('MIT-LICENSE')
28
+ rdoc.rdoc_files.include('lib/**/*.rb')
29
+ end
30
+
31
+ task :rdoc => 'doc:readme'
32
+
33
+ task :refresh => :rerdoc do
34
+ system 'open doc/index.html'
35
+ end
36
+
37
+ task :readme do
38
+ require 'support/rdoc/code_info'
39
+ RDoc::CodeInfo.parse('(lib|test)/*.rb')
40
+
41
+ strip_comments = lambda {|comment| comment.gsub(/^# ?/, '')}
42
+ docs_for = lambda do |location|
43
+ info = RDoc::CodeInfo.for(location)
44
+ raise RuntimeError, "Couldn't find documentation for `#{location}'" unless info
45
+ strip_comments[info.comment]
46
+ end
47
+
48
+ open('README', 'w') do |file|
49
+ file.write ERB.new(IO.read('README.erb')).result(binding)
50
+ end
51
+ end
52
+
53
+ task :deploy => :rerdoc do
54
+ sh %(scp -r doc zackchandler@rubyforge.org:/var/www/gforge-projects/trustcommerce/)
55
+ end
56
+
57
+ end
58
+
59
+ namespace :site do
60
+ require 'rdoc/markup/simple_markup'
61
+ require 'rdoc/markup/simple_markup/to_html'
62
+
63
+ readme = lambda { IO.read('README') }
64
+
65
+ readme_to_html = lambda do
66
+ handler = SM::ToHtml.new
67
+ handler.instance_eval do
68
+ require 'syntax'
69
+ require 'syntax/convertors/html'
70
+ def accept_verbatim(am, fragment)
71
+ syntax = Syntax::Convertors::HTML.for_syntax('ruby')
72
+ @res << %(<div class="ruby">#{syntax.convert(fragment.txt, true)}</div>)
73
+ end
74
+ end
75
+ html = SM::SimpleMarkup.new.convert(readme.call, handler)
76
+ html.gsub(%r{\[([\w\s]+)\]\(([\w:/.]+)\)}, '<a href="\2">\1</a>')
77
+ end
78
+
79
+ desc 'Regenerate the public website page'
80
+ task :build => 'doc:readme' do
81
+ open('site/public/index.html', 'w') do |file|
82
+ erb_data = {}
83
+ erb_data[:readme] = readme_to_html.call
84
+ file.write ERB.new(IO.read('site/index.erb')).result(binding)
85
+ end
86
+ end
87
+
88
+ task :refresh => :build do
89
+ system 'open site/public/index.html'
90
+ end
91
+
92
+ desc 'Update the live website'
93
+ task :deploy => :build do
94
+ site_files = FileList['site/public/*']
95
+ sh %(scp #{site_files.join ' '} zackchandler@rubyforge.org:/var/www/gforge-projects/trustcommerce/)
96
+ end
97
+ end
98
+
99
+ namespace :dist do
100
+
101
+ spec = Gem::Specification.new do |s|
102
+ s.name = 'trustcommerce'
103
+ s.version = Gem::Version.new(TrustCommerce::Version)
104
+ s.summary = 'TrustCommerce Subscription Library'
105
+ s.description = s.summary
106
+ s.email = 'zackchandler@depixelate.com'
107
+ s.author = 'Zack Chandler'
108
+ s.has_rdoc = true
109
+ s.extra_rdoc_files = %w(README MIT-LICENSE)
110
+ s.homepage = 'http://trustcommerce.rubyforge.org'
111
+ s.rubyforge_project = 'trustcommerce'
112
+ s.files = FileList['Rakefile', 'lib/*.rb', 'support/**/*.rb']
113
+ s.test_files = Dir['test/*']
114
+
115
+ s.rdoc_options = ['--title', 'TrustCommerce Subscription Library',
116
+ '--main', 'README',
117
+ '--line-numbers', '--inline-source']
118
+ end
119
+
120
+ # Regenerate README before packaging
121
+ task :package => 'doc:readme'
122
+ Rake::GemPackageTask.new(spec) do |pkg|
123
+ pkg.need_tar_gz = true
124
+ pkg.package_files.include('{lib,test,support}/**/*')
125
+ pkg.package_files.include('README')
126
+ pkg.package_files.include('MIT-LICENSE')
127
+ pkg.package_files.include('Rakefile')
128
+ end
129
+
130
+ desc 'Install with gems'
131
+ task :install => :repackage do
132
+ sh "sudo gem i pkg/#{spec.name}-#{spec.version}.gem"
133
+ end
134
+
135
+ desc 'Uninstall gem'
136
+ task :uninstall do
137
+ sh "sudo gem uninstall #{spec.name}"
138
+ end
139
+
140
+ desc 'Reinstall gem'
141
+ task :reinstall => [:uninstall, :install]
142
+
143
+ task :confirm_release do
144
+ print "Releasing version #{spec.version}. Are you sure you want to proceed? [Yn] "
145
+ abort if STDIN.getc == ?n
146
+ end
147
+
148
+ desc 'Tag release'
149
+ task :tag do
150
+ svn_root = 'svn+ssh://zackchandler@rubyforge.org/var/svn/trustcommerce'
151
+ sh %(svn cp #{svn_root}/trunk #{svn_root}/tags/rel-#{spec.version} -m "Tag #{spec.name} release #{spec.version}")
152
+ end
153
+
154
+ desc 'Update changelog to include a release marker'
155
+ task :add_release_marker_to_changelog do
156
+ changelog = IO.read('CHANGELOG')
157
+ changelog.sub!(/^trunk:/, "#{spec.version}:")
158
+
159
+ open('CHANGELOG', 'w') do |file|
160
+ file.write "trunk:\n\n#{changelog}"
161
+ end
162
+ end
163
+
164
+ task :commit_changelog do
165
+ sh %(svn ci CHANGELOG -m "Bump changelog version marker for release")
166
+ end
167
+
168
+ package_name = lambda {|specification| File.join('pkg', "#{specification.name}-#{specification.version}")}
169
+
170
+ desc 'Push a release to rubyforge'
171
+ task :release => [:confirm_release, :clean, :add_release_marker_to_changelog, :package, :commit_changelog, :tag] do
172
+ require 'rubyforge'
173
+ package = package_name[spec]
174
+
175
+ rubyforge = RubyForge.new
176
+ rubyforge.login
177
+
178
+ version_already_released = lambda do
179
+ releases = rubyforge.config['rubyforge']['release_ids']
180
+ releases.has_key?(spec.name) && releases[spec.name][spec.version]
181
+ end
182
+
183
+ abort("Release #{spec.version} already exists!") if version_already_released.call
184
+
185
+ if release_id = rubyforge.add_release(spec.rubyforge_project, spec.name, spec.version, "#{package}.tar.gz")
186
+ rubyforge.add_file(spec.rubyforge_project, spec.name, release_id, "#{package}.gem")
187
+ else
188
+ puts 'Release failed!'
189
+ end
190
+ end
191
+
192
+ task :spec do
193
+ puts spec.to_ruby
194
+ end
195
+
196
+ end
197
+
198
+ task :clean => ['dist:clobber_package', 'doc:clobber_rdoc']
@@ -0,0 +1,217 @@
1
+ require 'net/http'
2
+ require 'net/https'
3
+ require 'uri'
4
+
5
+ # [TrustCommerce](http://www.trustcommerce.com) is a payment gateway providing credit card
6
+ # processing and recurring / subscription billing services.
7
+ #
8
+ # This library provides a simple interface to create, edit, delete, and query subscriptions
9
+ # using TrustCommerce.
10
+ #
11
+ # == Background
12
+ #
13
+ # TrustCommerce's recurring / subscription billing solution is implemented through a service
14
+ # called [Citadel](http://www.trustcommerce.com/citadel.php). A Citadel-enabled account is
15
+ # required to use the subscription-based features implemented by this library.
16
+ #
17
+ # === Citadel Basics
18
+ # * Citadel stores customer profiles which can include credit card information and billing frequency.
19
+ # * Citadel will automatically bill customers on their respective schedules.
20
+ # * Citadel identifies each customer by a Billing ID (six-character alphanumeric string).
21
+ # * A customer's profile, credit card, and billing frequency can be modified using the Billing ID.
22
+ #
23
+ # == Installation
24
+ #
25
+ # The simple way:
26
+ # $ sudo gem install trustcommerce
27
+ #
28
+ # Directly from repository:
29
+ # $ svn co svn://rubyforge.org/var/svn/trustcommerce/trunk trustcommerce
30
+ #
31
+ # It is highly recommended to download and install the
32
+ # [TCLink ruby extension](http://www.trustcommerce.com/tclink.php).
33
+ # This extension provides failover capability and enhanced security features.
34
+ # If this library is not installed, standard POST over SSL will be used.
35
+ #
36
+ # == Configuration
37
+ #
38
+ # When you signup for a TrustCommerce account you are issued a custid and a password.
39
+ # These are your credentials when using the TrustCommerce API.
40
+ #
41
+ # TrustCommerce.custid = '123456'
42
+ # TrustCommerce.password = 'topsecret'
43
+ #
44
+ # # optional - sets Vault password for use in query() calls
45
+ # TrustCommerce.vault_password = 'supersecure'
46
+ #
47
+ # The password that TrustCommerce issues never changes or expires when used through the TCLink
48
+ # extension. However if you choose to use SSL over HTTP instead (the fallback option if the TCLink
49
+ # library is not installed), be aware that you need to set the password to your Vault password.
50
+ # Likewise, if your application uses the query() method you must set the vault_password.
51
+ # The reason is that TrustCommerce currently routes these query() calls through the vault
52
+ # and therefore your password must be set accordingly. To make matters more complicated,
53
+ # TrustCommerce currently forces you to change the Vault password every 90 days.
54
+ class TrustCommerce
55
+
56
+ class << self
57
+ attr_accessor :custid
58
+ attr_accessor :password
59
+ attr_accessor :vault_password
60
+
61
+ # Returns Vault password.
62
+ def vault_password
63
+ @vault_password || password
64
+ end
65
+ end
66
+ self.custid = 'TestMerchant'
67
+ self.password = 'password'
68
+
69
+ # Settings for standard POST over SSL
70
+ # Only used if TCLink library is not installed
71
+ API_SETTINGS = {
72
+ :domain => 'vault.trustcommerce.com',
73
+ :query_path => '/query/',
74
+ :trans_path => '/trans/',
75
+ :port => 443
76
+ }
77
+
78
+ class Subscription
79
+
80
+ # # Bill Jennifer $12.00 monthly
81
+ # response = TrustCommerce::Subscription.create(
82
+ # :cc => '4111111111111111',
83
+ # :exp => '0412',
84
+ # :name => 'Jennifer Smith',
85
+ # :amount => 1200,
86
+ # :cycle => '1m'
87
+ # )
88
+ #
89
+ # if response['status'] == 'approved'
90
+ # puts "Subscription created with Billing ID: #{response['billingid']}"
91
+ # else
92
+ # puts "An error occurred: #{response['error']}"
93
+ # end
94
+ def self.create(options)
95
+ return TrustCommerce.send_request(options.merge(:action => 'store'))
96
+ end
97
+
98
+ # # Update subscription to use new credit card
99
+ # response = TrustCommerce::Subscription.update(
100
+ # :billingid => 'ABC123',
101
+ # :cc => '5411111111111115',
102
+ # :exp => '0412'
103
+ # )
104
+ #
105
+ # if response['status'] == 'accepted'
106
+ # puts 'Subscription updated.'
107
+ # else
108
+ # puts "An error occurred: #{response['error']}"
109
+ # end
110
+ def self.update(options)
111
+ return TrustCommerce.send_request(options.merge(:action => 'store'))
112
+ end
113
+
114
+ # # Delete subscription
115
+ # response = TrustCommerce::Subscription.delete(
116
+ # :billingid => 'ABC123'
117
+ # )
118
+ #
119
+ # if response['status'] == 'accepted'
120
+ # puts 'Subscription removed from active use.'
121
+ # else
122
+ # puts 'An error occurred.'
123
+ # end
124
+ def self.delete(options)
125
+ return TrustCommerce.send_request(options.merge(:action => 'unstore'))
126
+ end
127
+
128
+ # # Process one-time sale against existing subscription
129
+ # response = TrustCommerce::Subscription.charge(
130
+ # :billingid => 'ABC123',
131
+ # :amount => 1995
132
+ # )
133
+ def self.charge(options)
134
+ return TrustCommerce.send_request(options.merge(:action => 'sale'))
135
+ end
136
+
137
+ # # Process one-time credit against existing transaction
138
+ # response = TrustCommerce::Subscription.credit(
139
+ # :transid => '001-0000111101',
140
+ # :amount => 1995
141
+ # )
142
+ def self.credit(options)
143
+ return TrustCommerce.send_request(options.merge(:action => 'credit'))
144
+ end
145
+
146
+ # # Get all sale transactions for a subscription in CSV format
147
+ # response = TrustCommerce::Subscription.query(
148
+ # :querytype => 'transaction',
149
+ # :action => 'sale',
150
+ # :billingid => 'ABC123'
151
+ # )
152
+ def self.query(options)
153
+ return TrustCommerce.send_query(options)
154
+ end
155
+
156
+ end
157
+
158
+ # It is highly recommended to download and install the
159
+ # [TCLink ruby extension](http://www.trustcommerce.com/tclink.php).
160
+ # This extension provides failover capability and enhanced security features.
161
+ # If this library is not installed, standard POST over SSL will be used.
162
+ def self.tclink?
163
+ begin
164
+ require 'tclink'
165
+ true
166
+ rescue LoadError
167
+ false
168
+ end
169
+ end
170
+
171
+ private
172
+
173
+ def self.stringify_hash(hash)
174
+ hash.inject({}) { |h,(k,v)| h[k.to_s] = v.to_s; h }
175
+ end
176
+
177
+ def self.symbolize_hash(hash)
178
+ hash.inject({}) { |h,(k,v)| h[k.to_sym] = v.to_s; h }
179
+ end
180
+
181
+ def self.send_request(options)
182
+ options[:custid] = self.custid
183
+ options[:password] = self.password
184
+ options.update(:demo => 'y') if ENV['RAILS_ENV'] != 'production'
185
+ parameters = stringify_hash(options)
186
+ if tclink? # use TCLink extension if installed
187
+ return symbolize_hash(TCLink.send(parameters))
188
+ else # TCLink library not installed - use https post
189
+ parameters[:password] = self.vault_password.to_s
190
+ response = send_https_request(API_SETTINGS[:trans_path], parameters)
191
+
192
+ # parse response
193
+ results = {}
194
+ response.body.split("\n").each do |line|
195
+ k, v = line.split('=')
196
+ results[k.to_sym] = v
197
+ end
198
+ results
199
+ end
200
+ end
201
+
202
+ def self.send_query(options)
203
+ options[:custid] = self.custid
204
+ options[:password] = self.vault_password.to_s
205
+ response = send_https_request(API_SETTINGS[:query_path], stringify_hash(options))
206
+ end
207
+
208
+ def self.send_https_request(path, parameters)
209
+ http = Net::HTTP.new(API_SETTINGS[:domain], API_SETTINGS[:port])
210
+ http.use_ssl = true
211
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE # avoid ssl cert warning
212
+ request = Net::HTTP::Post.new(path)
213
+ request.form_data = parameters
214
+ http.request(request)
215
+ end
216
+
217
+ end
data/lib/version.rb ADDED
@@ -0,0 +1,9 @@
1
+ class TrustCommerce
2
+ module VERSION #:nodoc:
3
+ MAJOR = '0'
4
+ MINOR = '5'
5
+ TINY = '0'
6
+ end
7
+
8
+ Version = [VERSION::MAJOR, VERSION::MINOR, VERSION::TINY].compact * '.'
9
+ end
@@ -0,0 +1,211 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rdoc/rdoc'
4
+
5
+ module RDoc
6
+ class CodeInfo
7
+ class << self
8
+ def parse(wildcard_pattern = nil)
9
+ @info_for_corpus = parse_files(wildcard_pattern)
10
+ end
11
+
12
+ def for(constant)
13
+ new(constant).info
14
+ end
15
+
16
+ def info_for_corpus
17
+ raise RuntimeError, "You must first generate a corpus to search by using RDoc::CodeInfo.parse" unless @info_for_corpus
18
+ @info_for_corpus
19
+ end
20
+
21
+ def parsed_files
22
+ info_for_corpus.map {|info| info.file_absolute_name}
23
+ end
24
+
25
+ def files_to_parse
26
+ @files_to_parse ||= Rake::FileList.new
27
+ end
28
+
29
+ private
30
+ def parse_files(pattern)
31
+ files = pattern ? Rake::FileList[pattern] : files_to_parse
32
+ options = Options.instance
33
+ options.parse(files << '-q', RDoc::GENERATORS)
34
+ rdoc.send(:parse_files, options)
35
+ end
36
+
37
+ def rdoc
38
+ TopLevel.reset
39
+ rdoc = RDoc.new
40
+ stats = Stats.new
41
+ # We don't want any output so we'll override the print method
42
+ stats.instance_eval { def print; nil end }
43
+ rdoc.instance_variable_set(:@stats, stats)
44
+ rdoc
45
+ end
46
+ end
47
+
48
+ attr_reader :info
49
+ def initialize(location)
50
+ @location = CodeLocation.new(location)
51
+ find_constant
52
+ find_method if @location.has_method?
53
+ end
54
+
55
+ private
56
+ attr_reader :location
57
+ attr_writer :info
58
+ def find_constant
59
+ parts = location.namespace_parts
60
+ self.class.info_for_corpus.each do |file_info|
61
+ @info = parts.inject(file_info) do |result, const_part|
62
+ (result.find_module_named(const_part) || result.find_class_named(const_part)) || break
63
+ end
64
+ return if info
65
+ end
66
+ end
67
+
68
+ def find_method
69
+ return unless info
70
+ self.info = info.method_list.detect do |method_info|
71
+ next unless method_info.name == location.method_name
72
+ if location.class_method?
73
+ method_info.singleton
74
+ elsif location.instance_method?
75
+ !method_info.singleton
76
+ else
77
+ true
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ class CodeLocation
84
+ attr_reader :location
85
+
86
+ def initialize(location)
87
+ @location = location
88
+ end
89
+
90
+ def parts
91
+ location.split(/::|\.|#/)
92
+ end
93
+
94
+ def namespace_parts
95
+ has_method? ? parts[0...-1] : parts
96
+ end
97
+
98
+ def has_method?
99
+ ('a'..'z').include?(parts.last[0, 1])
100
+ end
101
+
102
+ def instance_method?
103
+ !location['#'].nil?
104
+ end
105
+
106
+ def class_method?
107
+ has_method? && !location[/#|\./]
108
+ end
109
+
110
+ def method_name
111
+ parts.last if has_method?
112
+ end
113
+ end
114
+ end
115
+
116
+ if __FILE__ == $0
117
+ require 'test/unit'
118
+ class CodeInfoTest < Test::Unit::TestCase
119
+ def setup
120
+ RDoc::CodeInfo.parse(__FILE__)
121
+ end
122
+
123
+ def test_constant_lookup
124
+ assert RDoc::CodeInfo.for('RDoc')
125
+
126
+ info = RDoc::CodeInfo.for('RDoc::CodeInfo')
127
+ assert_equal 'CodeInfo', info.name
128
+ end
129
+
130
+ def test_method_lookup
131
+ {'RDoc::CodeInfo.parse' => true,
132
+ 'RDoc::CodeInfo::parse' => true,
133
+ 'RDoc::CodeInfo#parse' => false,
134
+ 'RDoc::CodeInfo.find_method' => true,
135
+ 'RDoc::CodeInfo::find_method' => false,
136
+ 'RDoc::CodeInfo#find_method' => true,
137
+ 'RDoc::CodeInfo#no_such_method' => false,
138
+ 'RDoc::NoSuchConst#foo' => false}.each do |location, result_of_lookup|
139
+ assert_equal result_of_lookup, !RDoc::CodeInfo.for(location).nil?
140
+ end
141
+ end
142
+ end
143
+
144
+ class CodeLocationTest < Test::Unit::TestCase
145
+ def test_parts
146
+ {'Foo' => %w(Foo),
147
+ 'Foo::Bar' => %w(Foo Bar),
148
+ 'Foo::Bar#baz' => %w(Foo Bar baz),
149
+ 'Foo::Bar.baz' => %w(Foo Bar baz),
150
+ 'Foo::Bar::baz' => %w(Foo Bar baz),
151
+ 'Foo::Bar::Baz' => %w(Foo Bar Baz)}.each do |location, parts|
152
+ assert_equal parts, RDoc::CodeLocation.new(location).parts
153
+ end
154
+ end
155
+
156
+ def test_namespace_parts
157
+ {'Foo' => %w(Foo),
158
+ 'Foo::Bar' => %w(Foo Bar),
159
+ 'Foo::Bar#baz' => %w(Foo Bar),
160
+ 'Foo::Bar.baz' => %w(Foo Bar),
161
+ 'Foo::Bar::baz' => %w(Foo Bar),
162
+ 'Foo::Bar::Baz' => %w(Foo Bar Baz)}.each do |location, namespace_parts|
163
+ assert_equal namespace_parts, RDoc::CodeLocation.new(location).namespace_parts
164
+ end
165
+ end
166
+
167
+ def test_has_method?
168
+ {'Foo' => false,
169
+ 'Foo::Bar' => false,
170
+ 'Foo::Bar#baz' => true,
171
+ 'Foo::Bar.baz' => true,
172
+ 'Foo::Bar::baz' => true,
173
+ 'Foo::Bar::Baz' => false}.each do |location, has_method_result|
174
+ assert_equal has_method_result, RDoc::CodeLocation.new(location).has_method?
175
+ end
176
+ end
177
+
178
+ def test_instance_method?
179
+ {'Foo' => false,
180
+ 'Foo::Bar' => false,
181
+ 'Foo::Bar#baz' => true,
182
+ 'Foo::Bar.baz' => false,
183
+ 'Foo::Bar::baz' => false,
184
+ 'Foo::Bar::Baz' => false}.each do |location, is_instance_method|
185
+ assert_equal is_instance_method, RDoc::CodeLocation.new(location).instance_method?
186
+ end
187
+ end
188
+
189
+ def test_class_method?
190
+ {'Foo' => false,
191
+ 'Foo::Bar' => false,
192
+ 'Foo::Bar#baz' => false,
193
+ 'Foo::Bar.baz' => false,
194
+ 'Foo::Bar::baz' => true,
195
+ 'Foo::Bar::Baz' => false}.each do |location, is_class_method|
196
+ assert_equal is_class_method, RDoc::CodeLocation.new(location).class_method?
197
+ end
198
+ end
199
+
200
+ def test_method_name
201
+ {'Foo' => nil,
202
+ 'Foo::Bar' => nil,
203
+ 'Foo::Bar#baz' => 'baz',
204
+ 'Foo::Bar.baz' => 'baz',
205
+ 'Foo::Bar::baz' => 'baz',
206
+ 'Foo::Bar::Baz' => nil}.each do |location, method_name|
207
+ assert_equal method_name, RDoc::CodeLocation.new(location).method_name
208
+ end
209
+ end
210
+ end
211
+ end
@@ -0,0 +1,69 @@
1
+ require 'test/unit'
2
+ require 'rubygems'
3
+ require File.dirname(__FILE__) + '/../lib/trustcommerce'
4
+
5
+ begin
6
+ require 'mocha'
7
+ rescue LoadError
8
+ puts "Mocha required to run tests. `gem install mocha` and try again."
9
+ exit 1
10
+ end
11
+
12
+ TCLINK_LIB_INSTALLED = TrustCommerce.tclink?
13
+
14
+ def via_tclink_and_https
15
+ # test via TCLink
16
+ if !TCLINK_LIB_INSTALLED
17
+ puts 'TCLink library not installed - skipping tests via TCLink'
18
+ else
19
+ TrustCommerce.stubs(:tclink?).returns(true)
20
+ yield
21
+ end
22
+
23
+ # test via https
24
+ TrustCommerce.stubs(:tclink?).returns(false)
25
+ yield
26
+ end
27
+
28
+ def create_subscription!(name)
29
+ response = TrustCommerce::Subscription.create(
30
+ :cc => CARDS[:visa][:cc],
31
+ :exp => CARDS[:visa][:exp],
32
+ :address1 => CARDS[:visa][:address],
33
+ :zip => CARDS[:visa][:zip],
34
+ :avs => 'y',
35
+ :name => name,
36
+ :amount => 1200,
37
+ :cycle => '1m',
38
+ :demo => 'y'
39
+ )
40
+ assert_equal 'approved', response[:status]
41
+ assert response.keys.include?(:billingid)
42
+ response[:billingid]
43
+ end
44
+
45
+ # --- [ TrustCommerce test data ] ---
46
+ # reference: https://vault.trustcommerce.com/downloads/TCDevGuide.html#testdata
47
+ CARDS = {
48
+ :visa => { :cc => '4111111111111111',
49
+ :exp => '0412',
50
+ :cvv => 123,
51
+ :address => '123 Test St.',
52
+ :city => 'Somewhere',
53
+ :state => 'CA',
54
+ :zip => 90001 },
55
+ :mastercard => { :cc => '5411111111111115',
56
+ :exp => '0412',
57
+ :cvv => 777,
58
+ :address => '4000 Main St.',
59
+ :city => 'Anytown',
60
+ :state => 'MA',
61
+ :zip => 85001 },
62
+ :amex => { :cc => '341111111111111',
63
+ :exp => '0412',
64
+ :cvv => 4000,
65
+ :address => '12 Colorado Blvd.',
66
+ :city => 'Elsewhere',
67
+ :state => 'IL',
68
+ :zip => 54321 }
69
+ }
@@ -0,0 +1,149 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ # The following special environment variables must be set up prior to running tests:
4
+ #
5
+ # $ export TC_USERNAME=123456
6
+ # $ export TC_PASSWORD=password
7
+ # $ export TC_VAULT_PASSWORD=password
8
+ #
9
+ # Run tests via rake:
10
+ #
11
+ # $ rake test
12
+ #
13
+ # Run tests via ruby:
14
+ #
15
+ # $ ruby test/trustcommerce_test.rb
16
+ class TrustCommerceSubscriptionTest < Test::Unit::TestCase
17
+
18
+ def setup
19
+ if ENV['TC_USERNAME'].nil? || ENV['TC_PASSWORD'].nil?
20
+ puts 'TC_USERNAME and TC_PASSWORD must be set.'
21
+ puts 'Usage: TC_USERNAME=username TC_PASSWORD=password TC_VAULT_PASSWORD=password ruby test/trustcommerce_test.rb'
22
+ exit 1
23
+ else
24
+ TrustCommerce.custid = ENV['TC_USERNAME']
25
+ TrustCommerce.password = ENV['TC_PASSWORD']
26
+ TrustCommerce.vault_password = ENV['TC_VAULT_PASSWORD'] if ENV['TC_VAULT_PASSWORD']
27
+ end
28
+ end
29
+
30
+ def test_subscription_create
31
+ via_tclink_and_https do
32
+ response = TrustCommerce::Subscription.create(
33
+ :cc => CARDS[:visa][:cc],
34
+ :exp => CARDS[:visa][:exp],
35
+ :address1 => CARDS[:visa][:address],
36
+ :zip => CARDS[:visa][:zip],
37
+ :avs => 'y',
38
+ :name => 'Jennifer Smith - create() test',
39
+ :amount => 1200,
40
+ :cycle => '1m',
41
+ :demo => 'y'
42
+ )
43
+ assert_equal Hash, response.class
44
+ assert_not_nil response[:billingid]
45
+ assert response.keys.include?(:transid)
46
+ assert_equal 'approved', response[:status]
47
+ end
48
+ end
49
+
50
+ def test_subscription_update
51
+ via_tclink_and_https do
52
+ billing_id = create_subscription!('update() test')
53
+ response = TrustCommerce::Subscription.update(
54
+ :billingid => billing_id,
55
+ :cc => CARDS[:mastercard][:cc],
56
+ :exp => CARDS[:mastercard][:exp],
57
+ :address1 => CARDS[:mastercard][:address],
58
+ :zip => CARDS[:mastercard][:zip],
59
+ :avs => 'y'
60
+ )
61
+ assert_equal 'accepted', response[:status]
62
+ end
63
+ end
64
+
65
+ def test_subscription_delete
66
+ via_tclink_and_https do
67
+ billing_id = create_subscription!('delete() test')
68
+ response = TrustCommerce::Subscription.delete(
69
+ :billingid => billing_id
70
+ )
71
+ assert response.keys.include?(:transid)
72
+ assert_equal 'accepted', response[:status]
73
+ end
74
+ end
75
+
76
+ def test_subscription_charge_and_credit
77
+ via_tclink_and_https do
78
+ billing_id = create_subscription!('charge and credit() test')
79
+
80
+ # charge
81
+ charge_response = TrustCommerce::Subscription.charge(
82
+ :billingid => billing_id,
83
+ :amount => 1995,
84
+ :demo => 'y'
85
+ )
86
+ assert charge_response.keys.include?(:transid)
87
+ assert_equal 'approved', charge_response[:status]
88
+
89
+ # credit
90
+ credit_response = TrustCommerce::Subscription.credit(
91
+ :transid => charge_response[:transid],
92
+ :amount => 995,
93
+ :demo => 'y'
94
+ )
95
+ assert credit_response.keys.include?(:transid)
96
+ assert_equal 'accepted', credit_response[:status]
97
+ end
98
+ end
99
+
100
+ def test_subscription_query
101
+ puts "\n"
102
+ puts "---------------------------------------------------------------------------"
103
+ puts "IMPORTANT: This query test will likely take between 1 and 2 minutes!"
104
+ puts "Make sure TC_VAULT_PASSWORD is set if it differs from your TCLink password."
105
+ puts "---------------------------------------------------------------------------"
106
+
107
+ # create subscription
108
+ billing_id = create_subscription!('query() test')
109
+
110
+ # query for charges
111
+ options = { :querytype => 'transaction', :action => 'sale', :billingid => billing_id }
112
+ while (query_response = TrustCommerce::Subscription.query(options))
113
+ if query_response.body =~ /error/i
114
+ fail(query_response.body)
115
+ break
116
+ elsif query_response.body.split("\n").size < 2
117
+ puts 'Transaction has not yet showed up... will try again in 15 seconds.'
118
+ sleep(15)
119
+ else
120
+ puts 'Transaction found.'
121
+
122
+ # setup index hash
123
+ field_names = query_response.body.split("\n")[0].split(',')
124
+ date_line_1 = query_response.body.split("\n")[1].split(',')
125
+ indexes = field_names.inject({}) {|h, field| h[field.to_sym] = field_names.index(field); h }
126
+
127
+ # check transaction data
128
+ assert_equal '1111', date_line_1[indexes[:cc]]
129
+ assert_equal '1200', date_line_1[indexes[:amount]]
130
+ assert_equal 'query() test', date_line_1[indexes[:name]]
131
+ break
132
+ end
133
+ end
134
+ end
135
+
136
+ # test private helpers
137
+ def test_stringify_hash
138
+ assert_equal ({ 'a' => '1', 'b' => '2' }), TrustCommerce.stringify_hash(:a => '1', :b => '2')
139
+ assert_equal ({ 'a' => '1', 'b' => '2' }), TrustCommerce.stringify_hash(:a => 1, :b => 2)
140
+ assert_equal ({ 'a' => '2' }), TrustCommerce.stringify_hash(:a => 1, :a => 2)
141
+ end
142
+
143
+ def test_symbolize_hash
144
+ assert_equal ({ :a => '1', :b => '2' }), TrustCommerce.symbolize_hash('a' => '1', 'b' => '2')
145
+ assert_equal ({ :a => '1', :b => '2' }), TrustCommerce.symbolize_hash(:a => 1, 'b' => 2)
146
+ assert_equal ({ :a => '1' }), TrustCommerce.symbolize_hash(:a => 1, 'a' => 2)
147
+ end
148
+
149
+ end
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.2
3
+ specification_version: 1
4
+ name: trustcommerce
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.5.0
7
+ date: 2007-02-28 00:00:00 -08:00
8
+ summary: TrustCommerce Subscription Library
9
+ require_paths:
10
+ - lib
11
+ email: zackchandler@depixelate.com
12
+ homepage: http://trustcommerce.rubyforge.org
13
+ rubyforge_project: trustcommerce
14
+ description: TrustCommerce Subscription Library
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Zack Chandler
31
+ files:
32
+ - Rakefile
33
+ - lib/trustcommerce.rb
34
+ - lib/version.rb
35
+ - support/rdoc/code_info.rb
36
+ - README
37
+ - MIT-LICENSE
38
+ test_files:
39
+ - test/test_helper.rb
40
+ - test/trustcommerce_test.rb
41
+ rdoc_options:
42
+ - --title
43
+ - TrustCommerce Subscription Library
44
+ - --main
45
+ - README
46
+ - --line-numbers
47
+ - --inline-source
48
+ extra_rdoc_files:
49
+ - README
50
+ - MIT-LICENSE
51
+ executables: []
52
+
53
+ extensions: []
54
+
55
+ requirements: []
56
+
57
+ dependencies: []
58
+