xpay 0.0.5
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/.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
|