epay 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/.gitignore +15 -0
  2. data/.travis.yml +5 -0
  3. data/CHANGELOG.md +13 -0
  4. data/Gemfile +2 -0
  5. data/Guardfile +10 -0
  6. data/MIT-LICENSE +20 -0
  7. data/README.markdown +21 -0
  8. data/Rakefile +8 -0
  9. data/bin/epay-console +8 -0
  10. data/epay.gemspec +55 -0
  11. data/lib/epay.rb +104 -0
  12. data/lib/epay/api.rb +92 -0
  13. data/lib/epay/api/response.rb +29 -0
  14. data/lib/epay/card.rb +23 -0
  15. data/lib/epay/model.rb +23 -0
  16. data/lib/epay/subscription.rb +132 -0
  17. data/lib/epay/transaction.rb +171 -0
  18. data/lib/epay/version.rb +3 -0
  19. data/lib/extensions/hash.rb +4 -0
  20. data/spec/api/response_spec.rb +71 -0
  21. data/spec/api_spec.rb +58 -0
  22. data/spec/card_spec.rb +21 -0
  23. data/spec/fixtures/vcr_cassettes/existing_subscription.yml +75 -0
  24. data/spec/fixtures/vcr_cassettes/existing_transaction.yml +47 -0
  25. data/spec/fixtures/vcr_cassettes/non_existing_transaction.yml +48 -0
  26. data/spec/fixtures/vcr_cassettes/subscription_authorization.yml +95 -0
  27. data/spec/fixtures/vcr_cassettes/subscription_creation.yml +101 -0
  28. data/spec/fixtures/vcr_cassettes/subscription_invalid_creation.yml +47 -0
  29. data/spec/fixtures/vcr_cassettes/subscriptions.yml +87 -0
  30. data/spec/fixtures/vcr_cassettes/transaction_creation.yml +90 -0
  31. data/spec/fixtures/vcr_cassettes/transaction_invalid_creation.yml +47 -0
  32. data/spec/fixtures/vcr_cassettes/transactions.yml +50 -0
  33. data/spec/helpers/http_responses.rb +13 -0
  34. data/spec/model_spec.rb +36 -0
  35. data/spec/spec_helper.rb +20 -0
  36. data/spec/subscription_spec.rb +139 -0
  37. data/spec/transaction_spec.rb +245 -0
  38. metadata +280 -0
@@ -0,0 +1,15 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ rdoc/
6
+ doc/
7
+ .yardoc/
8
+ coverage/
9
+ scratch/
10
+ examples/*.html
11
+ *.log
12
+ .rvmrc
13
+ .rbenv-version
14
+ .DS_Store
15
+ tags
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 1.8.7
5
+ - ree
@@ -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
@@ -0,0 +1,2 @@
1
+ source "http://rubygems.org"
2
+ gemspec
@@ -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
+
@@ -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.
@@ -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/)
@@ -0,0 +1,8 @@
1
+ require "rspec/core/rake_task"
2
+
3
+ task :default => :test
4
+
5
+ RSpec::Core::RakeTask.new(:test) do |spec|
6
+ spec.pattern = 'spec/**/*_spec.rb'
7
+ spec.rspec_opts = ['--backtrace']
8
+ end
@@ -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"
@@ -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
@@ -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
+
@@ -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
@@ -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
@@ -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