ipay 0.1.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/README.md +76 -0
- data/Rakefile +13 -0
- data/example/certification.rb +31 -0
- data/lib/console.rb +2 -0
- data/lib/ipay.rb +42 -0
- data/lib/ipay/api_request.rb +43 -0
- data/lib/ipay/cc.rb +49 -0
- data/lib/ipay/certification.rb +35 -0
- data/lib/ipay/config.rb +35 -0
- data/lib/ipay/countries.rb +7 -0
- data/lib/ipay/log.rb +28 -0
- data/lib/ipay/network.rb +13 -0
- data/lib/ipay/response.rb +67 -0
- data/lib/ipay/util.rb +29 -0
- data/lib/ipay/version.rb +3 -0
- data/lib/ipay/wallet.rb +44 -0
- data/lib/ipay/xml_request.rb +69 -0
- data/test/helper.rb +13 -0
- data/test/test_cc.rb +126 -0
- data/test/test_errors.rb +47 -0
- data/test/test_wallet.rb +199 -0
- metadata +86 -0
data/README.md
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
iPay
|
2
|
+
======
|
3
|
+
|
4
|
+
Ruby gem for interfacing with the iPay XML API
|
5
|
+
|
6
|
+
Changelog
|
7
|
+
---------
|
8
|
+
|
9
|
+
**v0.1.1**
|
10
|
+
|
11
|
+
- Added certification mode
|
12
|
+
|
13
|
+
**v0.1.0**
|
14
|
+
|
15
|
+
- CC (credit/debit) and Wallet (client/account) requests
|
16
|
+
- Tests
|
17
|
+
|
18
|
+
**v0.0.1**
|
19
|
+
|
20
|
+
- Initial commit
|
21
|
+
|
22
|
+
Dependencies
|
23
|
+
----
|
24
|
+
|
25
|
+
- libxml-ruby for processing xml responses
|
26
|
+
|
27
|
+
Setup
|
28
|
+
-----
|
29
|
+
|
30
|
+
Create a configuration file 'ipay.yml' and place it in your apps config/ folder. The configuration file is automatically loaded for you and values are accessible via iPay::config struct.
|
31
|
+
|
32
|
+
Example configuration:
|
33
|
+
|
34
|
+
:url: "https://uap.txngw.com/"
|
35
|
+
:company_key: 6990
|
36
|
+
:terminal_id: 6177
|
37
|
+
:pin: 1234
|
38
|
+
|
39
|
+
You can also configure IPay via a block:
|
40
|
+
|
41
|
+
IPay::config do |c|
|
42
|
+
c.url = 'https://uap.txngw.com/'
|
43
|
+
c.company_key = 6990
|
44
|
+
c.terminal_id = 6177
|
45
|
+
c.pin = 1234
|
46
|
+
end
|
47
|
+
|
48
|
+
Usage
|
49
|
+
----
|
50
|
+
require 'ipay'
|
51
|
+
resp = IPay::CC::Debit.sale(
|
52
|
+
:amount => '4.99',
|
53
|
+
:account_number => '4000009999999991',
|
54
|
+
:cvv => 123,
|
55
|
+
:expiration => '0614',
|
56
|
+
:first_name => 'nick',
|
57
|
+
:last_name => 'wilson',
|
58
|
+
:address => '123 fake st',
|
59
|
+
:city => 'sometown',
|
60
|
+
:state => 'NY',
|
61
|
+
:postal_code => '90210',
|
62
|
+
:country => IPay::Countries::USA
|
63
|
+
)
|
64
|
+
|
65
|
+
if resp.success?
|
66
|
+
puts resp.data[:transaction_id]
|
67
|
+
end
|
68
|
+
|
69
|
+
Certification
|
70
|
+
----
|
71
|
+
|
72
|
+
IPay requires that test accounts submit an xml file of compiled responses before allowing an account to be used in production mode. The IPay gem has a certification mode that will compile all responses into the appropriate file/format for you automatically:
|
73
|
+
|
74
|
+
IPay::Certification.capture do
|
75
|
+
# ... all responses for api requests are now logged and saved when the block ends
|
76
|
+
end
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'rake/testtask'
|
2
|
+
|
3
|
+
task :default => [:test]
|
4
|
+
|
5
|
+
task :console do
|
6
|
+
sh 'irb --simple-prompt -rubygems -I lib -r console.rb'
|
7
|
+
end
|
8
|
+
|
9
|
+
Rake::TestTask.new('test') do |t|
|
10
|
+
t.libs << 'test'
|
11
|
+
t.test_files = FileList['test/test*.rb']
|
12
|
+
t.verbose = true
|
13
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$:.unshift(File.join(File.dirname(__FILE__), 'lib'))
|
3
|
+
|
4
|
+
require 'ipay'
|
5
|
+
require 'pp'
|
6
|
+
|
7
|
+
CC_EXP = "#{Date.today.month.to_s.rjust(2,'0')}#{Date.today.year.to_s[2..-1]}"
|
8
|
+
|
9
|
+
IPay::log = STDOUT
|
10
|
+
|
11
|
+
IPay::config do |c|
|
12
|
+
c.url = 'https://uap.txngw.com/'
|
13
|
+
c.company_key = 6990
|
14
|
+
c.terminal_id = 6177
|
15
|
+
c.pin = 1234
|
16
|
+
end
|
17
|
+
|
18
|
+
IPay::Certification.capture do
|
19
|
+
|
20
|
+
resp = IPay::CC::Debit.sale(
|
21
|
+
:amount => '4.99',
|
22
|
+
:account_number => '4000009999999991', :cvv => 123, :expiration => CC_EXP,
|
23
|
+
:first_name => 'nick', :last_name => 'wilson', :address => '123 fake st', :city => 'sometown', :state => 'NY', :postal_code => '90210', :country => IPay::Countries::USA
|
24
|
+
)
|
25
|
+
|
26
|
+
resp = IPay::CC::Debit.sale(
|
27
|
+
:amount => '59.99',
|
28
|
+
:account_number => '4000009999999991', :cvv => 123, :expiration => CC_EXP,
|
29
|
+
:first_name => 'nick', :last_name => 'wilson', :address => '123 fake st', :city => 'sometown', :state => 'NY', :postal_code => '90210', :country => IPay::Countries::USA
|
30
|
+
)
|
31
|
+
end
|
data/lib/console.rb
ADDED
data/lib/ipay.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
module IPay
|
2
|
+
ENV = ENV['RAILS_ENV'] || ENV['IPAY_ENV'] || 'development'
|
3
|
+
ROOT = ENV['RAILS_ROOT'] || ENV['IPAY_ROOT'] || '.'
|
4
|
+
CONFIG_NAME = 'ipay.yml'
|
5
|
+
LOG_NAME = 'ipay.log'
|
6
|
+
|
7
|
+
EM_SWIPED = 1
|
8
|
+
EM_MANUAL_PRESENT = 2
|
9
|
+
EM_MANUAL_NOT_PRESENT = 3
|
10
|
+
|
11
|
+
GOODS_DIGITAL = 'D'
|
12
|
+
GOODS_PHYSICAL = 'P'
|
13
|
+
|
14
|
+
CUR_DOMESTIC = 0
|
15
|
+
CUR_MCP = 1
|
16
|
+
CUR_PYC = 2
|
17
|
+
|
18
|
+
TXN_VIA_MAIL = 'M'
|
19
|
+
TXN_VIA_POS = 'P'
|
20
|
+
TXN_VIA_PHONE = 'T'
|
21
|
+
TXN_VIA_RECUR = 2
|
22
|
+
TXN_AUTHENTICATED = 5
|
23
|
+
TXN_AUTH_FAILED = 6
|
24
|
+
TXN_VIA_HTTPS = 7
|
25
|
+
|
26
|
+
ApiError = Class.new(RuntimeError)
|
27
|
+
RequestError = Class.new(ApiError)
|
28
|
+
RequestTimeout = Class.new(RequestError)
|
29
|
+
ResponseError = Class.new(ApiError)
|
30
|
+
|
31
|
+
|
32
|
+
autoload :CC, 'ipay/cc'
|
33
|
+
autoload :Wallet, 'ipay/wallet'
|
34
|
+
autoload :Network, 'ipay/network'
|
35
|
+
|
36
|
+
autoload :Certification, 'ipay/certification'
|
37
|
+
end
|
38
|
+
|
39
|
+
$:.unshift(File.dirname(__FILE__))
|
40
|
+
%w[ version config log countries ].each do |file|
|
41
|
+
require "ipay/#{file}"
|
42
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'net/https'
|
2
|
+
require 'uri'
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
require 'ipay/xml_request'
|
6
|
+
require 'ipay/response'
|
7
|
+
|
8
|
+
module IPay
|
9
|
+
class ApiRequest
|
10
|
+
|
11
|
+
DEFAULT_SERVICE_FORMAT = '0000'
|
12
|
+
|
13
|
+
def self.service_format
|
14
|
+
@@service_format ||= DEFAULT_SERVICE_FORMAT
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.service_format=(val)
|
18
|
+
@@service_format = val
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.service
|
22
|
+
self.name.split('::')[1] rescue nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.service_type
|
26
|
+
self.name.split('::')[2] rescue nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.send_request(data = {}, service_subtype = nil)
|
30
|
+
data[:service] = self.service.upcase
|
31
|
+
data[:service_type] = self.service_type.upcase
|
32
|
+
data[:service_subtype] = service_subtype.nil? ? caller[0][/`.*'/][1..-2].upcase : service_subtype.to_s.upcase
|
33
|
+
data[:service_format] ||= self.service_format
|
34
|
+
|
35
|
+
m = eval("#{self.service}")
|
36
|
+
data = m::default_values(data) if m::respond_to?(:default_values)
|
37
|
+
|
38
|
+
request = XmlRequest.new(data)
|
39
|
+
Response.new request.send
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
data/lib/ipay/cc.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'ipay/api_request'
|
2
|
+
|
3
|
+
module IPay
|
4
|
+
module CC
|
5
|
+
|
6
|
+
def self.default_values(data)
|
7
|
+
data[:currency_code] ||= 840 # USD
|
8
|
+
data[:currency_indicator] ||= CUR_DOMESTIC
|
9
|
+
data[:transaction_indicator] ||= TXN_VIA_HTTPS
|
10
|
+
data
|
11
|
+
end
|
12
|
+
|
13
|
+
class Credit < ApiRequest
|
14
|
+
self.service_format = '1010'
|
15
|
+
|
16
|
+
def self.refund(data)
|
17
|
+
self.send_request(data)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.void(data)
|
21
|
+
self.send_request(data)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Debit < ApiRequest
|
26
|
+
self.service_format = '1010'
|
27
|
+
|
28
|
+
def self.auth(data)
|
29
|
+
data[:goods_indicator] ||= GOODS_DIGITAL
|
30
|
+
self.send_request(data)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.capture(data)
|
34
|
+
self.send_request(data)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.sale(data)
|
38
|
+
data[:goods_indicator] ||= GOODS_DIGITAL
|
39
|
+
data[:entry_mode] ||= EM_MANUAL_NOT_PRESENT
|
40
|
+
self.send_request(data)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.void(data)
|
44
|
+
self.send_request(data)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module IPay
|
4
|
+
module Certification
|
5
|
+
|
6
|
+
def self.log(parsed_xml)
|
7
|
+
@@responses ||= ''
|
8
|
+
@@responses << parsed_xml.to_s.split("\n")[2...-1].join("\n") + "\n"
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.capture(output_path = './')
|
12
|
+
IPay::config do |c|
|
13
|
+
c.certification = true
|
14
|
+
end
|
15
|
+
|
16
|
+
yield
|
17
|
+
|
18
|
+
self.save(output_path)
|
19
|
+
|
20
|
+
IPay::config do |c|
|
21
|
+
c.certification = false
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.save(path)
|
26
|
+
raise 'Certification mode is not activated' unless IPay::config.certification
|
27
|
+
|
28
|
+
file_name = File.join(path, "#{IPay::config.operator}_#{Date.today.to_s.split('-').join}#{Random.new.rand(1..99).to_s.rjust(2, '0')}.xml")
|
29
|
+
file = File.open(file_name, 'w')
|
30
|
+
raise "Failed to open certification file '#{file_name}' for writing" unless file
|
31
|
+
file.write("<RESPONSES>\n#{@@responses}</RESPONSES>")
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
data/lib/ipay/config.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
module IPay
|
5
|
+
|
6
|
+
def self.config_file
|
7
|
+
File.expand_path(File.join(ROOT, 'config', CONFIG_NAME))
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.load_config_file
|
11
|
+
path = File.expand_path(config_file)
|
12
|
+
|
13
|
+
if File.readable?(path)
|
14
|
+
IPay::log.debug "Using configuration file '#{path}'"
|
15
|
+
@config = OpenStruct.new(YAML.load_file(path)) unless @config
|
16
|
+
else
|
17
|
+
@config = OpenStruct.new
|
18
|
+
IPay::log.warn "Failed to locate configuration file '#{CONFIG_NAME}', place in 'config/'"
|
19
|
+
end
|
20
|
+
|
21
|
+
set_defaults(@config)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.set_defaults(config)
|
25
|
+
config.dry_run ||= false
|
26
|
+
config.certification ||= false
|
27
|
+
config
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.config
|
31
|
+
@config ||= load_config_file
|
32
|
+
yield @config if block_given?
|
33
|
+
@config
|
34
|
+
end
|
35
|
+
end
|
data/lib/ipay/log.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'logger'
|
2
|
+
module IPay
|
3
|
+
class Log < Logger
|
4
|
+
def format_message(severity, timestamp, progname, msg)
|
5
|
+
"#{timestamp} #{severity} -- #{msg}\n"
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.log_file
|
10
|
+
path = File.join(ROOT, 'log')
|
11
|
+
path = '.' unless File.directory?(path)
|
12
|
+
File.expand_path(File.join(path, LOG_NAME))
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.init_log(log = nil)
|
16
|
+
@log = Log.new(log||log_file)
|
17
|
+
@log.level = Logger::WARN if ENV == 'production'
|
18
|
+
@log
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.log
|
22
|
+
@log ||= init_log
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.log=(log)
|
26
|
+
init_log log
|
27
|
+
end
|
28
|
+
end
|
data/lib/ipay/network.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'xml'
|
2
|
+
require 'date'
|
3
|
+
require 'ipay/util'
|
4
|
+
|
5
|
+
module IPay
|
6
|
+
class Response
|
7
|
+
|
8
|
+
PARSER_OPT = XML::Parser::Options::NOBLANKS | XML::Parser::Options::NOERROR | XML::Parser::Options::RECOVER | XML::Parser::Options::NOWARNING
|
9
|
+
|
10
|
+
attr_reader :status, :server_time, :data, :raw_xml
|
11
|
+
|
12
|
+
def initialize(xml)
|
13
|
+
if IPay::config.dry_run
|
14
|
+
@status = {:arc => '00', :mrc => '00', :description => 'Dry Run, no response processed'}
|
15
|
+
else
|
16
|
+
@raw_xml = xml
|
17
|
+
parse_response @raw_xml
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def success?
|
22
|
+
@status[:arc] == '00' && @status[:mrc] == '00'
|
23
|
+
end
|
24
|
+
|
25
|
+
def error?
|
26
|
+
not success?
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def parse_response(xml)
|
32
|
+
IPay::log.debug 'Parsing response xml...'
|
33
|
+
parser = XML::Parser.string xml
|
34
|
+
parser.context.options = PARSER_OPT
|
35
|
+
parsed = parser.parse
|
36
|
+
|
37
|
+
IPay::Certification.log(parsed) if IPay::config.certification
|
38
|
+
|
39
|
+
response = xml_node_to_hash(parsed.find('//RESPONSE/RESPONSE/FIELDS')[0])
|
40
|
+
raise ResponseError.new 'Invalid response from server' unless response and response.include? :arc
|
41
|
+
|
42
|
+
d = response.delete(:local_date).match(/([0-9]{2})([0-9]{2})([0-9]{4})/)
|
43
|
+
t = response.delete(:local_time).match(/([0-9]{2})([0-9]{2})([0-9]{2})/)
|
44
|
+
|
45
|
+
@server_time = DateTime.parse("#{d[3]}-#{d[1]}-#{d[2]}T#{t[1]}:#{t[2]}:#{t[3]}") rescue DateTime.now
|
46
|
+
@status = { :arc => response.delete(:arc), :mrc => response.delete(:mrc), :description => response.delete(:response_text) }
|
47
|
+
@data = response
|
48
|
+
|
49
|
+
IPay::log.info "ARC=#{@status[:arc]}, MRC=#{@status[:mrc]}, RESPONSE_TEXT=#{@status[:description]}"
|
50
|
+
IPay::log.debug response
|
51
|
+
raise RequestTimeout.new(@status[:description]) if @status[:arc] == 'TO'
|
52
|
+
end
|
53
|
+
|
54
|
+
def xml_node_to_hash(node)
|
55
|
+
result = {}
|
56
|
+
if node.element? && node.children? && node.children[0].element?
|
57
|
+
node.children.each do |child|
|
58
|
+
result[child.name.downcase.to_sym] = xml_node_to_hash(child)
|
59
|
+
end
|
60
|
+
result
|
61
|
+
else
|
62
|
+
Util.unescape(node.content.strip)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
data/lib/ipay/util.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
module IPay
|
2
|
+
module Util
|
3
|
+
|
4
|
+
ESCAPE_CHARS = { '&' => '&', '"' => '"', '<' => '<', '>' => '>', "'" => ''' }
|
5
|
+
|
6
|
+
def self.escape(string)
|
7
|
+
string.to_s.gsub(Regexp.new(ESCAPE_CHARS.keys.join('|')), ESCAPE_CHARS)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.unescape(string)
|
11
|
+
string.gsub(Regexp.new(ESCAPE_CHARS.values.join('|')), ESCAPE_CHARS.invert)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.hash_to_xml(h, l=0)
|
15
|
+
return h.to_s unless h.is_a? Hash
|
16
|
+
xml = ''
|
17
|
+
h.each do |k,v|
|
18
|
+
k = k.to_s.upcase
|
19
|
+
xml << if v.kind_of? Enumerable
|
20
|
+
(v.is_a?(Array) ? v : Array[v]).collect { |group| "#{"\t"*l}<#{k}>\n#{hash_to_xml(group, l+1)}#{"\t"*l}</#{k}>\n"}.join
|
21
|
+
else
|
22
|
+
"#{"\t"*l}<#{k}>#{self.escape(v)}</#{k}>\n"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
xml
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
data/lib/ipay/version.rb
ADDED
data/lib/ipay/wallet.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'ipay/api_request'
|
2
|
+
|
3
|
+
module IPay
|
4
|
+
module Wallet
|
5
|
+
|
6
|
+
def self.default_values(data)
|
7
|
+
data[:transaction_indicator] ||= TXN_VIA_HTTPS
|
8
|
+
data
|
9
|
+
end
|
10
|
+
|
11
|
+
class Client < ApiRequest
|
12
|
+
self.service_format = '1010'
|
13
|
+
|
14
|
+
def self.insert(data)
|
15
|
+
self.send_request(data)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.modify(data)
|
19
|
+
self.send_request(data)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.delete(data)
|
23
|
+
self.send_request(data)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class Account < ApiRequest
|
28
|
+
self.service_format = '1010'
|
29
|
+
|
30
|
+
def self.insert(data)
|
31
|
+
self.send_request(data)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.modify(data)
|
35
|
+
self.send_request(data)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.delete(data)
|
39
|
+
self.send_request(data)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'net/https'
|
2
|
+
require 'uri'
|
3
|
+
require 'yaml'
|
4
|
+
require 'ipay/util'
|
5
|
+
|
6
|
+
module IPay
|
7
|
+
class XmlRequest
|
8
|
+
|
9
|
+
CONFIG_GLOBALS = [:operator, :terminal_id, :pin, :verbose_response]
|
10
|
+
|
11
|
+
def initialize(data = {})
|
12
|
+
@data = data
|
13
|
+
IPay::log.debug "Request Args: #{(@data.collect { |k, v| "#{k}=#{v}" }.join(','))}"
|
14
|
+
|
15
|
+
CONFIG_GLOBALS.each do |key|
|
16
|
+
next if @data.include?(key)
|
17
|
+
@data[key] = IPay::config.send(key) if IPay::config.respond_to?(key)
|
18
|
+
end
|
19
|
+
|
20
|
+
@xml = build_xml Util::hash_to_xml(@data)
|
21
|
+
IPay::log.debug @xml
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
@xml
|
26
|
+
end
|
27
|
+
|
28
|
+
def send
|
29
|
+
do_post(IPay::config.url, @xml)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def do_post(api_url, data)
|
35
|
+
IPay::log.debug "POST to #{api_url}"
|
36
|
+
|
37
|
+
if IPay::config.dry_run
|
38
|
+
IPay::log.info 'Dry run enabled, not sending to API'
|
39
|
+
return
|
40
|
+
end
|
41
|
+
|
42
|
+
url = URI.parse(api_url)
|
43
|
+
http = Net::HTTP.new(url.host, url.port)
|
44
|
+
http.use_ssl = true
|
45
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
46
|
+
store = OpenSSL::X509::Store.new
|
47
|
+
store.set_default_paths
|
48
|
+
http.cert_store = store
|
49
|
+
|
50
|
+
req = Net::HTTP::Post.new(url.path)
|
51
|
+
req.body = data
|
52
|
+
|
53
|
+
res = http.start { |http| http.request(req) }
|
54
|
+
res.body
|
55
|
+
|
56
|
+
rescue EOFError
|
57
|
+
raise RequestError.new('Unable to send your request or the request was rejected by the server.')
|
58
|
+
end
|
59
|
+
|
60
|
+
def build_xml(fields_xml)
|
61
|
+
"<REQUEST KEY=\"#{IPay::config.company_key}\" PROTOCOL=\"1\" FMT=\"1\" ENCODING=\"0\">
|
62
|
+
<TRANSACTION>
|
63
|
+
<FIELDS>#{fields_xml}</FIELDS>
|
64
|
+
</TRANSACTION>
|
65
|
+
</REQUEST>"
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'ipay'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
class Test::Unit::TestCase
|
6
|
+
def self.test(string, &block)
|
7
|
+
define_method("test:#{string}", &block)
|
8
|
+
end
|
9
|
+
|
10
|
+
def assert_not(expression)
|
11
|
+
assert_block("Expected <#{expression}> to be false!") { not expression }
|
12
|
+
end
|
13
|
+
end
|
data/test/test_cc.rb
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestCC < Test::Unit::TestCase
|
4
|
+
CC_EXP = "#{Date.today.month.to_s.rjust(2,'0')}#{Date.today.year.to_s[2..-1]}"
|
5
|
+
|
6
|
+
test 'debit sale' do
|
7
|
+
resp = IPay::CC::Debit.sale(
|
8
|
+
:amount => '4.99',
|
9
|
+
:account_number => '4000009999999991',
|
10
|
+
:cvv => 123,
|
11
|
+
:expiration => CC_EXP,
|
12
|
+
:first_name => 'nick',
|
13
|
+
:last_name => 'wilson',
|
14
|
+
:address => '123 fake st',
|
15
|
+
:city => 'sometown',
|
16
|
+
:state => 'NY',
|
17
|
+
:postal_code => '90210',
|
18
|
+
:country => IPay::Countries::USA
|
19
|
+
)
|
20
|
+
|
21
|
+
assert resp.success?
|
22
|
+
assert resp.data.include?(:transaction_id)
|
23
|
+
end
|
24
|
+
|
25
|
+
test 'debit capture and auth' do
|
26
|
+
resp = IPay::CC::Debit.auth(
|
27
|
+
:amount => '4.99',
|
28
|
+
:account_number => '4000009999999991',
|
29
|
+
:cvv => 123,
|
30
|
+
:expiration => CC_EXP,
|
31
|
+
:first_name => 'nick',
|
32
|
+
:last_name => 'wilson',
|
33
|
+
:address => '123 fake st',
|
34
|
+
:city => 'sometown',
|
35
|
+
:state => 'NY',
|
36
|
+
:postal_code => '90210',
|
37
|
+
:country => IPay::Countries::USA
|
38
|
+
)
|
39
|
+
|
40
|
+
assert resp.success?
|
41
|
+
assert resp.data.include?(:transaction_id)
|
42
|
+
|
43
|
+
sleep(3)
|
44
|
+
|
45
|
+
resp = IPay::CC::Debit.capture(
|
46
|
+
:amount => '4.99',
|
47
|
+
:transaction_id => resp.data[:transaction_id]
|
48
|
+
)
|
49
|
+
|
50
|
+
assert resp.success?
|
51
|
+
end
|
52
|
+
|
53
|
+
test 'debit void' do
|
54
|
+
resp = IPay::CC::Debit.sale(
|
55
|
+
:amount => '4.99',
|
56
|
+
:account_number => '4000009999999991',
|
57
|
+
:cvv => 123,
|
58
|
+
:expiration => CC_EXP,
|
59
|
+
:first_name => 'nick',
|
60
|
+
:last_name => 'wilson',
|
61
|
+
:address => '123 fake st',
|
62
|
+
:city => 'sometown',
|
63
|
+
:state => 'NY',
|
64
|
+
:postal_code => '90210',
|
65
|
+
:country => IPay::Countries::USA
|
66
|
+
)
|
67
|
+
|
68
|
+
assert resp.success?
|
69
|
+
assert resp.data.include?(:transaction_id)
|
70
|
+
|
71
|
+
sleep(3)
|
72
|
+
|
73
|
+
resp = IPay::CC::Debit.void(
|
74
|
+
:transaction_id => resp.data[:transaction_id]
|
75
|
+
)
|
76
|
+
|
77
|
+
assert resp.success?
|
78
|
+
end
|
79
|
+
|
80
|
+
test 'credit refund' do
|
81
|
+
resp = IPay::CC::Credit.refund(
|
82
|
+
:amount => '4.99',
|
83
|
+
:account_number => '4000009999999991',
|
84
|
+
:cvv => 123,
|
85
|
+
:expiration => CC_EXP,
|
86
|
+
:first_name => 'nick',
|
87
|
+
:last_name => 'wilson',
|
88
|
+
:address => '123 fake st',
|
89
|
+
:city => 'sometown',
|
90
|
+
:state => 'NY',
|
91
|
+
:postal_code => '90210',
|
92
|
+
:country => IPay::Countries::USA
|
93
|
+
)
|
94
|
+
|
95
|
+
assert resp.success?
|
96
|
+
assert resp.data.include?(:transaction_id)
|
97
|
+
end
|
98
|
+
|
99
|
+
test 'credit void' do
|
100
|
+
resp = IPay::CC::Credit.refund(
|
101
|
+
:amount => '4.99',
|
102
|
+
:account_number => '4000009999999991',
|
103
|
+
:cvv => 123,
|
104
|
+
:expiration => CC_EXP,
|
105
|
+
:first_name => 'nick',
|
106
|
+
:last_name => 'wilson',
|
107
|
+
:address => '123 fake st',
|
108
|
+
:city => 'sometown',
|
109
|
+
:state => 'NY',
|
110
|
+
:postal_code => '90210',
|
111
|
+
:country => IPay::Countries::USA
|
112
|
+
)
|
113
|
+
|
114
|
+
assert resp.success?
|
115
|
+
assert resp.data.include?(:transaction_id)
|
116
|
+
|
117
|
+
sleep(3)
|
118
|
+
|
119
|
+
resp = IPay::CC::Credit.void(
|
120
|
+
:transaction_id => resp.data[:transaction_id]
|
121
|
+
)
|
122
|
+
|
123
|
+
assert resp.success?
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
data/test/test_errors.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestErrors < Test::Unit::TestCase
|
4
|
+
CC_EXP = "#{Date.today.month.to_s.rjust(2,'0')}#{Date.today.year.to_s[2..-1]}"
|
5
|
+
|
6
|
+
test 'sale declined' do
|
7
|
+
|
8
|
+
resp = IPay::CC::Debit.sale(
|
9
|
+
:amount => '1.05',
|
10
|
+
:account_number => '4000009999999991',
|
11
|
+
:cvv => 123,
|
12
|
+
:expiration => CC_EXP,
|
13
|
+
:first_name => 'nick',
|
14
|
+
:last_name => 'wilson',
|
15
|
+
:address => '123 fake st',
|
16
|
+
:city => 'sometown',
|
17
|
+
:state => 'NY',
|
18
|
+
:postal_code => '90210',
|
19
|
+
:country => IPay::Countries::USA
|
20
|
+
)
|
21
|
+
|
22
|
+
assert resp.error?
|
23
|
+
assert resp.status[:arc] == '05'
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
test 'request timeout' do
|
28
|
+
|
29
|
+
assert_raise IPay::RequestTimeout do
|
30
|
+
resp = IPay::CC::Debit.sale(
|
31
|
+
:amount => '1.57',
|
32
|
+
:account_number => '4000009999999991',
|
33
|
+
:cvv => 123,
|
34
|
+
:expiration => CC_EXP,
|
35
|
+
:first_name => 'nick',
|
36
|
+
:last_name => 'wilson',
|
37
|
+
:address => '123 fake st',
|
38
|
+
:city => 'sometown',
|
39
|
+
:state => 'NY',
|
40
|
+
:postal_code => '90210',
|
41
|
+
:country => IPay::Countries::USA
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
data/test/test_wallet.rb
ADDED
@@ -0,0 +1,199 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestWallet < Test::Unit::TestCase
|
4
|
+
CC_EXP = "#{Date.today.month.to_s.rjust(2,'0')}#{Date.today.year.to_s[2..-1]}"
|
5
|
+
|
6
|
+
test 'client insert' do
|
7
|
+
resp = IPay::Wallet::Client.insert(
|
8
|
+
:account => 'CC',
|
9
|
+
:billing_transaction => 2,
|
10
|
+
:account_number => '4000009999999991',
|
11
|
+
:cvv => 123,
|
12
|
+
:expiration => CC_EXP,
|
13
|
+
:first_name => 'nick',
|
14
|
+
:last_name => 'wilson',
|
15
|
+
:address => '123 fake st',
|
16
|
+
:city => 'sometown',
|
17
|
+
:state => 'NY',
|
18
|
+
:postal_code => '90210',
|
19
|
+
:country => IPay::Countries::USA
|
20
|
+
)
|
21
|
+
|
22
|
+
assert resp.success?
|
23
|
+
assert resp.data.include?(:transaction_id)
|
24
|
+
assert resp.data.include?(:client_id)
|
25
|
+
end
|
26
|
+
|
27
|
+
test 'client modify' do
|
28
|
+
|
29
|
+
resp = IPay::Wallet::Client.insert(
|
30
|
+
:account => 'CC',
|
31
|
+
:billing_transaction => 2,
|
32
|
+
:account_number => '4000009999999991',
|
33
|
+
:cvv => 123,
|
34
|
+
:expiration => CC_EXP,
|
35
|
+
:first_name => 'nick',
|
36
|
+
:last_name => 'wilson',
|
37
|
+
:address => '123 fake st',
|
38
|
+
:city => 'sometown',
|
39
|
+
:state => 'NY',
|
40
|
+
:postal_code => '90210',
|
41
|
+
:country => IPay::Countries::USA
|
42
|
+
)
|
43
|
+
|
44
|
+
assert resp.success?
|
45
|
+
assert resp.data.include?(:transaction_id)
|
46
|
+
assert resp.data.include?(:client_id)
|
47
|
+
|
48
|
+
sleep(3)
|
49
|
+
|
50
|
+
resp = IPay::Wallet::Client.modify(
|
51
|
+
:client_id => resp.data[:client_id],
|
52
|
+
:first_name => 'gregory washington'
|
53
|
+
)
|
54
|
+
|
55
|
+
assert resp.success?
|
56
|
+
assert resp.data.include?(:transaction_id)
|
57
|
+
end
|
58
|
+
|
59
|
+
test 'client delete' do
|
60
|
+
resp = IPay::Wallet::Client.insert(
|
61
|
+
:account => 'CC',
|
62
|
+
:billing_transaction => 2,
|
63
|
+
:account_number => '4000009999999991',
|
64
|
+
:cvv => 123,
|
65
|
+
:expiration => CC_EXP,
|
66
|
+
:first_name => 'nick',
|
67
|
+
:last_name => 'wilson',
|
68
|
+
:address => '123 fake st',
|
69
|
+
:city => 'sometown',
|
70
|
+
:state => 'NY',
|
71
|
+
:postal_code => '90210',
|
72
|
+
:country => IPay::Countries::USA
|
73
|
+
)
|
74
|
+
|
75
|
+
assert resp.success?
|
76
|
+
assert resp.data.include?(:transaction_id)
|
77
|
+
assert resp.data.include?(:client_id)
|
78
|
+
|
79
|
+
sleep(3)
|
80
|
+
|
81
|
+
resp = IPay::Wallet::Client.delete(
|
82
|
+
:client_id => resp.data[:client_id]
|
83
|
+
)
|
84
|
+
|
85
|
+
assert resp.success?
|
86
|
+
assert resp.data.include?(:transaction_id)
|
87
|
+
assert resp.data.include?(:client_id)
|
88
|
+
end
|
89
|
+
|
90
|
+
test 'account insert' do
|
91
|
+
resp = IPay::Wallet::Client.insert(
|
92
|
+
:account => 'CC',
|
93
|
+
:billing_transaction => 2,
|
94
|
+
:account_number => '4000009999999991',
|
95
|
+
:cvv => 123,
|
96
|
+
:expiration => CC_EXP,
|
97
|
+
:first_name => 'nick',
|
98
|
+
:last_name => 'wilson',
|
99
|
+
:address => '123 fake st',
|
100
|
+
:city => 'sometown',
|
101
|
+
:state => 'NY',
|
102
|
+
:postal_code => '90210',
|
103
|
+
:country => IPay::Countries::USA
|
104
|
+
)
|
105
|
+
|
106
|
+
assert resp.success?
|
107
|
+
assert resp.data.include?(:transaction_id)
|
108
|
+
assert resp.data.include?(:client_id)
|
109
|
+
|
110
|
+
sleep(3)
|
111
|
+
|
112
|
+
resp = IPay::Wallet::Account.insert(
|
113
|
+
:account => 'CC',
|
114
|
+
:billing_transaction => 2,
|
115
|
+
:account_number => '4000009999999991',
|
116
|
+
:cvv => 123,
|
117
|
+
:expiration => '0512',
|
118
|
+
:client_id => resp.data[:client_id]
|
119
|
+
)
|
120
|
+
assert resp.success?
|
121
|
+
assert resp.data.include?(:transaction_id)
|
122
|
+
end
|
123
|
+
|
124
|
+
test 'account modify' do
|
125
|
+
resp = IPay::Wallet::Client.insert(
|
126
|
+
:account => 'CC',
|
127
|
+
:billing_transaction => 2,
|
128
|
+
:account_number => '4000009999999991',
|
129
|
+
:cvv => 123,
|
130
|
+
:expiration => CC_EXP,
|
131
|
+
:first_name => 'nick',
|
132
|
+
:last_name => 'wilson',
|
133
|
+
:address => '123 fake st',
|
134
|
+
:city => 'sometown',
|
135
|
+
:state => 'NY',
|
136
|
+
:postal_code => '90210',
|
137
|
+
:country => IPay::Countries::USA
|
138
|
+
)
|
139
|
+
|
140
|
+
assert resp.success?
|
141
|
+
assert resp.data.include?(:transaction_id)
|
142
|
+
assert resp.data.include?(:client_id)
|
143
|
+
|
144
|
+
sleep(3)
|
145
|
+
|
146
|
+
resp = IPay::Wallet::Account.modify(
|
147
|
+
:account_id => resp.data[:account_id],
|
148
|
+
:billing_transaction => 2,
|
149
|
+
:account_number => '5100009999999997',
|
150
|
+
:cvv => 456,
|
151
|
+
:expiration => CC_EXP
|
152
|
+
)
|
153
|
+
|
154
|
+
assert resp.success?
|
155
|
+
assert resp.data.include?(:transaction_id)
|
156
|
+
end
|
157
|
+
|
158
|
+
test 'account delete' do
|
159
|
+
resp = IPay::Wallet::Client.insert(
|
160
|
+
:account => 'CC',
|
161
|
+
:billing_transaction => 2,
|
162
|
+
:account_number => '4000009999999991',
|
163
|
+
:cvv => 123,
|
164
|
+
:expiration => CC_EXP,
|
165
|
+
:first_name => 'nick',
|
166
|
+
:last_name => 'wilson',
|
167
|
+
:address => '123 fake st',
|
168
|
+
:city => 'sometown',
|
169
|
+
:state => 'NY',
|
170
|
+
:postal_code => '90210',
|
171
|
+
:country => IPay::Countries::USA
|
172
|
+
)
|
173
|
+
|
174
|
+
assert resp.success?
|
175
|
+
assert resp.data.include?(:transaction_id)
|
176
|
+
assert resp.data.include?(:client_id)
|
177
|
+
|
178
|
+
sleep(3)
|
179
|
+
|
180
|
+
resp = IPay::Wallet::Account.insert(
|
181
|
+
:account => 'CC',
|
182
|
+
:billing_transaction => 2,
|
183
|
+
:account_number => '4000009999999991',
|
184
|
+
:cvv => 123,
|
185
|
+
:expiration => '0512',
|
186
|
+
:client_id => resp.data[:client_id]
|
187
|
+
)
|
188
|
+
assert resp.success?
|
189
|
+
assert resp.data.include?(:transaction_id)
|
190
|
+
|
191
|
+
sleep(3)
|
192
|
+
|
193
|
+
resp = IPay::Wallet::Account.delete(
|
194
|
+
:account_id => resp.data[:account_id]
|
195
|
+
)
|
196
|
+
assert resp.success?
|
197
|
+
assert resp.data.include?(:transaction_id)
|
198
|
+
end
|
199
|
+
end
|
metadata
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ipay
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.1.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Nick Wilson
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-05-16 00:00:00 -04:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: libxml-ruby
|
18
|
+
prerelease: false
|
19
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
|
+
none: false
|
21
|
+
requirements:
|
22
|
+
- - ">="
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: "0"
|
25
|
+
type: :runtime
|
26
|
+
version_requirements: *id001
|
27
|
+
description: iPay is a simple library for interfacing with Planet Payments payment solutions API, iPay. For more information visit http://www.ipay.com
|
28
|
+
email: wilson.nick@gmail.com
|
29
|
+
executables: []
|
30
|
+
|
31
|
+
extensions: []
|
32
|
+
|
33
|
+
extra_rdoc_files: []
|
34
|
+
|
35
|
+
files:
|
36
|
+
- lib/console.rb
|
37
|
+
- lib/ipay/api_request.rb
|
38
|
+
- lib/ipay/cc.rb
|
39
|
+
- lib/ipay/certification.rb
|
40
|
+
- lib/ipay/config.rb
|
41
|
+
- lib/ipay/countries.rb
|
42
|
+
- lib/ipay/log.rb
|
43
|
+
- lib/ipay/network.rb
|
44
|
+
- lib/ipay/response.rb
|
45
|
+
- lib/ipay/util.rb
|
46
|
+
- lib/ipay/version.rb
|
47
|
+
- lib/ipay/wallet.rb
|
48
|
+
- lib/ipay/xml_request.rb
|
49
|
+
- lib/ipay.rb
|
50
|
+
- test/helper.rb
|
51
|
+
- test/test_cc.rb
|
52
|
+
- test/test_errors.rb
|
53
|
+
- test/test_wallet.rb
|
54
|
+
- example/certification.rb
|
55
|
+
- README.md
|
56
|
+
- Rakefile
|
57
|
+
has_rdoc: true
|
58
|
+
homepage: https://github.com/AudioAddict/ipay
|
59
|
+
licenses: []
|
60
|
+
|
61
|
+
post_install_message:
|
62
|
+
rdoc_options: []
|
63
|
+
|
64
|
+
require_paths:
|
65
|
+
- lib
|
66
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: "0"
|
72
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: "0"
|
78
|
+
requirements: []
|
79
|
+
|
80
|
+
rubyforge_project:
|
81
|
+
rubygems_version: 1.6.2
|
82
|
+
signing_key:
|
83
|
+
specification_version: 3
|
84
|
+
summary: library for interfacing with iPay xml API
|
85
|
+
test_files: []
|
86
|
+
|