sisow 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -0
- data/Gemfile +4 -0
- data/README.rdoc +116 -0
- data/Rakefile +2 -0
- data/lib/sisow.rb +40 -0
- data/lib/sisow/api/callback.rb +51 -0
- data/lib/sisow/api/request.rb +67 -0
- data/lib/sisow/api/request/check_merchant_request.rb +40 -0
- data/lib/sisow/api/request/directory_request.rb +25 -0
- data/lib/sisow/api/request/ping_request.rb +25 -0
- data/lib/sisow/api/request/transaction_request.rb +86 -0
- data/lib/sisow/configuration.rb +33 -0
- data/lib/sisow/error_response.rb +14 -0
- data/lib/sisow/exception.rb +4 -0
- data/lib/sisow/issuer.rb +24 -0
- data/lib/sisow/merchant.rb +10 -0
- data/lib/sisow/payment.rb +40 -0
- data/lib/sisow/payment/bancontact_payment.rb +9 -0
- data/lib/sisow/payment/ideal_payment.rb +9 -0
- data/lib/sisow/payment/sofort_payment.rb +9 -0
- data/lib/sisow/ping.rb +9 -0
- data/lib/sisow/version.rb +3 -0
- data/sisow.gemspec +29 -0
- data/spec/models/bancontact_payment_spec.rb +29 -0
- data/spec/models/callback_spec.rb +72 -0
- data/spec/models/configuration_spec.rb +25 -0
- data/spec/models/error_response_spec.rb +15 -0
- data/spec/models/ideal_payment_spec.rb +29 -0
- data/spec/models/issuer_spec.rb +14 -0
- data/spec/models/merchant_spec.rb +11 -0
- data/spec/models/payment_spec.rb +57 -0
- data/spec/models/ping_spec.rb +11 -0
- data/spec/models/request_spec.rb +40 -0
- data/spec/models/sisow_spec.rb +28 -0
- data/spec/models/sofort_payment_spec.rb +29 -0
- data/spec/models/transaction_request_spec.rb +24 -0
- data/spec/sisow.yml +2 -0
- data/spec/sisow.yml.example +2 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/vcr_setup.rb +6 -0
- metadata +177 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
= Sisow
|
2
|
+
|
3
|
+
*NOTE* This gem is work in progress. I'm planning to have it ready somewhere in March.
|
4
|
+
|
5
|
+
This gem provides an interface to interact with the Sisow payment provider. Sisow offers payments through the iDeal (Dutch),
|
6
|
+
Bancontact/Mister Cash (Belgian) and Sofort (German) online payment systems.
|
7
|
+
|
8
|
+
To use this gem, you'll need a payment account at Sisow (you'll need your <tt>merchant key</tt> and <tt>merchant id</tt>).
|
9
|
+
The gem is aimed at Rails 3.2 but it should work on older Rails versions as well as in non-Rails apps.
|
10
|
+
|
11
|
+
== Installation
|
12
|
+
|
13
|
+
To install this gem, simply do <tt>gem install sisow</tt> or add it to your Gemfile:
|
14
|
+
|
15
|
+
gem 'sisow'
|
16
|
+
|
17
|
+
And update your bundle with <tt>bundle install</tt>
|
18
|
+
|
19
|
+
== Usage
|
20
|
+
|
21
|
+
=== Configuration
|
22
|
+
|
23
|
+
To be able to use the gem, you must first configure it. If you're on Rails, insert the following code in <tt>config/initializers/sisow.rb</tt>:
|
24
|
+
|
25
|
+
Sisow.setup do |config|
|
26
|
+
config.merchant_key = 'your-merchant-key'
|
27
|
+
config.merchant_id = 'your-merchant-id'
|
28
|
+
|
29
|
+
#
|
30
|
+
# The following settings are optional
|
31
|
+
#
|
32
|
+
config.test_mode = false # default: false
|
33
|
+
config.debug_mode = false # default: false
|
34
|
+
end
|
35
|
+
|
36
|
+
That's it. Once you restart your Rails application (or open a Rails console) you should be able to communicate with
|
37
|
+
the Sisow API.
|
38
|
+
|
39
|
+
=== Getting a list of issuers
|
40
|
+
|
41
|
+
To set up a payment, your user needs to choose an issuer (a bank) that will fulfill the payment. To fetch a list of Issuers, use the following command:
|
42
|
+
|
43
|
+
Sisow::Issuer.list
|
44
|
+
|
45
|
+
This will return a list of <tt>Sisow::Issuer</tt> objects that have an <tt>id</tt> and a <tt>name</tt>. The <tt>id</tt> is needed
|
46
|
+
to set up the payment in the following step.
|
47
|
+
|
48
|
+
=== Setting up a payment
|
49
|
+
|
50
|
+
After choosing an issuer, your user must be redirected to the payment page for that issuer. For that to happen, you'll have to
|
51
|
+
set up a payment through the Sisow API, after which you'll be given a URL to redirect your user to.
|
52
|
+
|
53
|
+
Setting up a payment looks like this:
|
54
|
+
|
55
|
+
payment_attributes = {
|
56
|
+
:purchase_id => '2012-01-28-33558', # for your own reference
|
57
|
+
:issuer_id => '99', # the issuer id from the previous step
|
58
|
+
:description => 'Acme Inc. payment', # description of this payment
|
59
|
+
:amount => 1299, # amount in Euro in cents
|
60
|
+
:entrance_code => 'foobar-foxtrot', # a verification code you can choose. Cannot contain spaces
|
61
|
+
:return_url => 'http://example.com', # where the user is sent after the payment
|
62
|
+
:cancel_url => 'http://example.com', # where the user is sent when he cancels the payment
|
63
|
+
:callback_url => 'http://example.com', # where a failed (not cancelled) payment will be reported
|
64
|
+
:notify_url => 'http://example.com', # where the payment status will be reported
|
65
|
+
}
|
66
|
+
|
67
|
+
payment = Sisow::IdealPayment.new(payment_attributes)
|
68
|
+
redirect_url = payment.payment_url
|
69
|
+
|
70
|
+
=== Supported payment methods
|
71
|
+
|
72
|
+
This gem supports payments through iDeal, Bancontact/Mister Cash and Sofort. Each of these payment methods have their own class. Payment attributes are the same for each payment method, so in the example above you should only need to switch <tt>Sisow::IdealPayment</tt> for one of the other classes. These are the available class names:
|
73
|
+
|
74
|
+
Sisow::IdealPayment # for iDeal payments
|
75
|
+
Sisow::BancontactPayment # for Bancontact/Mister Cash payments
|
76
|
+
Sisow::SofortPayment # for Sofort payments
|
77
|
+
|
78
|
+
=== Validity checks
|
79
|
+
|
80
|
+
The Sisow API has a few safety measures built in, to prevent malicious users from tampering with your payments. These checks are documented in the Sisow API documentation and are implemented in the gem.
|
81
|
+
|
82
|
+
=== Callbacks
|
83
|
+
|
84
|
+
As documented in the Sisow API documentation, four callbacks are available. When setting up your payment, each of these callbacks can be assigned a URL. These are: <tt>return_url</tt>, <tt>cancel_url</tt>, <tt>callback_url</tt> and <tt>notify_url</tt>. After a successful or failed payment, or when the payment timeout has been reached, the Sisow API will attempt to perform a GET request on the URL's you defined.
|
85
|
+
|
86
|
+
The <tt>Sisow::Api::Callback</tt> can handle these callbacks for you. To initialize such an instance you should provide the following query parameters (which are given by Sisow in the request):
|
87
|
+
|
88
|
+
callback = Sisow::Api::Callback.new(
|
89
|
+
:transaction_id => params[:trxid],
|
90
|
+
:entrance_code => params[:ec],
|
91
|
+
:status => params[:status],
|
92
|
+
:sha1 => params[:sha1]
|
93
|
+
)
|
94
|
+
|
95
|
+
After initializing a <tt>Sisow::Api::Callback</tt> instance, you can check the validity of the callback and check the transaction status:
|
96
|
+
|
97
|
+
callback.validate! # Will raise a Sisow::Exception unless the callback is valid
|
98
|
+
callback.valid? # Will return a boolean to indicate the validity of the callback
|
99
|
+
callback.success? # True if the transaction was successful
|
100
|
+
callback.expired? # True if the transaction has expired
|
101
|
+
callback.cancelled? # True if the transaction was cancelled
|
102
|
+
callback.failure? # True if the transaction has failed
|
103
|
+
|
104
|
+
== Development
|
105
|
+
|
106
|
+
Your contributions are more than welcome. To contribute to this gem, follow these steps:
|
107
|
+
|
108
|
+
1. Fork the repository from Github
|
109
|
+
2. Clone your fork on your development machine
|
110
|
+
3. Install the dependencies with <tt>bundle install</tt>
|
111
|
+
4. Copy <tt>spec/sisow.yml.example</tt> to <tt>spec/sisow.yml</tt> and enter your own Sisow credentials
|
112
|
+
5. Verify your clone is working by running <tt>rspec</tt>
|
113
|
+
6. Hack away
|
114
|
+
7. Run the specs with <tt>rspec</tt>
|
115
|
+
8. Verify spec coverage by opening <tt>coverage/index.html</tt>
|
116
|
+
9. If all is good: send me a pull request
|
data/Rakefile
ADDED
data/lib/sisow.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'httparty'
|
3
|
+
require 'hashie'
|
4
|
+
|
5
|
+
require 'sisow/configuration'
|
6
|
+
require 'sisow/error_response'
|
7
|
+
require 'sisow/exception'
|
8
|
+
require 'sisow/issuer'
|
9
|
+
require 'sisow/ping'
|
10
|
+
require 'sisow/payment'
|
11
|
+
require 'sisow/payment/ideal_payment'
|
12
|
+
require 'sisow/payment/bancontact_payment'
|
13
|
+
require 'sisow/payment/sofort_payment'
|
14
|
+
require 'sisow/merchant'
|
15
|
+
require 'sisow/api/request'
|
16
|
+
require 'sisow/api/request/directory_request'
|
17
|
+
require 'sisow/api/request/ping_request'
|
18
|
+
require 'sisow/api/request/transaction_request'
|
19
|
+
require 'sisow/api/request/check_merchant_request'
|
20
|
+
require 'sisow/api/callback'
|
21
|
+
|
22
|
+
module Sisow
|
23
|
+
|
24
|
+
class << self
|
25
|
+
|
26
|
+
def service_reachable?
|
27
|
+
ping = Sisow::Ping.send
|
28
|
+
end
|
29
|
+
|
30
|
+
def configure
|
31
|
+
yield configuration
|
32
|
+
end
|
33
|
+
|
34
|
+
def configuration
|
35
|
+
@configuration ||= Configuration.new
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Sisow
|
2
|
+
module Api
|
3
|
+
class Callback
|
4
|
+
|
5
|
+
attr_accessor :transaction_id,
|
6
|
+
:entrance_code,
|
7
|
+
:status,
|
8
|
+
:sha1
|
9
|
+
|
10
|
+
def initialize(attributes = {})
|
11
|
+
attributes.each do |k,v|
|
12
|
+
send("#{k}=", v)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def valid?
|
17
|
+
valid_callback == true
|
18
|
+
end
|
19
|
+
|
20
|
+
def validate!
|
21
|
+
raise Sisow::Exception, "This callback is forged" and return if valid_callback == false
|
22
|
+
end
|
23
|
+
|
24
|
+
def success?
|
25
|
+
@status == 'Success'
|
26
|
+
end
|
27
|
+
|
28
|
+
def expired?
|
29
|
+
@status == 'Expired'
|
30
|
+
end
|
31
|
+
|
32
|
+
def cancelled?
|
33
|
+
@status == 'Cancelled'
|
34
|
+
end
|
35
|
+
|
36
|
+
def failure?
|
37
|
+
@status == 'Failure'
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def valid_callback
|
43
|
+
string = [ @transaction_id, @entrance_code, @status, Sisow.configuration.merchant_id, Sisow.configuration.merchant_key ].join
|
44
|
+
calculated_sha1 = Digest::SHA1.hexdigest(string)
|
45
|
+
|
46
|
+
calculated_sha1 == @sha1
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Sisow
|
2
|
+
module Api
|
3
|
+
class Request
|
4
|
+
|
5
|
+
include HTTParty
|
6
|
+
base_uri "http://www.sisow.nl/Sisow/iDeal/RestHandler.ashx/"
|
7
|
+
|
8
|
+
def self.perform
|
9
|
+
new.perform
|
10
|
+
end
|
11
|
+
|
12
|
+
def perform
|
13
|
+
raise Sisow::Exception, 'Your merchant_id or merchant_key are not set' unless can_perform?
|
14
|
+
|
15
|
+
validate!
|
16
|
+
|
17
|
+
response = self.class.get(uri)
|
18
|
+
response = Hashie::Mash.new(response)
|
19
|
+
|
20
|
+
error!(response) if response.errorresponse?
|
21
|
+
|
22
|
+
clean(response)
|
23
|
+
end
|
24
|
+
|
25
|
+
def default_params
|
26
|
+
{
|
27
|
+
:merchantid => Sisow.configuration.merchant_id,
|
28
|
+
:merchantkey => Sisow.configuration.merchant_key,
|
29
|
+
:test => Sisow.configuration.test_mode_enabled?? test_mode_param : nil
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
def params; raise 'Implement me in a subclass'; end
|
34
|
+
def method; raise 'Implement me in a subclass'; end
|
35
|
+
def clean; raise 'Implement me in a subclass'; end
|
36
|
+
def validate!; raise 'Implement me in a subclass'; end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def can_perform?
|
41
|
+
!Sisow.configuration.merchant_id.empty? && !Sisow.configuration.merchant_key.empty?
|
42
|
+
end
|
43
|
+
|
44
|
+
def uri
|
45
|
+
[ '/', method, '?', encoded_params ].join
|
46
|
+
end
|
47
|
+
|
48
|
+
def params_string
|
49
|
+
params.map { |k,v| [ k, '=', v ].join }.join('&')
|
50
|
+
end
|
51
|
+
|
52
|
+
def encoded_params
|
53
|
+
URI.encode(params_string)
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_mode_param
|
57
|
+
'true'
|
58
|
+
end
|
59
|
+
|
60
|
+
def error!(response)
|
61
|
+
error_response = Sisow::ErrorResponse.new(response)
|
62
|
+
raise Sisow::Exception, error_response.message and return
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Sisow
|
2
|
+
class CheckMerchantRequest < Sisow::Api::Request
|
3
|
+
|
4
|
+
def method
|
5
|
+
'CheckMerchantRequest'
|
6
|
+
end
|
7
|
+
|
8
|
+
def params
|
9
|
+
default_params.merge!(merchant_params)
|
10
|
+
end
|
11
|
+
|
12
|
+
def clean(response)
|
13
|
+
if response.checkmerchantresponse? && response.checkmerchantresponse.merchant?
|
14
|
+
response.checkmerchantresponse.merchant.payments
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def validate!
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def merchant_params
|
25
|
+
{
|
26
|
+
:sha1 => sha1
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
def sha1
|
31
|
+
string = [
|
32
|
+
Sisow.configuration.merchant_id,
|
33
|
+
Sisow.configuration.merchant_key
|
34
|
+
].join
|
35
|
+
|
36
|
+
Digest::SHA1.hexdigest(string)
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Sisow
|
2
|
+
module Api
|
3
|
+
class DirectoryRequest < Request
|
4
|
+
|
5
|
+
def method
|
6
|
+
'DirectoryRequest'
|
7
|
+
end
|
8
|
+
|
9
|
+
def params
|
10
|
+
default_params
|
11
|
+
end
|
12
|
+
|
13
|
+
def clean(response)
|
14
|
+
if response.directoryresponse? && response.directoryresponse.directory?
|
15
|
+
response.directoryresponse.directory
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def validate!
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Sisow
|
2
|
+
module Api
|
3
|
+
class PingRequest < Request
|
4
|
+
|
5
|
+
def method
|
6
|
+
'PingRequest'
|
7
|
+
end
|
8
|
+
|
9
|
+
def params
|
10
|
+
default_params
|
11
|
+
end
|
12
|
+
|
13
|
+
def clean(response)
|
14
|
+
if response.pingresponse? && response.pingresponse.timestamp?
|
15
|
+
response.pingresponse.timestamp
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def validate!
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module Sisow
|
2
|
+
module Api
|
3
|
+
class TransactionRequest < Request
|
4
|
+
|
5
|
+
attr_accessor :purchase_id,
|
6
|
+
:issuer_id,
|
7
|
+
:description,
|
8
|
+
:amount,
|
9
|
+
:payment
|
10
|
+
|
11
|
+
def initialize(payment)
|
12
|
+
@payment = payment
|
13
|
+
end
|
14
|
+
|
15
|
+
def method
|
16
|
+
'TransactionRequest'
|
17
|
+
end
|
18
|
+
|
19
|
+
def params
|
20
|
+
default_params.merge!(transaction_params)
|
21
|
+
end
|
22
|
+
|
23
|
+
def clean(response)
|
24
|
+
check_validity!(response)
|
25
|
+
|
26
|
+
if response.transactionrequest? && response.transactionrequest.transaction?
|
27
|
+
response.transactionrequest.transaction
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def validate!
|
32
|
+
raise Sisow::Exception, 'One of your payment parameters is missing or invalid' unless @payment.valid?
|
33
|
+
end
|
34
|
+
|
35
|
+
def sha1
|
36
|
+
string = [
|
37
|
+
payment.purchase_id,
|
38
|
+
payment.entrance_code,
|
39
|
+
payment.amount,
|
40
|
+
Sisow.configuration.merchant_id,
|
41
|
+
Sisow.configuration.merchant_key
|
42
|
+
].join
|
43
|
+
|
44
|
+
Digest::SHA1.hexdigest(string)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def transaction_params
|
50
|
+
{
|
51
|
+
:payment => payment.payment_method,
|
52
|
+
:purchaseid => payment.purchase_id,
|
53
|
+
:amount => payment.amount,
|
54
|
+
:issuerid => payment.issuer_id,
|
55
|
+
:description => payment.description,
|
56
|
+
:entrancecode => payment.entrance_code,
|
57
|
+
:returnurl => payment.return_url,
|
58
|
+
:cancelurl => payment.cancel_url,
|
59
|
+
:callbackurl => payment.callback_url,
|
60
|
+
:notifyurl => payment.notify_url,
|
61
|
+
:shop_id => payment.shop_id,
|
62
|
+
:sha1 => sha1
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
def payment
|
67
|
+
@payment
|
68
|
+
end
|
69
|
+
|
70
|
+
def check_validity!(response)
|
71
|
+
string = [
|
72
|
+
response.transactionrequest.transaction.trxid,
|
73
|
+
response.transactionrequest.transaction.issuerurl,
|
74
|
+
Sisow.configuration.merchant_id,
|
75
|
+
Sisow.configuration.merchant_key
|
76
|
+
].join
|
77
|
+
calculated_sha1 = Digest::SHA1.hexdigest(string)
|
78
|
+
|
79
|
+
if calculated_sha1 != response.transactionrequest.signature.sha1
|
80
|
+
raise Sisow::Exception, "This response has been forged" and return
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|