active_merchant_mollie 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Edwin Vlieg
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,56 @@
1
+ = Active Merchant for Mollie iDeal
2
+
3
+ This gem contains an extension of ActiveMerchant [http://www.activemerchant.org] with support for the Dutch payment service provider Mollie [http://www.mollie.nl]. The extension allows you to make iDeal transactions based on the Mollie iDeal API.
4
+
5
+ == Installation
6
+
7
+ To install as a gem using Bundler, add the following to your Gemfile:
8
+
9
+ gem "active_merchant_mollie"
10
+
11
+ Run bundle install to install the gem.
12
+
13
+ You can also install as a Rails plugin:
14
+
15
+ ./script/plugin install git://github.com/bluetools/active_merchant_mollie.git
16
+
17
+ == Usage
18
+
19
+ Before you start using this gem, please read the API documentation of Mollie on their website: https://www.mollie.nl/beheer/betaaldiensten/documentatie/ideal. Make sure you have a Mollie account and know your 'partner id'.
20
+
21
+ === Create a new purchase
22
+
23
+ @gateway = ActiveMerchant::Billing::MollieIdealGateway.new(:partner_id => your_partner_id)
24
+ @response = @gateway.setup_purchase(1000, {
25
+ :return_url => "http://yourwebsite.com/ideal/return",
26
+ :report_url => "http://yourwebsite.com/ideal/report",
27
+ :bank_id => bank_id,
28
+ :description => "Description of this transaction"
29
+ })
30
+
31
+ # Store the transaction id in your database with a reference to the original order
32
+ @transaction_id = @response.token
33
+
34
+ # Now redirect the user to the selected bank
35
+ redirect_to @gateway.redirect_url_for( @response.token )
36
+
37
+ === Receive details of purchase
38
+
39
+ After the user returns on your website or Mollie requests you report URL, you should check the state of the purchase. Based in the transaction id you can update the state of your order in your database.
40
+
41
+ # The token usually sits in the transaction_id GET parameter
42
+ @token = params[:transaction_id]
43
+
44
+ @gateway = ActiveMerchant::Billing::MollieIdealGateway.new(:partner_id => your_partner_id)
45
+ @details_response = @gateway.details_for( @token )
46
+
47
+ if @details_response.success?
48
+ # The payment was successfull, update the state in your database
49
+ else
50
+ # Something went wrong, inspect the error message
51
+ puts @details_response.message
52
+ end
53
+
54
+ == Maintainer
55
+
56
+ This gem is based on ActiveMerchant and abstracted from the MoneyBird [http://www.moneybird.nl] project. For more information ask the MoneyBird team via mailto:info@moneybird.com
data/Rakefile ADDED
@@ -0,0 +1,52 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "active_merchant_mollie"
8
+ gem.summary = "ActiveMerchant extension to support the Dutch PSP Mollie with iDeal transactions"
9
+ gem.email = "info@moneybird.com"
10
+ gem.homepage = "http://github.com/bluetools/active_merchant_mollie"
11
+ gem.description = "ActiveMerchant extension to support the Dutch PSP Mollie with iDeal transactions"
12
+ gem.authors = ["Edwin Vlieg"]
13
+ gem.files = FileList["[A-Z]*", "{bin,generators,lib,test}/**/*"]
14
+ gem.add_dependency 'active_merchant'
15
+ gem.add_dependency 'hpricot'
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
20
+ end
21
+
22
+
23
+ require 'spec/rake/spectask'
24
+ desc "Run all examples"
25
+ Spec::Rake::SpecTask.new('spec') do |t|
26
+ t.spec_files = FileList['spec/*_spec.rb']
27
+ end
28
+
29
+ begin
30
+ require 'rcov/rcovtask'
31
+ Rcov::RcovTask.new do |test|
32
+ test.libs << 'test'
33
+ test.pattern = 'test/**/test_*.rb'
34
+ test.verbose = true
35
+ end
36
+ rescue LoadError
37
+ task :rcov do
38
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
39
+ end
40
+ end
41
+
42
+ task :default => :spec
43
+
44
+ require 'rake/rdoctask'
45
+ Rake::RDocTask.new do |rdoc|
46
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
47
+
48
+ rdoc.rdoc_dir = 'rdoc'
49
+ rdoc.title = "active_merchant_mollie #{version}"
50
+ rdoc.rdoc_files.include('README*')
51
+ rdoc.rdoc_files.include('lib/**/*.rb')
52
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,86 @@
1
+ require "hpricot"
2
+
3
+ module ActiveMerchant
4
+ module Billing
5
+ class MollieIdealGateway < Gateway
6
+
7
+ URL = "https://secure.mollie.nl/xml/ideal"
8
+
9
+ def initialize(options={})
10
+ requires!(options, :partner_id)
11
+
12
+ @options = options
13
+ end
14
+
15
+ def setup_purchase(money, options)
16
+ requires!(options, :return_url, :report_url, :bank_id, :description)
17
+
18
+ raise ArgumentError.new("Amount should be at least 1,80EUR") if money < 180
19
+
20
+ @response = build_response_fetch(commit("fetch", {
21
+ :amount => money,
22
+ :bank_id => options[:bank_id],
23
+ :description => CGI::escape(options[:description] || ""),
24
+ :partnerid => @options[:partner_id],
25
+ :reporturl => options[:report_url],
26
+ :returnurl => options[:return_url]
27
+ }))
28
+ end
29
+
30
+ def redirect_url_for(token)
31
+ @response.url if @response.token == token
32
+ end
33
+
34
+ def details_for(token)
35
+ build_response_check(commit('check', {
36
+ :partnerid => @options[:partner_id],
37
+ :transaction_id => token,
38
+ :testmode => ActiveMerchant::Billing::Base.test?
39
+ }))
40
+ end
41
+
42
+ private
43
+
44
+ def commit(action, parameters)
45
+ url = URL + "?a=#{action}&#{parameters.collect { |k,v| "#{k}=#{v}" }.join("&") }"
46
+ uri = URI.parse(url)
47
+ http = Net::HTTP.new(uri.host, uri.port)
48
+ http.get(uri.request_uri).body
49
+ end
50
+
51
+ def build_response_fetch(response)
52
+ vars = {}
53
+ doc = Hpricot.XML(response)
54
+ success = false
55
+ if doc.search("response/item").size > 0
56
+ errorcode = doc.at('response/item/errorcode').inner_text
57
+ message = doc.at('response/item/message').inner_text + " (#{errorcode})"
58
+ elsif doc.search("response/order").size > 0
59
+ vars = {}
60
+ resp = doc.at('response/order')
61
+ if resp && resp.at('amount') && resp.at('transaction_id') && resp.at('URL')
62
+ vars[:amount] = resp.at('amount').inner_text
63
+ vars[:transaction_id] = resp.at('transaction_id').inner_text
64
+ vars[:url] = resp.at('URL').inner_text
65
+ end
66
+ success = true
67
+ end
68
+ MollieIdealFetchResponse.new(success, message, vars)
69
+ end
70
+
71
+ def build_response_check(response)
72
+ doc = Hpricot.XML(response)
73
+ success = false
74
+ if doc.search("response/item").size > 0
75
+ errorcode = doc.at('response/item/errorcode').inner_text
76
+ message = doc.at('response/item/message').inner_text + " (#{errorcode})"
77
+ elsif doc.search("response/order").size > 0
78
+ resp = doc.at('response/order')
79
+ success = resp && resp.at('payed') && resp.at('payed').inner_text.downcase == "true"
80
+ end
81
+ MollieIdealCheckResponse.new(success, message)
82
+ end
83
+
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,7 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class MollieIdealCheckResponse < Response
4
+
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,19 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class MollieIdealFetchResponse < Response
4
+
5
+ def token
6
+ @params['transaction_id']
7
+ end
8
+
9
+ def url
10
+ @params['url']
11
+ end
12
+
13
+ def amount
14
+ @params['amount']
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,6 @@
1
+ require 'active_merchant'
2
+ require 'active_merchant/billing/mollie_ideal'
3
+ require 'active_merchant/billing/mollie_ideal_check_response'
4
+ require 'active_merchant/billing/mollie_ideal_fetch_response'
5
+
6
+ VERSION = "0.0.1"
@@ -0,0 +1,232 @@
1
+ require "spec_helper.rb"
2
+
3
+ describe "Mollie iDeal implementation for ActiveMerchant" do
4
+
5
+ it "should create a new billing gateway with a required partner id" do
6
+ ActiveMerchant::Billing::MollieIdealGateway.new(:partner_id => 123456).should be_kind_of(ActiveMerchant::Billing::MollieIdealGateway)
7
+ end
8
+
9
+ it "should throw an error if a gateway is created without a partner id" do
10
+ lambda {
11
+ ActiveMerchant::Billing::MollieIdealGateway.new
12
+ }.should raise_error(ArgumentError)
13
+ end
14
+
15
+ context "setup purchase" do
16
+
17
+ before do
18
+ @partner_id = 123456
19
+ @return_url = "http://www.example.com/mollie/return"
20
+ @report_url = "http://www.example.com/mollie/report"
21
+ @bank_id = "0031"
22
+ @description = "This is a test transaction"
23
+ @gateway = ActiveMerchant::Billing::MollieIdealGateway.new(:partner_id => @partner_id)
24
+ end
25
+
26
+ it "should create a new purchase via the Mollie API" do
27
+ http_mock = mock(Net::HTTP)
28
+ Net::HTTP.should_receive(:new).with("secure.mollie.nl", 443).and_return(http_mock)
29
+
30
+ response_mock = mock(Net::HTTPResponse)
31
+ response_mock.should_receive(:body).and_return('<?xml version="1.0"?>
32
+ <response>
33
+ <order>
34
+ <transaction_id>482d599bbcc7795727650330ad65fe9b</transaction_id>
35
+ <amount>123</amount>
36
+ <currency>EUR</currency>
37
+ <URL>https://mijn.postbank.nl/internetbankieren/SesamLoginServlet?sessie=ideal&trxid=003123456789123&random=123456789abcdefgh</URL>
38
+ <message>Your iDEAL-payment has succesfuly been setup. Your customer should visit the given URL to make the payment</message>
39
+ </order>
40
+ </response>')
41
+
42
+ http_mock.should_receive(:get) do |url|
43
+ { :partnerid => @partner_id, :returnurl => @return_url, :reporturl => @report_url, :description => CGI::escape(@description), :bank_id => @bank_id }.each do |param, value|
44
+ url.should include("#{param}=#{value}")
45
+ end
46
+ response_mock
47
+ end
48
+
49
+ @response = @gateway.setup_purchase(1000, {
50
+ :return_url => @return_url,
51
+ :report_url => @report_url,
52
+ :bank_id => @bank_id,
53
+ :description => @description
54
+ })
55
+
56
+ @response.token.should == "482d599bbcc7795727650330ad65fe9b"
57
+ @gateway.redirect_url_for(@response.token).should == "https://mijn.postbank.nl/internetbankieren/SesamLoginServlet?sessie=ideal&trxid=003123456789123&random=123456789abcdefgh"
58
+ end
59
+
60
+ it "should not allow a purchase without a return url" do
61
+ lambda {
62
+ @gateway.setup_purchase(1000, {
63
+ :report_url => @report_url,
64
+ :bank_id => @bank_id,
65
+ :description => @description
66
+ })
67
+ }.should raise_error(ArgumentError)
68
+ end
69
+
70
+ it "should not allow a purchase without a report url" do
71
+ lambda {
72
+ @gateway.setup_purchase(1000, {
73
+ :return_url => @return_url,
74
+ :bank_id => @bank_id,
75
+ :description => @description
76
+ })
77
+ }.should raise_error(ArgumentError)
78
+ end
79
+
80
+ it "should not allow a purchase without a bank id" do
81
+ lambda {
82
+ @gateway.setup_purchase(1000, {
83
+ :return_url => @return_url,
84
+ :report_url => @report_url,
85
+ :description => @description
86
+ })
87
+ }.should raise_error(ArgumentError)
88
+ end
89
+
90
+ it "should not allow a purchase without a description" do
91
+ lambda {
92
+ @gateway.setup_purchase(1000, {
93
+ :return_url => @return_url,
94
+ :report_url => @report_url,
95
+ :bank_id => @bank_id
96
+ })
97
+ }.should raise_error(ArgumentError)
98
+ end
99
+
100
+ it "should not allow a purchase with less than 1,80EUR" do
101
+ lambda {
102
+ @gateway.setup_purchase(179, {
103
+ :return_url => @return_url,
104
+ :report_url => @report_url,
105
+ :bank_id => @bank_id,
106
+ :description => @description
107
+ })
108
+ }.should raise_error(ArgumentError)
109
+ end
110
+
111
+ it "should return information about the error Mollie is throwing" do
112
+ http_mock = mock(Net::HTTP)
113
+ Net::HTTP.should_receive(:new).with("secure.mollie.nl", 443).and_return(http_mock)
114
+
115
+ response_mock = mock(Net::HTTPResponse)
116
+ response_mock.should_receive(:body).and_return('<?xml version="1.0"?>
117
+ <response>
118
+ <item>
119
+ <errorcode>-10</errorcode>
120
+ <message>This is an invalid order</message>
121
+ </item>
122
+ </response>')
123
+
124
+ http_mock.should_receive(:get).and_return(response_mock)
125
+
126
+ @response = @gateway.setup_purchase(1000, {
127
+ :return_url => @return_url,
128
+ :report_url => @report_url,
129
+ :bank_id => @bank_id,
130
+ :description => @description
131
+ })
132
+ @response.success?.should be_false
133
+ @response.message.should == "This is an invalid order (-10)"
134
+ end
135
+
136
+ end
137
+
138
+ context "check details" do
139
+
140
+ before do
141
+ @partner_id = 123456
142
+ @token = "482d599bbcc7795727650330ad65fe9b"
143
+ @gateway = ActiveMerchant::Billing::MollieIdealGateway.new(:partner_id => @partner_id)
144
+ end
145
+
146
+ it "should return information about a successfull transaction" do
147
+ http_mock = mock(Net::HTTP)
148
+ Net::HTTP.should_receive(:new).with("secure.mollie.nl", 443).and_return(http_mock)
149
+
150
+ response_mock = mock(Net::HTTPResponse)
151
+ response_mock.should_receive(:body).and_return('<?xml version="1.0"?>
152
+ <response>
153
+ <order>
154
+ <transaction_id>482d599bbcc7795727650330ad65fe9b</transaction_id>
155
+ <amount>123</amount>
156
+ <currency>EUR</currency>
157
+ <payed>true</payed>
158
+ <consumer>
159
+ <consumerName>Hr J Janssen</consumerName>
160
+ <consumerAccount>P001234567</consumerAccount>
161
+ <consumerCity>Amsterdam</consumerCity>
162
+ </consumer>
163
+ <message>This iDEAL-order has successfuly been payed for, and this is the first time you check it.</message>
164
+ </order>
165
+ </response>')
166
+
167
+ http_mock.should_receive(:get) do |url|
168
+ { :partnerid => @partner_id, :transaction_id => @token }.each do |param, value|
169
+ url.should include("#{param}=#{value}")
170
+ end
171
+ response_mock
172
+ end
173
+
174
+ @details_response = @gateway.details_for(@token)
175
+ @details_response.success?.should be_true
176
+ end
177
+
178
+ it "should return information about a successfull transaction" do
179
+ http_mock = mock(Net::HTTP)
180
+ Net::HTTP.should_receive(:new).with("secure.mollie.nl", 443).and_return(http_mock)
181
+
182
+ response_mock = mock(Net::HTTPResponse)
183
+ response_mock.should_receive(:body).and_return('<?xml version="1.0"?>
184
+ <response>
185
+ <order>
186
+ <transaction_id>482d599bbcc7795727650330ad65fe9b</transaction_id>
187
+ <amount>123</amount>
188
+ <currency>EUR</currency>
189
+ <payed>false</payed>
190
+ <message>This iDEAL-order has successfuly been payed for, and this is the first time you check it.</message>
191
+ </order>
192
+ </response>')
193
+
194
+ http_mock.should_receive(:get) do |url|
195
+ { :partnerid => @partner_id, :transaction_id => @token }.each do |param, value|
196
+ url.should include("#{param}=#{value}")
197
+ end
198
+ response_mock
199
+ end
200
+
201
+ @details_response = @gateway.details_for(@token)
202
+ @details_response.success?.should be_false
203
+ end
204
+
205
+ it "should return information about the error Mollie is throwing" do
206
+ http_mock = mock(Net::HTTP)
207
+ Net::HTTP.should_receive(:new).with("secure.mollie.nl", 443).and_return(http_mock)
208
+
209
+ response_mock = mock(Net::HTTPResponse)
210
+ response_mock.should_receive(:body).and_return('<?xml version="1.0"?>
211
+ <response>
212
+ <item>
213
+ <errorcode>-10</errorcode>
214
+ <message>This is an invalid order</message>
215
+ </item>
216
+ </response>')
217
+
218
+ http_mock.should_receive(:get) do |url|
219
+ { :partnerid => @partner_id, :transaction_id => @token }.each do |param, value|
220
+ url.should include("#{param}=#{value}")
221
+ end
222
+ response_mock
223
+ end
224
+
225
+ @details_response = @gateway.details_for(@token)
226
+ @details_response.success?.should be_false
227
+ @details_response.message.should == "This is an invalid order (-10)"
228
+ end
229
+
230
+ end
231
+
232
+ end
@@ -0,0 +1,11 @@
1
+ require "rubygems"
2
+ require "spec"
3
+ require "active_merchant"
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+
8
+ require "active_merchant_mollie"
9
+
10
+ ActiveMerchant::Billing::Base.mode = :test
11
+
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: active_merchant_mollie
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Edwin Vlieg
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-04-18 00:00:00 +02:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: active_merchant
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ version: "0"
30
+ type: :runtime
31
+ version_requirements: *id001
32
+ - !ruby/object:Gem::Dependency
33
+ name: hpricot
34
+ prerelease: false
35
+ requirement: &id002 !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ segments:
40
+ - 0
41
+ version: "0"
42
+ type: :runtime
43
+ version_requirements: *id002
44
+ description: ActiveMerchant extension to support the Dutch PSP Mollie with iDeal transactions
45
+ email: info@moneybird.com
46
+ executables: []
47
+
48
+ extensions: []
49
+
50
+ extra_rdoc_files:
51
+ - LICENSE
52
+ - README.rdoc
53
+ files:
54
+ - LICENSE
55
+ - README.rdoc
56
+ - Rakefile
57
+ - VERSION
58
+ - lib/active_merchant/billing/mollie_ideal.rb
59
+ - lib/active_merchant/billing/mollie_ideal_check_response.rb
60
+ - lib/active_merchant/billing/mollie_ideal_fetch_response.rb
61
+ - lib/active_merchant_mollie.rb
62
+ has_rdoc: true
63
+ homepage: http://github.com/bluetools/active_merchant_mollie
64
+ licenses: []
65
+
66
+ post_install_message:
67
+ rdoc_options:
68
+ - --charset=UTF-8
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ segments:
76
+ - 0
77
+ version: "0"
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ segments:
83
+ - 0
84
+ version: "0"
85
+ requirements: []
86
+
87
+ rubyforge_project:
88
+ rubygems_version: 1.3.6
89
+ signing_key:
90
+ specification_version: 3
91
+ summary: ActiveMerchant extension to support the Dutch PSP Mollie with iDeal transactions
92
+ test_files:
93
+ - spec/mollie_ideal_spec.rb
94
+ - spec/spec_helper.rb