buckaroo_client 0.0.1.pre
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +98 -0
- data/Rakefile +2 -0
- data/buckaroo_client.gemspec +26 -0
- data/lib/buckaroo_client.rb +35 -0
- data/lib/buckaroo_client/gateway.rb +7 -0
- data/lib/buckaroo_client/gateway/batch.rb +31 -0
- data/lib/buckaroo_client/gateway/nvp.rb +84 -0
- data/lib/buckaroo_client/gateway/nvp/invoice_info_response.rb +16 -0
- data/lib/buckaroo_client/gateway/nvp/response.rb +56 -0
- data/lib/buckaroo_client/gateway/nvp/signature.rb +38 -0
- data/lib/buckaroo_client/service.rb +8 -0
- data/lib/buckaroo_client/service/credit_management.rb +71 -0
- data/lib/buckaroo_client/service/invoice_specification.rb +92 -0
- data/lib/buckaroo_client/service/pay_per_email.rb +40 -0
- data/lib/buckaroo_client/transaction.rb +39 -0
- data/lib/buckaroo_client/version.rb +3 -0
- data/spec/buckaroo_client/gateway/batch_spec.rb +54 -0
- data/spec/buckaroo_client/gateway/nvp_spec.rb +173 -0
- data/spec/buckaroo_client/service/credit_management_spec.rb +52 -0
- data/spec/buckaroo_client/service/invoice_specification_spec.rb +44 -0
- data/spec/buckaroo_client/service/pay_per_email_spec.rb +68 -0
- data/spec/buckaroo_client/transaction_spec.rb +73 -0
- data/spec/spec_helper.rb +89 -0
- metadata +148 -0
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
data/.rspec
ADDED
data/Gemfile
ADDED
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,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,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
|