buckaruby 1.1.1 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,8 +2,10 @@
2
2
 
3
3
  module Buckaruby
4
4
  # Helper for calculating the IBAN for a given account number, bank code and country code.
5
- class Iban
6
- def self.calculate_iban(account_number, bank_code, country_code = "NL")
5
+ module Iban
6
+ module_function
7
+
8
+ def calculate_iban(account_number, bank_code, country_code = "NL")
7
9
  if account_number.nil? || account_number.to_s.empty?
8
10
  raise ArgumentError, "Invalid account number"
9
11
  end
@@ -4,6 +4,7 @@ module Buckaruby
4
4
  module Operation
5
5
  TRANSACTION_REQUEST = "TransactionRequest"
6
6
  TRANSACTION_STATUS = "TransactionStatus"
7
+ TRANSACTION_REQUEST_SPECIFICATION = "TransactionRequestSpecification"
7
8
  REFUND_INFO = "RefundInfo"
8
9
  CANCEL_TRANSACTION = "CancelTransaction"
9
10
  end
@@ -8,11 +8,13 @@ module Buckaruby
8
8
  SEPA_DIRECT_DEBIT = "sepadirectdebit"
9
9
  PAYPAL = "paypal"
10
10
  BANCONTACT_MISTER_CASH = "bancontactmrcash"
11
+ SOFORT = "sofortueberweisung"
11
12
  TRANSFER = "transfer"
12
13
 
13
14
  # Credit cards
14
15
  VISA = "visa"
15
16
  MASTER_CARD = "mastercard"
16
17
  MAESTRO = "maestro"
18
+ AMERICAN_EXPRESS = "amex"
17
19
  end
18
20
  end
@@ -50,14 +50,16 @@ module Buckaruby
50
50
  # Try to catch some common exceptions Net::HTTP might raise
51
51
  rescue Errno::ETIMEDOUT, Errno::EINVAL, Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::EHOSTUNREACH,
52
52
  IOError, SocketError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::OpenTimeout,
53
- Net::ProtocolError, Net::ReadTimeout, OpenSSL::SSL::SSLError => ex
54
- raise ConnectionException, ex
53
+ Net::ProtocolError, Net::ReadTimeout, OpenSSL::SSL::SSLError => e
54
+ raise ConnectionException, e
55
55
  end
56
56
 
57
57
  def build_request_data(options)
58
58
  params = { brq_websitekey: @config.website }
59
59
 
60
60
  params.merge!(build_request_params(options))
61
+ params.merge!(build_custom_params(options[:custom])) if options[:custom]
62
+ params.merge!(build_additional_params(options[:additional])) if options[:additional]
61
63
 
62
64
  params[:add_buckaruby] = "Buckaruby #{Buckaruby::VERSION}"
63
65
 
@@ -67,6 +69,14 @@ module Buckaruby
67
69
  params
68
70
  end
69
71
 
72
+ def build_custom_params(options)
73
+ options.map { |key, value| [:"cust_#{key}", value] }.to_h
74
+ end
75
+
76
+ def build_additional_params(options)
77
+ options.map { |key, value| [:"add_#{key}", value] }.to_h
78
+ end
79
+
70
80
  def post_data(params)
