buckaroo_client 0.0.1.pre

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2911056e32cd71be8234f88bf7dd089fe06adadf
4
+ data.tar.gz: cfbec2e5be8084042f248a16bd456aae797d187b
5
+ SHA512:
6
+ metadata.gz: f5c6d596217c75f66e093138cfe01170f55f2d6a1080a7437a1db9e7856384548cd7726f6900ffe321d10fcec58dab96364889eb065f16ad1571979f14afbd6c
7
+ data.tar.gz: 1b1bd2230444f5a8154af5800c92a8a7dc19a27856332f9d280bbd3c3ca4d41581e402ef16ed1194ae768e9bf9ba45555eac5bc8557ff0f4d87eb84595f26561
data/.gitignore ADDED
@@ -0,0 +1,15 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ .env
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in buckaroo_client.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Floris Huetink
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,98 @@
1
+ # BuckarooClient
2
+
3
+ Ruby support for Buckaroo Payment Engine 3.0
4
+
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'buckaroo_client'
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install buckaroo_client
21
+
22
+
23
+ ## Usage
24
+
25
+ ### Setup
26
+
27
+ Set the following `ENV` vars in your application:
28
+
29
+ * `BUCKAROO_CLIENT_WEBSITEKEY`: website key as generated by Buckaroo
30
+ * `BUCKAROO_CLIENT_SECRET`: shared secret to digitally sign API requests
31
+ * `BUCKAROO_CLIENT_ENVIRONMENT`: set this to `production` to create real
32
+ transactions. Defaults to `test`.
33
+
34
+ Or alternatively, configure using a block (e.g. in a Rails initializer script):
35
+
36
+ ```ruby
37
+ BuckarooClient.configure do |c|
38
+ c.websitekey = 'yourwebsitekey'
39
+ c.secret = 'randomsharedsecretstring'
40
+ c.environment = 'production'
41
+ end
42
+ ```
43
+
44
+ ### Creating a transaction
45
+
46
+ Start by creating a base transaction:
47
+
48
+ ```ruby
49
+ transaction = BuckarooClient.transaction(amount: 9.99, description: 'Payment')
50
+ ```
51
+
52
+ After that, you must select a primary service:
53
+
54
+ ```ruby
55
+ transaction.select_service(:pay_per_email) do |s|
56
+ s.customeremail = 'example@example.com'
57
+ # ... and some more values
58
+ end
59
+ ```
60
+
61
+ Optionally select additional payment services as you please:
62
+
63
+ ```ruby
64
+ transaction.select_additional_service(:invoice_specification) do |s|
65
+ s.add_invoice_line(description: 'Some Product', amount: 10.00)
66
+ s.add_total_line(description: 'Total', amount: 10.00)
67
+ end
68
+ transaction.select_additional_service(:credit_management) do |s|
69
+ s.invoice_date = Date.current
70
+ s.date_due = s.invoice_date.next_day(14)
71
+ end
72
+ ```
73
+
74
+ ### Sending data to Buckaroo Payment Engine
75
+
76
+ Use `BuckarooClient.gateway` to set up Buckaroo NVP Gateway transactions:
77
+
78
+ ```ruby
79
+ BuckarooClient.gateway.transaction_request(transaction.gateway_attributes)
80
+ ```
81
+
82
+ This will send a signed `POST` request to the Buckaroo gateway.
83
+
84
+
85
+ ## Known limitations
86
+
87
+ * This gem currently only supports PayPerEmail transactions.
88
+ * Batch file creation is experimental and cannot handle invoices with
89
+ mixed numbers of invoice lines.
90
+
91
+
92
+ ## Contributing
93
+
94
+ 1. Fork it ( https://github.com/brightin/buckaroo_client/fork )
95
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
96
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
97
+ 4. Push to the branch (`git push origin my-new-feature`)
98
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'buckaroo_client/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "buckaroo_client"
8
+ spec.version = BuckarooClient::VERSION
9
+ spec.authors = ["Floris Huetink"]
10
+ spec.email = ["floris@brightin.nl"]
11
+ spec.summary = %q{Ruby support for Buckaroo Payment Engine 3.0}
12
+ spec.homepage = "https://github.com/brightin/buckaroo_client"
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_dependency "addressable"
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.7"
23
+ spec.add_development_dependency "dotenv", "~> 1.0"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ spec.add_development_dependency "rspec", "~> 3.0"
26
+ end
@@ -0,0 +1,35 @@
1
+ require 'buckaroo_client/gateway'
2
+ require 'buckaroo_client/service'
3
+ require 'buckaroo_client/transaction'
4
+ require 'buckaroo_client/version'
5
+
6
+ module BuckarooClient
7
+ DEFAULT_TRANSACTION_ATTRIBUTES = {
8
+ websitekey: ENV['BUCKAROO_CLIENT_WEBSITEKEY']
9
+ }
10
+
11
+ def self.gateway
12
+ Gateway::NVP
13
+ end
14
+
15
+ def self.batch(attributes = {})
16
+ Gateway::Batch.new(attributes)
17
+ end
18
+
19
+ def self.transaction(attributes = {})
20
+ Transaction.new(DEFAULT_TRANSACTION_ATTRIBUTES.merge(attributes))
21
+ end
22
+
23
+ def self.service(name, attributes = {})
24
+ case name.to_s
25
+ when 'credit_management'
26
+ Service::CreditManagement.new
27
+ when 'invoice_specification'
28
+ Service::InvoiceSpecification.new
29
+ when 'pay_per_email'
30
+ Service::PayPerEmail.new(attributes)
31
+ else
32
+ raise ArgumentError.new("service '#{name}' does not exist")
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,7 @@
1
+ require 'buckaroo_client/gateway/batch'
2
+ require 'buckaroo_client/gateway/nvp'
3
+
4
+ module BuckarooClient
5
+ module Gateway
6
+ end
7
+ end
@@ -0,0 +1,31 @@
1
+ require 'forwardable'
2
+ require 'csv'
3
+
4
+ module BuckarooClient
5
+ module Gateway
6
+ class Batch
7
+ attr_reader :transactions
8
+
9
+ extend Forwardable
10
+ def_delegators :@transactions, :<<, :size, :each
11
+
12
+ def initialize(args = {})
13
+ @transactions = args.fetch(:transactions, [])
14
+ end
15
+
16
+ def to_csv
17
+ table = CSV::Table.new([])
18
+ transactions.each { |t| table << transaction_to_csv(t) }
19
+ table.to_csv(col_sep: ';')
20
+ end
21
+
22
+ private
23
+
24
+ def transaction_to_csv(transaction)
25
+ data = transaction.gateway_attributes
26
+ CSV::Row.new(data.keys, data.values)
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,84 @@
1
+ require 'addressable/uri'
2
+ require 'digest/sha1'
3
+ require 'buckaroo_client/gateway/nvp/response'
4
+ require 'buckaroo_client/gateway/nvp/signature'
5
+
6
+ module BuckarooClient
7
+ module Gateway
8
+ module NVP
9
+ extend Signature
10
+
11
+ def self.environment
12
+ ENV['BUCKAROO_CLIENT_ENVIRONMENT'] || 'test'
13
+ end
14
+
15
+ def self.websitekey
16
+ ENV['BUCKAROO_CLIENT_WEBSITEKEY'] || raise("BUCKAROO_CLIENT_WEBSITEKEY not set")
17
+ end
18
+
19
+ def self.url(action: nil)
20
+ path = case environment
21
+ when 'production'
22
+ 'https://checkout.buckaroo.nl/nvp/'
23
+ else
24
+ 'https://testcheckout.buckaroo.nl/nvp/'
25
+ end
26
+ path += "?op=#{action}" unless action.nil?
27
+ URI.parse(path)
28
+ end
29
+
30
+ def self.transaction_request(buckaroo_variables, custom: {}, additional: {})
31
+ do_request('transactionrequest', buckaroo_variables, custom, additional)
32
+ end
33
+
34
+ def self.transaction_status(buckaroo_variables, custom: {}, additional: {})
35
+ do_request('transactionstatus', buckaroo_variables, custom, additional)
36
+ end
37
+
38
+ def self.invoice_info(buckaroo_variables, custom: {}, additional: {})
39
+ do_request('invoiceinfo', buckaroo_variables, custom, additional)
40
+ end
41
+
42
+ def self.transaction_request_specification(buckaroo_variables, custom: {}, additional: {})
43
+ do_request('transactionrequestspecification', buckaroo_variables, custom, additional)
44
+ end
45
+
46
+ private
47
+
48
+ def self.do_request(action, buckaroo_variables, custom = {}, additional = {})
49
+ nvp_data = prefixed_and_signed_request_data(buckaroo_variables, custom, additional)
50
+ Response.for_action(action, post_data(action, nvp_data))
51
+ end
52
+
53
+ def self.post_data(action, data)
54
+ Net::HTTP.post_form(url(action: action), data)
55
+ end
56
+
57
+ def self.prefixed_and_signed_request_data(buckaroo, custom, additional)
58
+ output = prefix_if_needed(data: buckaroo, prefix: 'brq_')
59
+ output.merge!(prefix_if_needed(data: custom, prefix: 'cust_'))
60
+ output.merge!(prefix_if_needed(data: additional, prefix: 'add_'))
61
+ output['brq_websitekey'] ||= websitekey
62
+ if output.key?('brq_service')
63
+ output['brq_payment_method'] = output.delete('brq_service')
64
+ end
65
+ sign!(output)
66
+ return output
67
+ end
68
+
69
+ def self.prefix_if_needed(prefix:, data:)
70
+ output = {}
71
+ data.each do |key, value|
72
+ prefixed_key = key.to_s.index(prefix) == 0 ? key : "#{prefix}#{key}"
73
+ output[prefixed_key] = value
74
+ end
75
+ return output
76
+ end
77
+
78
+ def self.sign!(input)
79
+ input['brq_signature'] = signature(input)
80
+ input
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,16 @@
1
+ require 'buckaroo_client/gateway/nvp/response'
2
+
3
+ module BuckarooClient
4
+ module Gateway
5
+ module NVP
6
+ class InvoiceInfoResponse < Response
7
+
8
+ def paid?
9
+ return nil unless response.key?('BRQ_INVOICE_1_PAID')
10
+ response['BRQ_INVOICE_1_PAID'].to_s.downcase == 'true'
11
+ end
12
+
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,56 @@
1
+ # Partly taken from:
2
+ # https://github.com/inventid/buckaroo/blob/develop/lib/buckaroo/response.rb
3
+ require 'addressable/uri'
4
+ require 'digest/sha1'
5
+ require 'buckaroo_client/gateway/nvp/signature'
6
+
7
+ module BuckarooClient
8
+ module Gateway
9
+ module NVP
10
+ class Response
11
+
12
+ include Signature
13
+
14
+ attr_reader :body, :response, :status_code, :success
15
+
16
+ def self.for_action(action, response)
17
+ klass = case action
18
+ when 'invoiceinfo'
19
+ require 'buckaroo_client/gateway/nvp/invoice_info_response'
20
+ InvoiceInfoResponse
21
+ else
22
+ Response
23
+ end
24
+ klass.new(response)
25
+ end
26
+
27
+ def initialize(response)
28
+ @body = response.body
29
+ @response = Hash[Addressable::URI.form_unencode(body)]
30
+ @status_code = @response['BRQ_STATUSCODE'].to_i
31
+ @success = !error_occurred?
32
+ end
33
+
34
+ alias_method :success?, :success
35
+
36
+ def verify!
37
+ verified? or raise "Response signature does not match expected value"
38
+ end
39
+
40
+ def verified?
41
+ input = response.dup
42
+ given_hash = input['BRQ_SIGNATURE']
43
+ input.delete('BRQ_SIGNATURE')
44
+ signature(input) == given_hash
45
+ end
46
+
47
+ private
48
+
49
+ def error_occurred?
50
+ !verified? || status_code == 491 || status_code == 492
51
+ end
52
+
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,38 @@
1
+ require 'digest/sha1'
2
+
3
+ module BuckarooClient
4
+ module Gateway
5
+ module NVP
6
+ module Signature
7
+ def secret_key
8
+ ENV['BUCKAROO_CLIENT_SECRET'] || raise("BUCKAROO_CLIENT_SECRET not set")
9
+ end
10
+
11
+ def signature(input)
12
+ # Base logic and comments taken from github.com/inventid/buckaroo
13
+ #
14
+ # This might actually need some explanation why we are converting do lowercase here
15
+ # BuckarooClient specifies to sort these parameters, although the exact matter of sorting
16
+ # is quite ambigious. So after quite a while of debugging, I discovered that by
17
+ # sorting they do not use the ASCII based sorting Ruby uses. In fact, the sorting
18
+ # is specified to place symbols first (which ASCII does, except for the underscore (_)
19
+ # which is located between the capitals and lowercase letters (jeej ASCII!).
20
+ # So in this case, by converting everything to lowercase before comparing, we ensure
21
+ # that all symbols are in the table before the letters.
22
+ #
23
+ # Actual case where it went wrong: keys BRQ_TRANSACTIONS and BRQ_TRANSACTION_CANCELABLE
24
+ # Ruby would sort these in this exact order, whereas BuckarooClient would reverse them. And
25
+ # since for hashing the reversal generates a totally different sequence, that would
26
+ # break message validation.
27
+ #
28
+ # TLDR; Leave it with a downcase
29
+ sorted_data = input.sort_by { |key, _| key.to_s.downcase }
30
+ to_hash = ''
31
+ sorted_data.each { |key, value| to_hash << key.to_s+'='+value.to_s }
32
+ to_hash << secret_key
33
+ Digest::SHA1.hexdigest(to_hash)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end