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 ADDED
@@ -0,0 +1,12 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
6
+ *~
7
+ *.gem
8
+ tmp
9
+ log
10
+ .yardoc
11
+ doc
12
+ .idea
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source :gemcutter
2
+
3
+ # Specify your gem's dependencies in efcm.gemspec
4
+ gemspec
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