sisow 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/.gitignore +7 -0
  2. data/Gemfile +4 -0
  3. data/README.rdoc +116 -0
  4. data/Rakefile +2 -0
  5. data/lib/sisow.rb +40 -0
  6. data/lib/sisow/api/callback.rb +51 -0
  7. data/lib/sisow/api/request.rb +67 -0
  8. data/lib/sisow/api/request/check_merchant_request.rb +40 -0
  9. data/lib/sisow/api/request/directory_request.rb +25 -0
  10. data/lib/sisow/api/request/ping_request.rb +25 -0
  11. data/lib/sisow/api/request/transaction_request.rb +86 -0
  12. data/lib/sisow/configuration.rb +33 -0
  13. data/lib/sisow/error_response.rb +14 -0
  14. data/lib/sisow/exception.rb +4 -0
  15. data/lib/sisow/issuer.rb +24 -0
  16. data/lib/sisow/merchant.rb +10 -0
  17. data/lib/sisow/payment.rb +40 -0
  18. data/lib/sisow/payment/bancontact_payment.rb +9 -0
  19. data/lib/sisow/payment/ideal_payment.rb +9 -0
  20. data/lib/sisow/payment/sofort_payment.rb +9 -0
  21. data/lib/sisow/ping.rb +9 -0
  22. data/lib/sisow/version.rb +3 -0
  23. data/sisow.gemspec +29 -0
  24. data/spec/models/bancontact_payment_spec.rb +29 -0
  25. data/spec/models/callback_spec.rb +72 -0
  26. data/spec/models/configuration_spec.rb +25 -0
  27. data/spec/models/error_response_spec.rb +15 -0
  28. data/spec/models/ideal_payment_spec.rb +29 -0
  29. data/spec/models/issuer_spec.rb +14 -0
  30. data/spec/models/merchant_spec.rb +11 -0
  31. data/spec/models/payment_spec.rb +57 -0
  32. data/spec/models/ping_spec.rb +11 -0
  33. data/spec/models/request_spec.rb +40 -0
  34. data/spec/models/sisow_spec.rb +28 -0
  35. data/spec/models/sofort_payment_spec.rb +29 -0
  36. data/spec/models/transaction_request_spec.rb +24 -0
  37. data/spec/sisow.yml +2 -0
  38. data/spec/sisow.yml.example +2 -0
  39. data/spec/spec_helper.rb +26 -0
  40. data/spec/vcr_setup.rb +6 -0
  41. metadata +177 -0
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ lib/sisow.yml
6
+ coverage/
7
+ spec/vcr_cassettes
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in sisow.gemspec
4
+ gemspec
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
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
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