71
81
  params.map { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&")
72
82
  end
@@ -149,6 +159,30 @@ module Buckaruby
149
159
  end
150
160
  end
151
161
 
162
+ # Request for a creating a transaction specification.
163
+ class TransactionSpecificationRequest < Request
164
+ def execute(options)
165
+ super(options.merge(operation: Operation::TRANSACTION_REQUEST_SPECIFICATION))
166
+ end
167
+
168
+ def build_request_params(options)
169
+ params = {}
170
+
171
+ if options[:payment_method]
172
+ if options[:payment_method].respond_to?(:join)
173
+ params[:brq_services] = options[:payment_method].join(",")
174
+ else
175
+ params[:brq_services] = options[:payment_method]
176
+ end
177
+ end
178
+
179
+ params[:brq_latestversiononly] = "true"
180
+ params[:brq_culture] = options[:culture] || Language::DUTCH
181
+
182
+ params
183
+ end
184
+ end
185
+
152
186
  # Request for a creating a recurrent transaction.
153
187
  class RecurrentTransactionRequest < TransactionRequest
154
188
  def build_transaction_request_params(options)
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'support/case_insensitive_hash'
4
+
3
5
  require 'cgi'
4
6
  require 'date'
5
7
 
@@ -20,25 +22,43 @@ module Buckaruby
20
22
  end
21
23
 
22
24
  def status
23
- # See http://support.buckaroo.nl/index.php/Statuscodes
24
- case params[:brq_statuscode]
25
- when '190'
26
- TransactionStatus::SUCCESS
27
- when '490', '491', '492'
28
- TransactionStatus::FAILED
29
- when '690'
30
- TransactionStatus::REJECTED
31
- when '790', '791', '792', '793'
32
- TransactionStatus::PENDING
33
- when '890', '891'
34
- TransactionStatus::CANCELLED
35
- end
25
+ TransactionStatus.parse(params[:brq_statuscode])
36
26
  end
37
27
 
38
28
  def timestamp
39
29
  parse_time(params[:brq_timestamp])
40
30
  end
41
31
 
32
+ def custom
33
+ @custom ||= begin
34
+ custom = Support::CaseInsensitiveHash.new
35
+
36
+ params.each do |key, value|
37
+ next unless key.upcase.start_with?("CUST_")
38
+
39
+ new_key = key.to_s[5..-1]
40
+ custom[new_key] = value
41
+ end
42
+
43
+ custom
44
+ end
45
+ end
46
+
47
+ def additional
48
+ @additional ||= begin
49
+ additional = Support::CaseInsensitiveHash.new
50
+
51
+ params.each do |key, value|
52
+ next unless key.upcase.start_with?("ADD_")
53
+
54
+ new_key = key.to_s[4..-1]
55
+ additional[new_key] = value
56
+ end
57
+
58
+ additional
59
+ end
60
+ end
61
+
42
62
  private
43
63
 
44
64
  def parse_response(body)
@@ -53,7 +73,7 @@ module Buckaruby
53
73
  end
54
74
 
55
75
  def verify_signature!(response, config)
56
- if params[:brq_apiresult] != "Fail"
76
+ if params[:brq_apiresult].nil? || params[:brq_apiresult].casecmp("fail") != 0
57
77
  sent_signature = params[:brq_signature]
58
78
  generated_signature = Signature.generate_signature(response, config)
59
79
 
@@ -144,27 +164,7 @@ module Buckaruby
144
164
  end
145
165
 
146
166
  def transaction_type
147
- if params[:brq_transaction_type] && !params[:brq_transaction_type].empty?
148
- # See http://support.buckaroo.nl/index.php/Transactietypes
149
- case params[:brq_transaction_type]
150
- when 'C001', 'C002', 'C004', 'C021', 'C043', 'C044', 'C046', 'C089', 'C090', 'C192', 'C251', 'V001', 'V002', 'V010', 'V021', 'V032', 'V034', 'V043', 'V044', 'V046', 'V089', 'V090', 'V192', 'V245'
151
- # Check the recurring flag to detect a normal or recurring transaction.
152
- if params[:brq_recurring] && params[:brq_recurring].casecmp("true").zero?
153
- TransactionType::PAYMENT_RECURRENT
154
- else
155
- TransactionType::PAYMENT
156
- end
157
- when 'C005', 'V014', 'V031', 'V094'
158
- TransactionType::PAYMENT_RECURRENT
159
- when 'C079', 'C080', 'C082', 'C092', 'C101', 'C102', 'C121', 'C194', 'C197', 'C252', 'C500', 'V067', 'V068', 'V070', 'V079', 'V080', 'V082', 'V092', 'V101', 'V102', 'V110', 'V149', 'V194', 'V197', 'V246'
160
- TransactionType::REFUND
161
- when 'C501', 'C502', 'C546', 'C551', 'C553', 'C554', 'C562', 'C589', 'C593', 'V111', 'V131', 'V132', 'V134', 'V143', 'V144', 'V146', 'V543', 'V544', 'V545', 'V546', 'V589', 'V592'
162
- TransactionType::REVERSAL
163
- end
164
- else
165
- # Fallback when transaction type is not known (cancelling credit card)
166
- TransactionType::PAYMENT
167
- end
167
+ TransactionType.parse(params[:brq_transaction_type], params[:brq_recurring])
168
168
  end
169
169
 
170
170
  def transaction_status
@@ -269,4 +269,19 @@ module Buckaruby
269
269
  class CallbackResponse < Response
270
270
  include TransactionResponse
271
271
  end
272
+
273
+ # Response when retrieving the specification for a transaction.
274
+ class TransactionSpecificationResponse < ApiResponse
275
+ def services
276
+ @services ||= FieldMapper.map_fields(params, :brq_services)
277
+ end
278
+
279
+ def basic_fields
280
+ @basic_fields ||= FieldMapper.map_fields(params, :brq_basicfields)
281
+ end
282
+
283
+ def custom_parameters
284
+ @custom_parameters ||= FieldMapper.map_fields(params, :brq_customparameters)
285
+ end
286
+ end
272
287
  end
@@ -5,8 +5,10 @@ require 'digest'
5
5
  module Buckaruby
6
6
  # Calculate a signature based on the parameters of the payment request or response.
7
7
  # -> see BPE 3.0 Gateway NVP, chapter 4 'Digital Signature'
8
- class Signature
9
- def self.generate_signature(params, config)
8
+ module Signature
9
+ module_function
10
+
11
+ def generate_signature(params, config)
10
12
  case config.hash_method
11
13
  when :sha1
12
14
  Digest::SHA1.hexdigest(generate_signature_string(params, config.secret))
@@ -19,11 +21,27 @@ module Buckaruby
19
21
  end
20
22
  end
21
23
 
22
- def self.generate_signature_string(params, secret)
24
+ def generate_signature_string(params, secret)
23
25
  sign_params = params.select { |key, _value| key.to_s.upcase.start_with?("BRQ_", "ADD_", "CUST_") && key.to_s.casecmp("BRQ_SIGNATURE").nonzero? }
24
- string = sign_params.sort_by { |p| p.first.downcase }.map { |param| "#{param[0]}=#{param[1]}" }.join
26
+ sign_params = order_signature_params(sign_params)
27
+
28
+ string = sign_params.map { |param| "#{param[0]}=#{param[1]}" }.join
25
29
  string << secret
26
30
  string
27
31
  end
32
+
33
+ # Excerpt from the Buckaroo documentation, chapter 4 'Digital Signature':
34
+ # In the payment engine, the used lexical sort algorithm uses the following order:
35
+ # symbols first, then numbers, then case insensitive letters. Also, a shorter string
36
+ # that is identical to the beginning of a longer string, comes before the longer string.
37
+ # Take for example the following, comma separated, list which has been sorted:
38
+ # a_a, a0, a0a, a1a, aaA, aab, aba, aCa
39
+ CHAR_ORDER = "_01234567890abcdefghijklmnopqrstuvwxyz"
40
+
41
+ def order_signature_params(params)
42
+ params.sort_by do |key, _value|
43
+ key.to_s.downcase.each_char.map { |c| CHAR_ORDER.index(c) }
44
+ end
45
+ end
28
46
  end
29
47
  end
@@ -1,11 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Buckaruby
4
+ # Parses the transaction status code from Buckaroo.
4
5
  module TransactionStatus
5
6
  SUCCESS = 1
6
7
  FAILED = 2
7
8
  REJECTED = 3
8
9
  CANCELLED = 4
9
10
  PENDING = 5
11
+
12
+ module_function
13
+
14
+ # See https://support.buckaroo.nl/categorie%C3%ABn/transacties/status
15
+ def parse(brq_statuscode)
16
+ case brq_statuscode
17
+ when '190'
18
+ TransactionStatus::SUCCESS
19
+ when '490', '491', '492'
20
+ TransactionStatus::FAILED
21
+ when '690'
22
+ TransactionStatus::REJECTED
23
+ when '790', '791', '792', '793'
24
+ TransactionStatus::PENDING
25
+ when '890', '891'
26
+ TransactionStatus::CANCELLED
27
+ end
28
+ end
10
29
  end
11
30
  end
@@ -1,10 +1,89 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Buckaruby
4
+ # Parses the transaction type from Buckaroo.
4
5
  module TransactionType
5
6
  PAYMENT = 1
6
7
  PAYMENT_RECURRENT = 2
7
8
  REFUND = 3
8
9
  REVERSAL = 4
10
+
11
+ module_function
12
+
13
+ # See https://support.buckaroo.nl/categorie%C3%ABn/integratie/transactietypes-overzicht
14
+ def parse(brq_transaction_type, brq_recurring)
15
+ if brq_transaction_type && !brq_transaction_type.empty?
16
+ case brq_transaction_type
17
+ when 'C021', 'V021', # iDEAL
18
+ 'C002', 'C004', 'C005', # (SEPA) Direct Debit
19
+ 'V010', 'V014', # PayPal
20
+ 'C090', 'V090', # Bancontact
21
+ 'N074', 'C075', # Sofort
22
+ 'C001', # Transfer
23
+
24
+ 'C044', 'C192', 'C283', 'C293', 'C318', 'C345',
25
+ 'C880', 'C963', 'V002', 'V032', 'V038', 'V044',
26
+ 'V192', 'V283', 'V293', 'V313', 'V318', 'V345',
27
+ 'V696', # Visa
28
+
29
+ 'C043', 'C089', 'C273', 'C303', 'C328', 'C355',
30
+ 'C876', 'C969', 'V001', 'V031', 'V037', 'V043',
31
+ 'V089', 'V273', 'V303', 'V328', 'V355', 'V702', # MasterCard
32
+
33
+ 'C046', 'C251', 'C288', 'C308', 'C333', 'C872',
34
+ 'C972', 'V034', 'V040', 'V046', 'V094', 'V245',
35
+ 'V288', 'V308', 'V333', 'V705', # Maestro
36
+
37
+ 'V003', 'V030', 'V036', 'V042' # American Express
38
+
39
+ # Check the recurring flag to detect a normal or recurring transaction.
40
+ if brq_recurring && brq_recurring.casecmp("true").zero?
41
+ TransactionType::PAYMENT_RECURRENT
42
+ else
43
+ TransactionType::PAYMENT
44
+ end
45
+ when 'C121', # iDEAL
46
+ 'C102', 'C500', # (SEPA) Direct Debit
47
+ 'V110', # PayPal
48
+ 'C092', 'V092', # Bancontact
49
+ 'N540', 'C543', # Sofort
50
+ 'C101', # Transfer
51
+
52
+ 'C080', 'C194', 'C281', 'C290', 'C315', 'C342',
53
+ 'C881', 'C961', 'V068', 'V074', 'V080', 'V102',
54
+ 'V194', 'V281', 'V290', 'V315', 'V342', 'V694', # Visa
55
+
56
+ 'C079', 'C197', 'C300', 'C325', 'C352', 'C371',
57
+ 'C877', 'C967', 'V067', 'V073', 'V079', 'V101',
58
+ 'V149', 'V197', 'V300', 'V325', 'V352', 'V371',
59
+ 'V700', # MasterCard
60
+
61
+ 'C082', 'C252', 'C286', 'C305', 'C330', 'C873',
62
+ 'C970', 'V070', 'V076', 'V082', 'V246', 'V286',
63
+ 'V305', 'V330', 'V703', # Maestro
64
+
65
+ 'V066', 'V072', 'V078', 'V103' # American Express
66
+ TransactionType::REFUND
67
+ when 'C501', 'C502', 'C562', # (SEPA) Direct Debit
68
+ 'V111', # PayPal
69
+ 'C544', # Sofort
70
+
71
+ 'C554', 'C593', 'C882', 'V132', 'V138', 'V144',
72
+ 'V544', 'V592', # Visa
73
+
74
+ 'C553', 'C589', 'C878', 'V131', 'V137', 'V143',
75
+ 'V543', 'V589', # MasterCard
76
+
77
+ 'C546', 'C551', 'C874', 'V134', 'V140', 'V146',
78
+ 'V545', 'V546', # Maestro
79
+
80
+ 'V130', 'V136', 'V142' # American Express
81
+ TransactionType::REVERSAL
82
+ end
83
+ else
84
+ # Fallback when transaction type is not known (e.g. cancelling credit card)
85
+ TransactionType::PAYMENT
86
+ end
87
+ end
9
88
  end
10
89
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Buckaruby
4
- VERSION = "1.1.1"
4
+ VERSION = "1.5.0"
5
5
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'spec_helper'
4
4
 
5
- describe Buckaruby::Configuration do
5
+ RSpec.describe Buckaruby::Configuration do
6
6
  describe '#website' do
7
7
  it 'sets the website from options' do
8
8
  config = Buckaruby::Configuration.new(website: "12345678", secret: "7C222FB2927D828AF22F592134E8932480637C0D")
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Buckaruby::FieldMapper do
6
+ describe '.map_fields' do
7
+ let(:fields) do
8
+ {
9
+ brq_services_1_description: "iDEAL",
10
+ brq_services_1_name: "ideal",
11
+ brq_services_1_version: "2",
12
+ brq_services_1_supportedcurrencies_1_code: "EUR",
13
+ brq_services_1_supportedcurrencies_1_isonumber: "978",
14
+ brq_services_1_supportedcurrencies_1_name: "Euro",
15
+ brq_services_2_description: "Visa",
16
+ brq_services_2_name: "visa",
17
+ brq_services_2_version: "1",
18
+ brq_services_2_supportedcurrencies_1_code: "GBP",
19
+ brq_services_2_supportedcurrencies_1_isonumber: "826",
20
+ brq_services_2_supportedcurrencies_1_name: "Pond",
21
+ brq_services_2_supportedcurrencies_2_code: "USD",
22
+ brq_services_2_supportedcurrencies_2_isonumber: "840",
23
+ brq_services_2_supportedcurrencies_2_name: "US Dollar"
24
+ }
25
+ end
26
+
27
+ it 'maps NVP fields to arrays and hashes' do
28
+ services = Buckaruby::FieldMapper.map_fields(fields, :brq_services)
29
+ expect(services).to be_an_instance_of(Array)
30
+ expect(services.count).to eq(2)
31
+
32
+ ideal = services.first
33
+ expect(ideal).to be_an_instance_of(Buckaruby::Support::CaseInsensitiveHash)
34
+ expect(ideal[:description]).to eq("iDEAL")
35
+ expect(ideal[:name]).to eq("ideal")
36
+ expect(ideal[:version]).to eq("2")
37
+
38
+ visa = services.last
39
+ expect(visa).to be_an_instance_of(Buckaruby::Support::CaseInsensitiveHash)
40
+ expect(visa[:description]).to eq("Visa")
41
+ expect(visa[:name]).to eq("visa")
42
+ expect(visa[:version]).to eq("1")
43
+ end
44
+
45
+ it 'maps NVP subfields to arrays and hashes' do
46
+ services = Buckaruby::FieldMapper.map_fields(fields, :brq_services)
47
+
48
+ currencies = services.first[:supportedcurrencies]
49
+ expect(currencies).to be_an_instance_of(Array)
50
+ expect(currencies.count).to eq(1)
51
+
52
+ currency = currencies.first
53
+ expect(currency).to be_an_instance_of(Buckaruby::Support::CaseInsensitiveHash)
54
+ expect(currency[:code]).to eq("EUR")
55
+ expect(currency[:isonumber]).to eq("978")
56
+ expect(currency[:name]).to eq("Euro")
57
+
58
+ currencies = services.last[:supportedcurrencies]
59
+ expect(currencies).to be_an_instance_of(Array)
60
+ expect(currencies.count).to eq(2)
61
+
62
+ currency = currencies.first
63
+ expect(currency).to be_an_instance_of(Buckaruby::Support::CaseInsensitiveHash)
64
+ expect(currency[:code]).to eq("GBP")
65
+ expect(currency[:isonumber]).to eq("826")
66
+ expect(currency[:name]).to eq("Pond")
67
+ end
68
+ end
69
+ end