trustcommerce 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README +147 -0
- data/Rakefile +198 -0
- data/lib/trustcommerce.rb +217 -0
- data/lib/version.rb +9 -0
- data/support/rdoc/code_info.rb +211 -0
- data/test/test_helper.rb +69 -0
- data/test/trustcommerce_test.rb +149 -0
- metadata +58 -0
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,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
|
data/test/test_helper.rb
ADDED
@@ -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
|
+
|