epay 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +15 -0
- data/.travis.yml +5 -0
- data/CHANGELOG.md +13 -0
- data/Gemfile +2 -0
- data/Guardfile +10 -0
- data/MIT-LICENSE +20 -0
- data/README.markdown +21 -0
- data/Rakefile +8 -0
- data/bin/epay-console +8 -0
- data/epay.gemspec +55 -0
- data/lib/epay.rb +104 -0
- data/lib/epay/api.rb +92 -0
- data/lib/epay/api/response.rb +29 -0
- data/lib/epay/card.rb +23 -0
- data/lib/epay/model.rb +23 -0
- data/lib/epay/subscription.rb +132 -0
- data/lib/epay/transaction.rb +171 -0
- data/lib/epay/version.rb +3 -0
- data/lib/extensions/hash.rb +4 -0
- data/spec/api/response_spec.rb +71 -0
- data/spec/api_spec.rb +58 -0
- data/spec/card_spec.rb +21 -0
- data/spec/fixtures/vcr_cassettes/existing_subscription.yml +75 -0
- data/spec/fixtures/vcr_cassettes/existing_transaction.yml +47 -0
- data/spec/fixtures/vcr_cassettes/non_existing_transaction.yml +48 -0
- data/spec/fixtures/vcr_cassettes/subscription_authorization.yml +95 -0
- data/spec/fixtures/vcr_cassettes/subscription_creation.yml +101 -0
- data/spec/fixtures/vcr_cassettes/subscription_invalid_creation.yml +47 -0
- data/spec/fixtures/vcr_cassettes/subscriptions.yml +87 -0
- data/spec/fixtures/vcr_cassettes/transaction_creation.yml +90 -0
- data/spec/fixtures/vcr_cassettes/transaction_invalid_creation.yml +47 -0
- data/spec/fixtures/vcr_cassettes/transactions.yml +50 -0
- data/spec/helpers/http_responses.rb +13 -0
- data/spec/model_spec.rb +36 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/subscription_spec.rb +139 -0
- data/spec/transaction_spec.rb +245 -0
- metadata +280 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Changelog: epay
|
2
|
+
|
3
|
+
## 0.0.4
|
4
|
+
- Ruby 1.9.7 and REE compatibility
|
5
|
+
|
6
|
+
## 0.0.3
|
7
|
+
- Add methods for permanent and temporary error
|
8
|
+
|
9
|
+
## 0.0.2
|
10
|
+
- Bugfix: Subscriptions now properly returns failed transaction, if unable to authorize.
|
11
|
+
|
12
|
+
## 0.0.1
|
13
|
+
- Initial release
|
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'rspec', :version => 2 do
|
5
|
+
watch(%r{^spec/.+_spec\.rb$})
|
6
|
+
watch(%r{^lib/.*([^\/]+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
7
|
+
|
8
|
+
watch('spec/spec_helper.rb') { "spec" }
|
9
|
+
end
|
10
|
+
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2012 Mattias Pfeiffer, Netdate.dk ApS
|
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.markdown
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
ePay
|
2
|
+
=========
|
3
|
+
This gem is extracted from Netdate.dk, and gives you a comfortable and easy way of communicating with the ePay API.
|
4
|
+
|
5
|
+
Installation
|
6
|
+
---------
|
7
|
+
Installation is pretty straight-forward. Just install the gem:
|
8
|
+
|
9
|
+
$ gem install epay
|
10
|
+
|
11
|
+
Usage
|
12
|
+
---------
|
13
|
+
See http://pfeiffer.github.com/epay/
|
14
|
+
|
15
|
+
License
|
16
|
+
---------
|
17
|
+
Released under MIT-license.
|
18
|
+
|
19
|
+
-----
|
20
|
+
|
21
|
+
[Mattias Pfeiffer](http://pfeiffer.dk/) - [Netdate.dk](http://netdate.dk/)
|
data/Rakefile
ADDED
data/bin/epay-console
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
|
3
|
+
|
4
|
+
libs = " -r irb/completion"
|
5
|
+
libs << " -r #{File.dirname(__FILE__) + '/../lib/epay'}"
|
6
|
+
load_paths = " -I #{File.dirname(__FILE__) + '/../lib'}"
|
7
|
+
puts "Loading epay gem"
|
8
|
+
exec "#{irb} #{libs} #{load_paths} --simple-prompt"
|
data/epay.gemspec
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "epay/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "epay"
|
7
|
+
s.version = Epay::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.summary = "Ruby client for ePay API"
|
10
|
+
s.homepage = "http://github.com/netdate/epay"
|
11
|
+
s.authors = [ 'Mattias Pfeiffer' ]
|
12
|
+
s.email = 'mattias@netdate.dk'
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
17
|
+
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
|
20
|
+
s.extra_rdoc_files = [ "README.markdown" ]
|
21
|
+
s.rdoc_options = [ "--charset=UTF-8" ]
|
22
|
+
|
23
|
+
s.required_rubygems_version = ">= 1.3.6"
|
24
|
+
|
25
|
+
# = Library dependencies
|
26
|
+
#
|
27
|
+
s.add_dependency "rest-client", "~> 1.6.0"
|
28
|
+
s.add_dependency "activesupport", "~> 3"
|
29
|
+
s.add_dependency "builder", "~> 2.1.2"
|
30
|
+
|
31
|
+
# = Development dependencies
|
32
|
+
#
|
33
|
+
s.add_development_dependency "bundler", "~> 1.0"
|
34
|
+
s.add_development_dependency "yajl-ruby", "~> 0.8.0"
|
35
|
+
s.add_development_dependency "shoulda"
|
36
|
+
s.add_development_dependency "rspec"
|
37
|
+
s.add_development_dependency "webmock", "~>1.7"
|
38
|
+
s.add_development_dependency "nokogiri"
|
39
|
+
s.add_development_dependency "vcr", "~> 2.0.0.rc1"
|
40
|
+
s.add_development_dependency "rake"
|
41
|
+
|
42
|
+
# These gems are not needed for CI
|
43
|
+
#
|
44
|
+
unless ENV["CI"]
|
45
|
+
s.add_development_dependency "guard"
|
46
|
+
s.add_development_dependency "guard-rspec"
|
47
|
+
s.add_development_dependency "rb-fsevent"
|
48
|
+
s.add_development_dependency "rdoc"
|
49
|
+
s.add_development_dependency "turn", "~> 0.9"
|
50
|
+
end
|
51
|
+
|
52
|
+
s.description = <<-DESC
|
53
|
+
Coming soon..
|
54
|
+
DESC
|
55
|
+
end
|
data/lib/epay.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'rest_client'
|
2
|
+
require 'active_support/core_ext'
|
3
|
+
require 'builder'
|
4
|
+
|
5
|
+
require 'extensions/hash'
|
6
|
+
|
7
|
+
require 'epay/version'
|
8
|
+
|
9
|
+
require 'epay/api'
|
10
|
+
require 'epay/api/response'
|
11
|
+
|
12
|
+
require 'epay/model'
|
13
|
+
require 'epay/card'
|
14
|
+
require 'epay/subscription'
|
15
|
+
require 'epay/transaction'
|
16
|
+
|
17
|
+
module Epay
|
18
|
+
API_HOST = 'ssl.ditonlinebetalingssystem.dk'
|
19
|
+
PAYMENT_SOAP_URL = 'https://' + API_HOST + '/remote/payment'
|
20
|
+
SUBSCRIPTION_SOAP_URL = 'https://' + API_HOST + '/remote/subscription'
|
21
|
+
AUTHORIZE_URL = 'https://' + API_HOST + '/auth/default.aspx'
|
22
|
+
|
23
|
+
CURRENCY_CODES = {
|
24
|
+
:ADP => '020', :AED => '784', :AFA => '004', :ALL => '008', :AMD => '051',
|
25
|
+
:ANG => '532', :AOA => '973', :ARS => '032', :AUD => '036', :AWG => '533',
|
26
|
+
:AZM => '031', :BAM => '977', :BBD => '052', :BDT => '050', :BGL => '100',
|
27
|
+
:BGN => '975', :BHD => '048', :BIF => '108', :BMD => '060', :BND => '096',
|
28
|
+
:BOB => '068', :BOV => '984', :BRL => '986', :BSD => '044', :BTN => '064',
|
29
|
+
:BWP => '072', :BYR => '974', :BZD => '084', :CAD => '124', :CDF => '976',
|
30
|
+
:CHF => '756', :CLF => '990', :CLP => '152', :CNY => '156', :COP => '170',
|
31
|
+
:CRC => '188', :CUP => '192', :CVE => '132', :CYP => '196', :CZK => '203',
|
32
|
+
:DJF => '262', :DKK => '208', :DOP => '214', :DZD => '012', :ECS => '218',
|
33
|
+
:ECV => '983', :EEK => '233', :EGP => '818', :ERN => '232', :ETB => '230',
|
34
|
+
:EUR => '978', :FJD => '242', :FKP => '238', :GBP => '826', :GEL => '981',
|
35
|
+
:GHC => '288', :GIP => '292', :GMD => '270', :GNF => '324', :GTQ => '320',
|
36
|
+
:GWP => '624', :GYD => '328', :HKD => '344', :HNL => '340', :HRK => '191',
|
37
|
+
:HTG => '332', :HUF => '348', :IDR => '360', :ILS => '376', :INR => '356',
|
38
|
+
:IQD => '368', :IRR => '364', :ISK => '352', :JMD => '388', :JOD => '400',
|
39
|
+
:JPY => '392', :KES => '404', :KGS => '417', :KHR => '116', :KMF => '174',
|
40
|
+
:KPW => '408', :KRW => '410', :KWD => '414', :KYD => '136', :KZT => '398',
|
41
|
+
:LAK => '418', :LBP => '422', :LKR => '144', :LRD => '430', :LSL => '426',
|
42
|
+
:LTL => '440', :LVL => '428', :LYD => '434', :MAD => '504', :MDL => '498',
|
43
|
+
:MGF => '450', :MKD => '807', :MMK => '104', :MNT => '496', :MOP => '446',
|
44
|
+
:MRO => '478', :MTL => '470', :MUR => '480', :MVR => '462', :MWK => '454',
|
45
|
+
:MXN => '484', :MXV => '979', :MYR => '458', :MZM => '508', :NAD => '516',
|
46
|
+
:NGN => '566', :NIO => '558', :NOK => '578', :NPR => '524', :NZD => '554',
|
47
|
+
:OMR => '512', :PAB => '590', :PEN => '604', :PGK => '598', :PHP => '608',
|
48
|
+
:PKR => '586', :PLN => '985', :PYG => '600', :QAR => '634', :ROL => '642',
|
49
|
+
:RUB => '643', :RUR => '810', :RWF => '646', :SAR => '682', :SBD => '090',
|
50
|
+
:SCR => '690', :SDD => '736', :SEK => '752', :SGD => '702', :SHP => '654',
|
51
|
+
:SIT => '705', :SKK => '703', :SLL => '694', :SOS => '706', :SRG => '740',
|
52
|
+
:STD => '678', :SVC => '222', :SYP => '760', :SZL => '748', :THB => '764',
|
53
|
+
:TJS => '972', :TMM => '795', :TND => '788', :TOP => '776', :TPE => '626',
|
54
|
+
:TRL => '792', :TRY => '949', :TTD => '780', :TWD => '901', :TZS => '834',
|
55
|
+
:UAH => '980', :UGX => '800', :USD => '840', :UYU => '858', :UZS => '860',
|
56
|
+
:VEB => '862', :VND => '704', :VUV => '548', :XAF => '950', :XCD => '951',
|
57
|
+
:XOF => '952', :XPF => '953', :YER => '886', :YUM => '891', :ZAR => '710',
|
58
|
+
:ZMK => '894', :ZWD => '716'
|
59
|
+
}
|
60
|
+
|
61
|
+
CARD_KINDS = {
|
62
|
+
1 => :dankort,
|
63
|
+
2 => :visa_dankort,
|
64
|
+
3 => :visa_electron_foreign,
|
65
|
+
4 => :mastercard,
|
66
|
+
5 => :mastercard_foreign,
|
67
|
+
6 => :visa_electron,
|
68
|
+
7 => :jcb,
|
69
|
+
8 => :diners,
|
70
|
+
9 => :maestro,
|
71
|
+
10 => :american_express,
|
72
|
+
11 => :unknown,
|
73
|
+
12 => :edk,
|
74
|
+
13 => :diners_foreign,
|
75
|
+
14 => :american_express_foreign,
|
76
|
+
15 => :maestro_foreign,
|
77
|
+
16 => :forbrugsforeningen,
|
78
|
+
17 => :ewire,
|
79
|
+
18 => :visa,
|
80
|
+
19 => :ikano,
|
81
|
+
21 => :nordea_solo,
|
82
|
+
22 => :danske_bank,
|
83
|
+
23 => :bg_bank,
|
84
|
+
24 => :lic_mastercard,
|
85
|
+
25 => :lic_mastercard_foreign,
|
86
|
+
26 => :paypal,
|
87
|
+
27 => :mobilpenge
|
88
|
+
}
|
89
|
+
|
90
|
+
TEMPORARY_ERROR_CODES = ['-5511', '100', '102', '116', '121', '255', '256', '906',
|
91
|
+
'907', '910', '911', '912', '915', '920', '921', '923',
|
92
|
+
'945', '946', '-1000', '-1005', '-23', '-3', '-4']
|
93
|
+
|
94
|
+
mattr_accessor :merchant_number, :default_currency, :password
|
95
|
+
|
96
|
+
class ApiError < StandardError; end
|
97
|
+
class TransactionAlreadyCaptured < StandardError; end
|
98
|
+
class TransactionNotFound < StandardError; end
|
99
|
+
class AuthorizationNotFound < StandardError; end
|
100
|
+
class TransactionInGracePeriod < StandardError; end
|
101
|
+
class SubscriptionNotFound < StandardError; end
|
102
|
+
class InvalidMerchantNumber < StandardError; end
|
103
|
+
end
|
104
|
+
|
data/lib/epay/api.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
module Epay
|
2
|
+
module Api
|
3
|
+
class << self
|
4
|
+
def authorize(post)
|
5
|
+
# Authorize transaction:
|
6
|
+
RestClient.post AUTHORIZE_URL, post do |response, request, result|
|
7
|
+
# The authorization request redirects to either accept or decline url:
|
8
|
+
if location = response.headers[:location]
|
9
|
+
query = CGI::parse(URI.parse(location.gsub(' ', '%20')).query)
|
10
|
+
|
11
|
+
Hash[query.map do |k, v|
|
12
|
+
[k, v.is_a?(Array) && v.size == 1 ? v[0] : v] # make values like ['v'] into 'v'
|
13
|
+
end]
|
14
|
+
else
|
15
|
+
# No location header found
|
16
|
+
raise ApiError, response
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def default_post_for_params(params)
|
22
|
+
{
|
23
|
+
:merchantnumber => Epay.merchant_number,
|
24
|
+
|
25
|
+
:cardno => params[:card_no],
|
26
|
+
:cvc => params[:cvc],
|
27
|
+
:expmonth => params[:exp_month],
|
28
|
+
:expyear => params[:exp_year],
|
29
|
+
|
30
|
+
:amount => (params[:amount] * 100).to_i,
|
31
|
+
:currency => Epay::CURRENCY_CODES[(params[:currency] || Epay.default_currency).to_sym],
|
32
|
+
:orderid => params[:order_no],
|
33
|
+
|
34
|
+
:accepturl => AUTHORIZE_URL + "?accept=1",
|
35
|
+
:declineurl => AUTHORIZE_URL + "?decline=1",
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
def handle_failed_response(response)
|
40
|
+
case response.data['epayresponse']
|
41
|
+
when "-1002" then raise InvalidMerchantNumber
|
42
|
+
when "-1008" then raise TransactionNotFound
|
43
|
+
else raise ApiError, response.data['epayresponse']
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def request(url, action, params = {}, &block)
|
48
|
+
service_url = "#{url}.asmx"
|
49
|
+
soap_action = url + '/' + action
|
50
|
+
|
51
|
+
params[:merchantnumber] ||= Epay.merchant_number
|
52
|
+
params[:pwd] = Epay.password if Epay.password.present?
|
53
|
+
|
54
|
+
headers = {
|
55
|
+
'Content-Type' => 'text/xml; charset=utf-8',
|
56
|
+
'SOAPAction' => soap_action,
|
57
|
+
'User-Agent' => "Ruby / epay (#{VERSION})"
|
58
|
+
}
|
59
|
+
|
60
|
+
# Setup the SOAP body:
|
61
|
+
xml = Builder::XmlMarkup.new(:indent => 2)
|
62
|
+
xml.instruct!
|
63
|
+
xml.tag! 'soap:Envelope', { 'xmlns:xsi' => 'http://schemas.xmlsoap.org/soap/envelope/',
|
64
|
+
'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
|
65
|
+
'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/' } do
|
66
|
+
|
67
|
+
xml.tag! 'soap:Body' do
|
68
|
+
xml.tag! action, { 'xmlns' => url } do
|
69
|
+
params.each do |attribute, value|
|
70
|
+
xml.tag! attribute, value if value.present?
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
RestClient.post service_url, xml.target!, headers do |raw_response, request, result|
|
77
|
+
response = Response.new(raw_response, action)
|
78
|
+
|
79
|
+
if block_given?
|
80
|
+
yield response
|
81
|
+
else
|
82
|
+
if response.success?
|
83
|
+
return response
|
84
|
+
else
|
85
|
+
handle_failed_response(response)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Epay
|
2
|
+
module Api
|
3
|
+
class Response
|
4
|
+
attr_accessor :raw_response, :action
|
5
|
+
|
6
|
+
def initialize(raw_response, action)
|
7
|
+
@raw_response = raw_response
|
8
|
+
@action = action
|
9
|
+
end
|
10
|
+
|
11
|
+
def success?
|
12
|
+
code == 200 && data["#{action}Result"] == "true"
|
13
|
+
end
|
14
|
+
|
15
|
+
def data
|
16
|
+
if headers[:content_type] =~ %r(text/xml) && code == 200
|
17
|
+
# Remove envelope and XML namespace objects
|
18
|
+
Hash.from_xml(raw_response.to_s).first.last["Body"]["#{action}Response"].reject { |k,v| k.match(/xmlns/) }
|
19
|
+
else
|
20
|
+
raw_response.to_s
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def method_missing(method, *args)
|
25
|
+
raw_response.send(method, *args)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/epay/card.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module Epay
|
2
|
+
class Card
|
3
|
+
attr_accessor :exp_month, :exp_year, :kind, :number
|
4
|
+
|
5
|
+
def initialize(attributes = {})
|
6
|
+
attributes.each do |name, value|
|
7
|
+
self.send("#{name}=", value) if respond_to?("#{name}=")
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def expires_at
|
12
|
+
Date.new(2000 + exp_year, exp_month, 1).end_of_month
|
13
|
+
end
|
14
|
+
|
15
|
+
def hash
|
16
|
+
[number, exp_year, exp_month].join("") if number.present?
|
17
|
+
end
|
18
|
+
|
19
|
+
def last_digits
|
20
|
+
number[-4, 4] if number.present?
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/epay/model.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module Epay
|
2
|
+
module Model
|
3
|
+
attr_accessor :id, :data
|
4
|
+
|
5
|
+
def initialize(id, data = {})
|
6
|
+
@id = id
|
7
|
+
@data = data
|
8
|
+
end
|
9
|
+
|
10
|
+
def inspect
|
11
|
+
inspection = self.class.inspectable_attributes.collect do |name|
|
12
|
+
#"#{name}: #{selfsend(name)}"
|
13
|
+
"#{name}: #{send(name).inspect}" if respond_to?(name)
|
14
|
+
end.join(", ")
|
15
|
+
|
16
|
+
"#<#{self.class} #{inspection}>"
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.inspect
|
20
|
+
"inspect via class"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|