giact 0.0.1
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/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.md +45 -0
- data/Rakefile +60 -0
- data/VERSION +1 -0
- data/lib/giact.rb +106 -0
- data/lib/giact/cancel_recurring_check_list.rb +17 -0
- data/lib/giact/client.rb +136 -0
- data/lib/giact/installment_payment_request.rb +20 -0
- data/lib/giact/payment_reply.rb +22 -0
- data/lib/giact/payment_request.rb +44 -0
- data/lib/giact/recurring_check_list.rb +29 -0
- data/lib/giact/recurring_payment_request.rb +26 -0
- data/lib/giact/transaction_result.rb +113 -0
- data/test/fixtures/cancel_recurring_check_list.xml +4 -0
- data/test/fixtures/invalid_routing_number.xml +2 -0
- data/test/fixtures/login.xml +2 -0
- data/test/fixtures/login_failed.txt +1 -0
- data/test/fixtures/logout.xml +2 -0
- data/test/fixtures/payment_pass.xml +2 -0
- data/test/fixtures/recurring_check_list.xml +4 -0
- data/test/fixtures/transaction_search.xml +6 -0
- data/test/helper.rb +44 -0
- data/test/test_giact.rb +438 -0
- metadata +160 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Wynn Netherland
|
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.md
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# Giact
|
2
|
+
|
3
|
+
If you have to use the Giact recurring eCheck POST API my heart goes out to you. Here's a Ruby wrapper to ease your burden.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
sudo gem install giact
|
8
|
+
|
9
|
+
## TODO
|
10
|
+
|
11
|
+
API calls left to implement:
|
12
|
+
|
13
|
+
* list installment checks by order ID
|
14
|
+
* list installment checks by transaction ID
|
15
|
+
* cancel future installment checks by order ID
|
16
|
+
* cancel future installment checks by transaction ID
|
17
|
+
* cancel a transaction by order ID that has not been batched
|
18
|
+
* cancel a transaction by transaction ID that has not been batched
|
19
|
+
* request a refund by order ID
|
20
|
+
* request a refund by transaction ID
|
21
|
+
* request a partial refund by order ID
|
22
|
+
* request a partial refund by transaction ID
|
23
|
+
* return a daily summary
|
24
|
+
* list daily deposits for a given date
|
25
|
+
* list checks returned for a given date
|
26
|
+
* list checks refunded for a given date
|
27
|
+
* list transactions scrubbed for a given date
|
28
|
+
* list errored transactions for a given date
|
29
|
+
* list checks returned for a given date range
|
30
|
+
* list checks refunded for a given date range
|
31
|
+
|
32
|
+
## Note on Patches/Pull Requests
|
33
|
+
|
34
|
+
* Fork the project.
|
35
|
+
* Make your feature addition or bug fix.
|
36
|
+
* Add tests for it. This is important so I don't break it in a
|
37
|
+
future version unintentionally.
|
38
|
+
* Commit, do not mess with rakefile, version, or history.
|
39
|
+
(if you want to have your own version, that is fine but
|
40
|
+
bump version in a commit by itself I can ignore when I pull)
|
41
|
+
* Send me a pull request. Bonus points for topic branches.
|
42
|
+
|
43
|
+
## Copyright
|
44
|
+
|
45
|
+
Copyright (c) 2010 [Wynn Netherland](http://wynnnetherland.com). See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "giact"
|
8
|
+
gem.summary = %Q{Ruby wrapper for the Giact real time checks API}
|
9
|
+
gem.description = %Q{Ruby wrapper for the Giact real time checks API}
|
10
|
+
gem.email = "wynn.netherland@gmail.com"
|
11
|
+
gem.homepage = "http://github.com/pengwynn/giact"
|
12
|
+
gem.authors = ["Wynn Netherland"]
|
13
|
+
gem.add_dependency('hashie', '~> 0.1.3')
|
14
|
+
gem.add_dependency('httparty', '~> 0.4.5')
|
15
|
+
gem.add_dependency('activesupport', '>= 2.3')
|
16
|
+
|
17
|
+
gem.add_development_dependency('thoughtbot-shoulda', '>= 2.10.1')
|
18
|
+
gem.add_development_dependency('jnunemaker-matchy', '0.4.0')
|
19
|
+
gem.add_development_dependency('mocha', '0.9.4')
|
20
|
+
gem.add_development_dependency('fakeweb', '>= 1.2.5')
|
21
|
+
gem.add_development_dependency "yard", ">= 0"
|
22
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
23
|
+
end
|
24
|
+
Jeweler::GemcutterTasks.new
|
25
|
+
rescue LoadError
|
26
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
27
|
+
end
|
28
|
+
|
29
|
+
require 'rake/testtask'
|
30
|
+
Rake::TestTask.new(:test) do |test|
|
31
|
+
test.libs << 'lib' << 'test'
|
32
|
+
test.pattern = 'test/**/test_*.rb'
|
33
|
+
test.verbose = true
|
34
|
+
end
|
35
|
+
|
36
|
+
begin
|
37
|
+
require 'rcov/rcovtask'
|
38
|
+
Rcov::RcovTask.new do |test|
|
39
|
+
test.libs << 'test'
|
40
|
+
test.pattern = 'test/**/test_*.rb'
|
41
|
+
test.verbose = true
|
42
|
+
end
|
43
|
+
rescue LoadError
|
44
|
+
task :rcov do
|
45
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
task :test => :check_dependencies
|
50
|
+
|
51
|
+
task :default => :test
|
52
|
+
|
53
|
+
begin
|
54
|
+
require 'yard'
|
55
|
+
YARD::Rake::YardocTask.new
|
56
|
+
rescue LoadError
|
57
|
+
task :yardoc do
|
58
|
+
abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
|
59
|
+
end
|
60
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
data/lib/giact.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'time'
|
3
|
+
gem 'activesupport', '~> 2.3.2'
|
4
|
+
require 'activesupport'
|
5
|
+
|
6
|
+
gem 'hashie', '~> 0.1.3'
|
7
|
+
require 'hashie'
|
8
|
+
|
9
|
+
gem 'httparty', '>= 0.5.0'
|
10
|
+
require 'httparty'
|
11
|
+
|
12
|
+
gem 'validatable', '~> 1.6.7'
|
13
|
+
require 'validatable'
|
14
|
+
|
15
|
+
|
16
|
+
directory = File.expand_path(File.dirname(__FILE__))
|
17
|
+
|
18
|
+
Hash.send :include, Hashie::HashExtensions
|
19
|
+
|
20
|
+
class Hash
|
21
|
+
|
22
|
+
# Converts all of the keys to strings, optionally formatting key name
|
23
|
+
def camelize_keys!
|
24
|
+
keys.each{|k|
|
25
|
+
v = delete(k)
|
26
|
+
new_key = k.to_s.camelize.gsub(/Id/, "ID")
|
27
|
+
self[new_key] = v
|
28
|
+
v.camelize_keys! if v.is_a?(Hash)
|
29
|
+
v.each{|p| p.camelize_keys! if p.is_a?(Hash)} if v.is_a?(Array)
|
30
|
+
}
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
module Giact
|
37
|
+
|
38
|
+
class GiactError < StandardError
|
39
|
+
attr_reader :errors
|
40
|
+
|
41
|
+
def initialize(errors)
|
42
|
+
@errors = errors
|
43
|
+
super
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class Unauthorized < StandardError; end
|
48
|
+
class InvalidRequest < GiactError; end
|
49
|
+
|
50
|
+
# config/initializers/giact.rb (for instance)
|
51
|
+
#
|
52
|
+
# Giact.configure do |config|
|
53
|
+
# config.company_id = 'your_company_id'
|
54
|
+
# config.username = 'your_api_username'
|
55
|
+
# config.password = 'your_api_password'
|
56
|
+
# end
|
57
|
+
def self.configure
|
58
|
+
yield self
|
59
|
+
|
60
|
+
Giact.company_id = company_id
|
61
|
+
Giact.username = username
|
62
|
+
Giact.password = password
|
63
|
+
true
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.company_id
|
67
|
+
@company_id
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.company_id=(company_id)
|
71
|
+
@company_id = company_id
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.username
|
75
|
+
@username
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.username=(username)
|
79
|
+
@username = username
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.password
|
83
|
+
@password
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.password=(password)
|
87
|
+
@password = password
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.test_mode?
|
91
|
+
@test_mode || false
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.test_mode=(test_mode)
|
95
|
+
@test_mode = test_mode
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
require File.join(directory, 'giact', 'cancel_recurring_check_list')
|
100
|
+
require File.join(directory, 'giact', 'recurring_check_list')
|
101
|
+
require File.join(directory, 'giact', 'transaction_result')
|
102
|
+
require File.join(directory, 'giact', 'payment_reply')
|
103
|
+
require File.join(directory, 'giact', 'payment_request')
|
104
|
+
require File.join(directory, 'giact', 'installment_payment_request')
|
105
|
+
require File.join(directory, 'giact', 'recurring_payment_request')
|
106
|
+
require File.join(directory, 'giact', 'client')
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Giact
|
2
|
+
class CancelRecurringCheckList
|
3
|
+
attr_accessor :cancelled, :details
|
4
|
+
|
5
|
+
def initialize(row="")
|
6
|
+
@cancelled, @details = row.split("|")
|
7
|
+
end
|
8
|
+
|
9
|
+
def cancelled?
|
10
|
+
@cancelled.downcase == 'true'
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.from_response(response)
|
14
|
+
response.split(/\n/).map{|line| Giact::CancelRecurringCheckList.new(line)}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/giact/client.rb
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
module Giact
|
2
|
+
class Client
|
3
|
+
include HTTParty
|
4
|
+
format :xml
|
5
|
+
|
6
|
+
attr_reader :company_id
|
7
|
+
|
8
|
+
# @param [Hash] options method options
|
9
|
+
# @option options [Integer] :gateway Number (1-3) of gateway server to use
|
10
|
+
# @option options [String] :company_id Your Giact company ID
|
11
|
+
# @option options [String] :username Your Giact API username
|
12
|
+
# @option options [String] :password Your Giact API password
|
13
|
+
def initialize(options={})
|
14
|
+
options[:gateway] ||= 1
|
15
|
+
self.class.base_uri("https://gatewaydtx#{options[:gateway]}.giact.com/RealTime/POST/RealTimeChecks.asmx")
|
16
|
+
|
17
|
+
@company_id ||= Giact.company_id ||= options[:company_id]
|
18
|
+
@username ||= Giact.username ||= options[:username]
|
19
|
+
@password ||= Giact.password ||= options[:password]
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
# Return an authentication token
|
24
|
+
#
|
25
|
+
# @return [String] authentication token
|
26
|
+
def login
|
27
|
+
response = self.class.post("/Login", :body => {:companyID => @company_id, :un => @username, :pw => @password})
|
28
|
+
if auth_token = response['string']
|
29
|
+
@token = auth_token
|
30
|
+
else
|
31
|
+
raise Giact::Unauthorized.new(response.body)
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns current authentication token or calls login
|
37
|
+
#
|
38
|
+
# @return [String] authentication token
|
39
|
+
def token
|
40
|
+
@token ||= self.login
|
41
|
+
end
|
42
|
+
|
43
|
+
# Process a single payment
|
44
|
+
#
|
45
|
+
# @param [Giact::PaymentRequest] payment_request Customer and payment info to be processed
|
46
|
+
# @return [Giact::PaymentReply] Transaction information
|
47
|
+
def single_payment(payment_request)
|
48
|
+
payment_request.merge!(:company_id => self.company_id, :token => self.token)
|
49
|
+
raise Giact::InvalidRequest.new(payment_request.errors.full_messages) unless payment_request.valid?
|
50
|
+
|
51
|
+
response = self.class.post("/SinglePayment", :body => payment_request.to_request_hash)
|
52
|
+
if reply = response['string']
|
53
|
+
Giact::PaymentReply.new(reply)
|
54
|
+
else
|
55
|
+
raise response.body
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Process recurring payments
|
60
|
+
#
|
61
|
+
# @param [Giact::RecurringPaymentRequest] payment_request Customer and payment info to be processed
|
62
|
+
# @return [Giact::PaymentReply] Transaction information
|
63
|
+
def recurring_payments(payment_request)
|
64
|
+
payment_request.merge!(:company_id => self.company_id, :token => self.token)
|
65
|
+
raise Giact::InvalidRequest.new(payment_request.errors.full_messages) if not payment_request.is_a?(Giact::RecurringPaymentRequest) or not payment_request.valid?
|
66
|
+
|
67
|
+
response = self.class.post("/RecurringPayments", :body => payment_request.to_request_hash)
|
68
|
+
if reply = response['string']
|
69
|
+
Giact::PaymentReply.new(reply)
|
70
|
+
else
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Process installment payments
|
76
|
+
#
|
77
|
+
# @param [Giact::InstallmentPaymentRequest] payment_request Customer and payment info to be processed
|
78
|
+
# @return [Giact::PaymentReply] Transaction information
|
79
|
+
def installments_payments(payment_request)
|
80
|
+
payment_request.merge!(:company_id => self.company_id, :token => self.token)
|
81
|
+
raise Giact::InvalidRequest.new(payment_request.errors.full_messages) if not payment_request.is_a?(Giact::InstallmentPaymentRequest) or not payment_request.valid?
|
82
|
+
|
83
|
+
response = self.class.post("/InstallmentsPayments", :body => payment_request.to_request_hash)
|
84
|
+
if reply = response['string']
|
85
|
+
Giact::PaymentReply.new(reply)
|
86
|
+
else
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# @param [Hash] options method options
|
92
|
+
# @option options [String] :transaction_id Transaction ID to list checks
|
93
|
+
# @option options [String] :order_id Order ID to list checks
|
94
|
+
def recurring_checks(options={})
|
95
|
+
|
96
|
+
path = "/RecurringChecksBy#{options.keys.first.to_s.camelize.gsub(/Id$/, "ID")}"
|
97
|
+
options.merge!(:company_id => self.company_id, :token => self.token)
|
98
|
+
options.camelize_keys!
|
99
|
+
|
100
|
+
response = self.class.post(path, :body => options)['string']
|
101
|
+
Giact::RecurringCheckList.from_response(response)
|
102
|
+
end
|
103
|
+
|
104
|
+
# @param [Hash] options method options
|
105
|
+
# @option options [String] :transaction_id Transaction ID for which to cancel checks
|
106
|
+
# @option options [String] :order_id Order ID for which to cancel checks
|
107
|
+
def cancel_recurring(options={})
|
108
|
+
|
109
|
+
path = "/CancelRecurringBy#{options.keys.first.to_s.camelize.gsub(/Id$/, "ID")}"
|
110
|
+
options.merge!(:company_id => self.company_id, :token => self.token)
|
111
|
+
options.camelize_keys!
|
112
|
+
|
113
|
+
response = self.class.post(path, :body => options)['string']
|
114
|
+
Giact::CancelRecurringCheckList.from_response(response)
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
# @param [Hash] options method options
|
119
|
+
# @option options [String] :name_on_check Name on check to search
|
120
|
+
# @option options [String] :customer_id Customer ID to search
|
121
|
+
# @option options [String] :order_id Order ID to search
|
122
|
+
def search_transactions(options={})
|
123
|
+
|
124
|
+
path = "/TransactionsBy#{options.keys.first.to_s.camelize.gsub(/Id$/, "ID")}"
|
125
|
+
options.merge!(:company_id => self.company_id, :token => self.token)
|
126
|
+
options.camelize_keys!
|
127
|
+
|
128
|
+
response = self.class.post(path, :body => options)['string']
|
129
|
+
Giact::TransactionResult.from_response(response)
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
|
134
|
+
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Giact
|
2
|
+
class InstallmentPaymentRequest < PaymentRequest
|
3
|
+
|
4
|
+
validates_each :installment_amount, :logic => lambda { errors.add(:installment_amount, "must be greater than zero") if installment_amount.to_f == 0.0 }
|
5
|
+
|
6
|
+
|
7
|
+
def installment_count
|
8
|
+
self[:installment_amount].to_i
|
9
|
+
end
|
10
|
+
|
11
|
+
def installments_start_date
|
12
|
+
if self[:installments_start_date].respond_to?(:year)
|
13
|
+
self[:installments_start_date].strftime("%m/%d/%Y")
|
14
|
+
else
|
15
|
+
self[:installments_start_date]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Giact
|
2
|
+
class PaymentReply
|
3
|
+
attr_reader :transaction_id, :error, :pass, :details, :code
|
4
|
+
|
5
|
+
def initialize(response)
|
6
|
+
@transaction_id, @error, @pass, @details, @code = response.split("|")
|
7
|
+
end
|
8
|
+
|
9
|
+
def transaction_id
|
10
|
+
@transaction_id.to_i == 0 ? nil : @transaction_id.to_i
|
11
|
+
end
|
12
|
+
|
13
|
+
def error?
|
14
|
+
@error.to_s == 'true'
|
15
|
+
end
|
16
|
+
|
17
|
+
def pass?
|
18
|
+
@pass.to_s == 'true'
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|