xpay 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +12 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +35 -0
- data/LICENSE +20 -0
- data/README.rdoc +109 -0
- data/Rakefile +52 -0
- data/UPGRADES +9 -0
- data/lib/xpay.rb +109 -0
- data/lib/xpay/core/creditcard.rb +49 -0
- data/lib/xpay/core/customer.rb +47 -0
- data/lib/xpay/core/operation.rb +58 -0
- data/lib/xpay/payment.rb +209 -0
- data/lib/xpay/transaction.rb +35 -0
- data/lib/xpay/version.rb +3 -0
- data/rails/init.rb +2 -0
- data/tasks/xpay_tasks.rake +4 -0
- data/test/fixtures/config/xpay.yml +25 -0
- data/test/fixtures/creditcards.yml +37 -0
- data/test/fixtures/customer.xml +1 -0
- data/test/fixtures/customer.yml +22 -0
- data/test/fixtures/operation.xml +1 -0
- data/test/fixtures/operation.yml +23 -0
- data/test/fixtures/request_rewritten.xml +1 -0
- data/test/fixtures/response.xml +9 -0
- data/test/fixtures/response_3d.xml +40 -0
- data/test/fixtures/root.xml +1 -0
- data/test/fixtures/xpay_defaults.yml +21 -0
- data/test/functional/payment_functional_test.rb +84 -0
- data/test/test_helper.rb +46 -0
- data/test/xpay/core/creditcard_test.rb +28 -0
- data/test/xpay/core/customer_test.rb +63 -0
- data/test/xpay/core/operation_test.rb +45 -0
- data/test/xpay/payment_test.rb +105 -0
- data/test/xpay_test.rb +41 -0
- data/xpay.gemspec +23 -0
- metadata +193 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
xpay (0.0.1)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: http://rubygems.org/
|
8
|
+
specs:
|
9
|
+
diff-lcs (1.1.2)
|
10
|
+
log_buddy (0.5.0)
|
11
|
+
mocha (0.9.8)
|
12
|
+
rake
|
13
|
+
rake (0.8.7)
|
14
|
+
rspec (2.0.0)
|
15
|
+
rspec-core (= 2.0.0)
|
16
|
+
rspec-expectations (= 2.0.0)
|
17
|
+
rspec-mocks (= 2.0.0)
|
18
|
+
rspec-core (2.0.0)
|
19
|
+
rspec-expectations (2.0.0)
|
20
|
+
diff-lcs (>= 1.1.2)
|
21
|
+
rspec-mocks (2.0.0)
|
22
|
+
rspec-core (= 2.0.0)
|
23
|
+
rspec-expectations (= 2.0.0)
|
24
|
+
shoulda (2.11.3)
|
25
|
+
|
26
|
+
PLATFORMS
|
27
|
+
ruby
|
28
|
+
|
29
|
+
DEPENDENCIES
|
30
|
+
bundler (>= 1.0.0)
|
31
|
+
log_buddy
|
32
|
+
mocha
|
33
|
+
rspec (>= 2.0.0)
|
34
|
+
shoulda
|
35
|
+
xpay!
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Volker Pacher
|
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.rdoc
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
= Xpay
|
2
|
+
|
3
|
+
An abstaction layer on top Xpay from SecureTrading.com
|
4
|
+
http://www.securetrading.com/support/xpay.html for futher details, in depth specifications and installation guides of the xpay java applet
|
5
|
+
|
6
|
+
== Install
|
7
|
+
* install the xpay java applet and configure it according to the installation guidelines from SecureTrading
|
8
|
+
(http://www.securetrading.com/download/DOC_COM_ST-XPAY4-USER-GUIDE.pdf)
|
9
|
+
* start the applet then install the gem:
|
10
|
+
|
11
|
+
$ gem install xpay
|
12
|
+
|
13
|
+
* configure by either creating a yaml file at config/xpay.yml or call Xpay.set_config with a hash
|
14
|
+
* the following options can be set:
|
15
|
+
- merchant_name
|
16
|
+
- version
|
17
|
+
- alias
|
18
|
+
- site_reference
|
19
|
+
- port
|
20
|
+
- callback_url
|
21
|
+
|
22
|
+
== Usage
|
23
|
+
=== An example of a 3D-Secure Transaction
|
24
|
+
|
25
|
+
-first: create the class instance for CreditCard, Customer and Operatioon
|
26
|
+
|
27
|
+
CreditCard:
|
28
|
+
cc = Xpay::CreditCard.new(:card_type => "Visa",
|
29
|
+
:number => "4111111111111160",
|
30
|
+
:security_code => "123",
|
31
|
+
:valid_until => "11/11")
|
32
|
+
|
33
|
+
Customer:
|
34
|
+
cus = Xpay::Customer.new(:fullname => "Fred Bloggs")
|
35
|
+
|
36
|
+
Operation:
|
37
|
+
ops = Xpay::Operation.new(:currency => "GBP", :amount => 1999)
|
38
|
+
|
39
|
+
Create payment instance
|
40
|
+
p = Xpay::Payment.new({:creditcard => cc, :customer => cus, :operation => ops)
|
41
|
+
|
42
|
+
Make payment
|
43
|
+
rt = p.make_payment
|
44
|
+
|
45
|
+
-second: depending on the return value rt:
|
46
|
+
|
47
|
+
rt==0 An error occured during processing
|
48
|
+
Check p.response_block[:error_code] for further details
|
49
|
+
|
50
|
+
rt==1 Settlement request approve, check p.response_block for all the details
|
51
|
+
rt==2 Settlement request declined, check p.response_block for all the details
|
52
|
+
|
53
|
+
rt==-1 A redirection to an 3D-Secure server is necessary.
|
54
|
+
All necessary information is contained in p.three_secure
|
55
|
+
It has the following keys:
|
56
|
+
:md a unique reference generated according to the 3D Secure specification
|
57
|
+
(currently up to 1024 bytes in base64 format).
|
58
|
+
it is passed as parameter in the callback from the 3D-Secure server
|
59
|
+
and you will need to store it to find the transaction on callback
|
60
|
+
|
61
|
+
:pareq The pareq contains purchase transaction details upon which ACS authentication decisions are based.
|
62
|
+
If you are making your own customised redirect page instead of using the <Html> provided then this
|
63
|
+
field MUST be included in that html, as a hidden field. The length of the PaReq is not explicitly
|
64
|
+
defined by the 3-D Secure specification. We recommend allowing at least 4096 bytes
|
65
|
+
for this field in any variables/storage systems.
|
66
|
+
|
67
|
+
:termurl Your own callback url. If you are making your own customised redirect page
|
68
|
+
instead of using the <Html> provided then this field MUST be included in that html as a hidden field.
|
69
|
+
|
70
|
+
:acsurl This is the url of the ACS it should be used as the location of the redirect if you use your own form/redirect
|
71
|
+
|
72
|
+
:html This value contains the complete html code if you don't want to use your own form.
|
73
|
+
This can be achieved by sending your normal server headers to the client immediately followed by the contents of the <Html> tag.
|
74
|
+
|
75
|
+
:request_xml This is a complete xml document as string that you need to pass to a new payment instance after the callback.
|
76
|
+
You need to store this field for the callback.
|
77
|
+
|
78
|
+
-third: after the callback from the 3D Secure Server:
|
79
|
+
|
80
|
+
make sure that protect_from_forgery is switched off for this controller method as it is not transmitted by the callback
|
81
|
+
you will have to paramaters in the params hash
|
82
|
+
params[:MD] and params[:PaRes]
|
83
|
+
|
84
|
+
Use params[:md] to find your stored transaction:
|
85
|
+
|
86
|
+
create a new class instance with the request_xml stored in the transaction and params[:PaRes] from the callback
|
87
|
+
|
88
|
+
p = Xpay::Payment.new({:xml => request_xml, :pares => params[:PaRes])
|
89
|
+
|
90
|
+
make the payment
|
91
|
+
|
92
|
+
rt = p.make_payment
|
93
|
+
|
94
|
+
The possible values of rt are as above.
|
95
|
+
|
96
|
+
=== more examples to follow
|
97
|
+
|
98
|
+
|
99
|
+
== Copyright
|
100
|
+
|
101
|
+
See LICENSE for details.
|
102
|
+
|
103
|
+
== Note on Patches/Pull Requests
|
104
|
+
|
105
|
+
* Fork the project.
|
106
|
+
* Make your feature addition or bug fix.
|
107
|
+
* Add tests for it. This is important so I don't break it in a future version unintentionally.
|
108
|
+
* Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself in another branch so I can ignore when I pull)
|
109
|
+
* Send me a pull request. Bonus points for topic branches.
|
data/Rakefile
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'bundler'
|
5
|
+
|
6
|
+
require File.expand_path('../lib/xpay/version', __FILE__)
|
7
|
+
|
8
|
+
|
9
|
+
namespace :test do
|
10
|
+
Rake::TestTask.new(:all) do |test|
|
11
|
+
test.libs << 'lib' << 'test'
|
12
|
+
test.ruby_opts << '-rubygems'
|
13
|
+
test.pattern = 'test/**/*_test.rb'
|
14
|
+
test.verbose = true
|
15
|
+
end
|
16
|
+
Rake::TestTask.new(:unit) do |test|
|
17
|
+
test.libs << 'lib' << 'test'
|
18
|
+
test.ruby_opts << '-rubygems'
|
19
|
+
test.pattern = 'test/xpay/**/*_test.rb'
|
20
|
+
test.verbose = true
|
21
|
+
end
|
22
|
+
Rake::TestTask.new(:functional) do |test|
|
23
|
+
test.libs << 'lib' << 'test'
|
24
|
+
test.ruby_opts << '-rubygems'
|
25
|
+
test.pattern = 'test/functional/*_test.rb'
|
26
|
+
test.verbose = true
|
27
|
+
end
|
28
|
+
end
|
29
|
+
task :default => :test
|
30
|
+
|
31
|
+
desc 'Builds the gem'
|
32
|
+
task :build do
|
33
|
+
sh "gem build xpay.gemspec"
|
34
|
+
end
|
35
|
+
|
36
|
+
desc 'Builds and installs the gem'
|
37
|
+
task :install => :build do
|
38
|
+
sh "gem install ./pkg/xpay-#{Xpay::Version}"
|
39
|
+
end
|
40
|
+
|
41
|
+
desc 'Tags version, pushes to remote, and pushes gem'
|
42
|
+
task :release => :build do
|
43
|
+
sh "git tag v#{Xpay::Version}"
|
44
|
+
sh "git push origin master"
|
45
|
+
sh "git push origin v#{Xpay::Version}"
|
46
|
+
sh "gem push ./pkg/xpay-#{Xpay::Version}.gem"
|
47
|
+
end
|
48
|
+
desc "Open an irb session preloaded with this library"
|
49
|
+
task :console do
|
50
|
+
sh "irb -rubygems -I lib -r xpay.rb"
|
51
|
+
end
|
52
|
+
Bundler::GemHelper.install_tasks
|
data/UPGRADES
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
0.0.1 => 0.0.2
|
2
|
+
* established basic structure of the gem, wrote most test and added first functional tests, added fixtures
|
3
|
+
|
4
|
+
0.0.2 => 0.0.3
|
5
|
+
* finished all methods necessary to make a succesful transaction
|
6
|
+
* wrote basic documentation for usage
|
7
|
+
|
8
|
+
0.0.3 => 0.0.4
|
9
|
+
* bug fixed that prevents loading of MerchantName from config and create root xml
|
data/lib/xpay.rb
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
require 'rexml/xmldecl'
|
3
|
+
require 'ostruct'
|
4
|
+
require 'erb'
|
5
|
+
require 'xpay/transaction'
|
6
|
+
|
7
|
+
module Xpay
|
8
|
+
# These are the default settings. You can change them by placing YAML file into config/xpay.yml with settings for each environment.
|
9
|
+
# Alternatively you can change the settings by calling the config setter for each attribute for example:
|
10
|
+
# Xpay.config.alias = "your_new_alias"
|
11
|
+
# Another option is to call Xpay.set_config with a hash containing the attributes you want to change
|
12
|
+
#
|
13
|
+
# merchant_name: CompanyName
|
14
|
+
# version: 3.51 'this is the only supported version at the moment and has to be 3.51, as String'
|
15
|
+
# alias: site12345
|
16
|
+
# site_reference: site12345
|
17
|
+
# port: 5000 'this needs to be an Integer'
|
18
|
+
# default_query: ST3DCARDQUERY 'defaults to 3D Card query if not otherwise specified'
|
19
|
+
# settlement_day: 1 'this needs to be a String'
|
20
|
+
# default_currency: GBP
|
21
|
+
|
22
|
+
autoload :Payment, 'xpay/payment'
|
23
|
+
autoload :CreditCard, 'xpay/core/creditcard'
|
24
|
+
autoload :Customer, 'xpay/core/customer'
|
25
|
+
autoload :Operation, 'xpay/core/operation'
|
26
|
+
|
27
|
+
@xpay_config = OpenStruct.new({
|
28
|
+
"merchant_name" => "CompanyName",
|
29
|
+
"version" => "3.51",
|
30
|
+
"alias" => "site12345",
|
31
|
+
"site_reference" => "site12345",
|
32
|
+
"port" => 5000,
|
33
|
+
"callback_url" => "http://localhost/gateway_callback",
|
34
|
+
"default_query" => "ST3DCARDQUERY",
|
35
|
+
"settlement_day" => "1",
|
36
|
+
"default_currency" => "GBP"
|
37
|
+
})
|
38
|
+
class XpayError < StandardError
|
39
|
+
attr_reader :data
|
40
|
+
|
41
|
+
def initialize(data)
|
42
|
+
@data = data
|
43
|
+
super
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class PaResMissing < XpayError; end
|
48
|
+
class General < XpayError; end
|
49
|
+
|
50
|
+
class << self
|
51
|
+
attr_accessor :app_root, :environment
|
52
|
+
|
53
|
+
def load_config(app_root = Dir.pwd)
|
54
|
+
self.app_root = (RAILS_ROOT if defined?(RAILS_ROOT)) || app_root
|
55
|
+
self.environment = (RAILS_ENV if defined?(RAILS_ENV)) || "development"
|
56
|
+
parse_config
|
57
|
+
return true
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
def root_xml
|
62
|
+
@request_xml ||= create_root_xml
|
63
|
+
end
|
64
|
+
|
65
|
+
def root_to_s
|
66
|
+
self.root_xml.to_s
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
def config
|
71
|
+
@xpay_config
|
72
|
+
end
|
73
|
+
|
74
|
+
def set_config(conf)
|
75
|
+
conf.each do |key, value|
|
76
|
+
@xpay_config.send("#{key}=", value) if @xpay_config.respond_to? key
|
77
|
+
end
|
78
|
+
return true
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
def parse_config
|
83
|
+
path = "#{app_root}/config/xpay.yml"
|
84
|
+
return unless File.exists?(path)
|
85
|
+
conf = YAML::load(ERB.new(IO.read(path)).result)[environment]
|
86
|
+
self.set_config(conf)
|
87
|
+
end
|
88
|
+
|
89
|
+
def create_root_xml
|
90
|
+
r = REXML::Document.new
|
91
|
+
r << REXML::XMLDecl.new("1.0", "iso-8859-1")
|
92
|
+
rb = r.add_element "RequestBlock", {"Version" => config.version}
|
93
|
+
request = rb.add_element "Request", {"Type" => config.default_query}
|
94
|
+
operation = request.add_element "Operation"
|
95
|
+
site_ref = operation.add_element "SiteReference"
|
96
|
+
site_ref.text = config.site_reference
|
97
|
+
if config.default_query == "ST3DCARDQUERY"
|
98
|
+
mn = operation.add_element "MerchantName"
|
99
|
+
mn.text = config.merchant_name
|
100
|
+
tu = operation.add_element "TermUrl"
|
101
|
+
tu.text = config.callback_url
|
102
|
+
end
|
103
|
+
cer = rb.add_element "Certificate"
|
104
|
+
cer.text = config.alias
|
105
|
+
return r
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Xpay
|
2
|
+
class CreditCard
|
3
|
+
attr_accessor :card_type, :valid_until, :valid_from, :issue, :parent_transaction_ref, :transaction_verifier
|
4
|
+
attr_writer :security_code
|
5
|
+
def initialize(options={})
|
6
|
+
if !options.nil? && options.is_a?(Hash)
|
7
|
+
options.each do |key, value|
|
8
|
+
self.send("#{key.to_s}=", value) if self.respond_to? key.to_s
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def number
|
14
|
+
@number.sub(/^([0-9]+)([0-9]{4})$/) { 'x' * $1.length + $2 }
|
15
|
+
end
|
16
|
+
|
17
|
+
def number=(new_val)
|
18
|
+
@number = new_val.to_s.gsub(/[^0-9]/, "")
|
19
|
+
end
|
20
|
+
|
21
|
+
def security_code=(new_val)
|
22
|
+
@security_code = new_val.to_s
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_to_xml(doc)
|
26
|
+
op = REXML::XPath.first(doc, "//Request")
|
27
|
+
op.delete_element "PaymentMethod"
|
28
|
+
pa = op.add_element "PaymentMethod"
|
29
|
+
cc = pa.add_element("CreditCard")
|
30
|
+
cc.add_element("Type").add_text(self.card_type) if self.card_type
|
31
|
+
cc.add_element("Number").add_text(self.number_internal) if self.number_internal
|
32
|
+
cc.add_element("StartDate").add_text(self.valid_from) if self.valid_from
|
33
|
+
cc.add_element("ExpiryDate").add_text(self.valid_until) if self.valid_until
|
34
|
+
cc.add_element("ParentTransactionReference").add_text(self.parent_transaction_ref) if self.parent_transaction_ref
|
35
|
+
cc.add_element("TransactionVerifier").add_text(self.transaction_verifier) if self.transaction_verifier
|
36
|
+
cc.add_element("SecurityCode").add_text(self.security_code) if self.security_code
|
37
|
+
cc.add_element("Issue").add_text(self.issue) if self.issue
|
38
|
+
end
|
39
|
+
|
40
|
+
protected
|
41
|
+
def number_internal
|
42
|
+
@number
|
43
|
+
end
|
44
|
+
|
45
|
+
def security_code
|
46
|
+
@security_code
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Xpay
|
2
|
+
|
3
|
+
# The customer is not required for a transaction except for 3D secure transactions in which case
|
4
|
+
# http_accept and user_agent are required for ST3DCARDQUERY
|
5
|
+
#
|
6
|
+
# All other fields are optional and also depend on your security policy with SecureTrading
|
7
|
+
#
|
8
|
+
# A further note:
|
9
|
+
# fullname and firstname + lastname are different and end up in different places in the final xml. You can supply fullname as it appears on the card.
|
10
|
+
|
11
|
+
class Customer
|
12
|
+
|
13
|
+
attr_accessor :title, :fullname, :firstname, :lastname, :middlename, :namesuffix, :companyname,
|
14
|
+
:street, :city, :stateprovince, :postcode, :countrycode,
|
15
|
+
:phone, :email,
|
16
|
+
:http_accept, :user_agent
|
17
|
+
|
18
|
+
|
19
|
+
def initialize(options={})
|
20
|
+
options.each { |key, value| self.send("#{key}=", value) if self.respond_to? key } if (!options.nil? && options.is_a?(Hash))
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_to_xml(doc)
|
24
|
+
op = REXML::XPath.first(doc, "//Request")
|
25
|
+
op.delete_element "CustomerInfo"
|
26
|
+
ci = op.add_element "CustomerInfo"
|
27
|
+
postal = ci.add_element("Postal")
|
28
|
+
name = postal.add_element("Name")
|
29
|
+
name.text = self.fullname if self.fullname
|
30
|
+
name.add_element("NamePrefix").add_text(self.title) if self.title
|
31
|
+
name.add_element("FirstName").add_text(self.firstname) if self.firstname
|
32
|
+
name.add_element("MiddleName").add_text(self.middlename) if self.middlename
|
33
|
+
name.add_element("LastName").add_text(self.lastname) if self.lastname
|
34
|
+
name.add_element("NameSuffix").add_text(self.namesuffix) if self.namesuffix
|
35
|
+
postal.add_element("Company").add_text(self.companyname) if self.companyname
|
36
|
+
postal.add_element("Street").add_text(self.street) if self.street
|
37
|
+
postal.add_element("City").add_text(self.city) if self.city
|
38
|
+
postal.add_element("StateProv").add_text(self.stateprovince) if self.stateprovince
|
39
|
+
postal.add_element("PostalCode").add_text(self.postcode) if self.postcode
|
40
|
+
postal.add_element("CountryCode").add_text(self.countrycode) if self.countrycode
|
41
|
+
ci.add_element("Telecom").add_element("Phone").add_text(self.phone) if self.phone
|
42
|
+
ci.add_element("Online").add_element("Email").add_text(self.email) if self.email
|
43
|
+
ci.add_element("Accept").add_text(self.http_accept) if self.http_accept
|
44
|
+
ci.add_element("UserAgent").add_text(self.user_agent) if self.user_agent
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|