buckaruby 1.3.1 → 1.7.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
@@ -8,12 +8,13 @@ module Buckaruby
8
8
  "BUNQNL2A" => "bunq",
9
9
  "HANDNL2A" => "Handelsbanken",
10
10
  "INGBNL2A" => "ING",
11
- "KNABNL2H" => "Knab bank",
11
+ "KNABNL2H" => "Knab",
12
12
  "MOYONL21" => "Moneyou",
13
13
  "RABONL2U" => "Rabobank",
14
- "RBRBNL21" => "RegioBank",
14
+ "RBRBNL21" => "Regiobank",
15
+ "REVOLT21" => "Revolut",
15
16
  "SNSBNL2A" => "SNS Bank",
16
- "TRIONL2U" => "Triodos Bank",
17
+ "TRIONL2U" => "Triodos",
17
18
  "FVLBNL22" => "Van Lanschot"
18
19
  }.freeze
19
20
  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,6 +8,8 @@ module Buckaruby
8
8
  SEPA_DIRECT_DEBIT = "sepadirectdebit"
9
9
  PAYPAL = "paypal"
10
10
  BANCONTACT_MISTER_CASH = "bancontactmrcash"
11
+ SOFORT = "sofortueberweisung"
12
+ GIROPAY = "giropay"
11
13
  TRANSFER = "transfer"
12
14
 
13
15
  # Credit cards
@@ -159,6 +159,30 @@ module Buckaruby
159
159
  end
160
160
  end
161
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
+
162
186
  # Request for a creating a recurrent transaction.
163
187
  class RecurrentTransactionRequest < TransactionRequest
164
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,19 +22,7 @@ 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
@@ -83,7 +73,7 @@ module Buckaruby
83
73
  end
84
74
 
85
75
  def verify_signature!(response, config)
86
- if params[:brq_apiresult] != "Fail"
76
+ if params[:brq_apiresult].nil? || params[:brq_apiresult].casecmp("fail") != 0
87
77
  sent_signature = params[:brq_signature]
88
78
  generated_signature = Signature.generate_signature(response, config)
89
79
 
@@ -174,76 +164,7 @@ module Buckaruby
174
164
  end
175
165
 
176
166
  def transaction_type
177
- if params[:brq_transaction_type] && !params[:brq_transaction_type].empty?
178
- # See http://support.buckaroo.nl/index.php/Transactietypes
179
- case params[:brq_transaction_type]
180
- when 'C021', 'V021', # iDEAL
181
- 'C002', 'C004', 'C005', # (SEPA) Direct Debit
182
- 'V010', 'V014', # PayPal
183
- 'C090', 'V090', # Bancontact
184
- 'C001', # Transfer
185
-
186
- 'C044', 'C192', 'C283', 'C293', 'C318', 'C345',
187
- 'C880', 'C963', 'V002', 'V032', 'V038', 'V044',
188
- 'V192', 'V283', 'V293', 'V313', 'V318', 'V345',
189
- 'V696', # Visa
190
-
191
- 'C043', 'C089', 'C273', 'C303', 'C328', 'C355',
192
- 'C876', 'C969', 'V001', 'V031', 'V037', 'V043',
193
- 'V089', 'V273', 'V303', 'V328', 'V355', 'V702', # MasterCard
194
-
195
- 'C046', 'C251', 'C288', 'C308', 'C333', 'C872',
196
- 'C972', 'V034', 'V040', 'V046', 'V094', 'V245',
197
- 'V288', 'V308', 'V333', 'V705', # Maestro
198
-
199
- 'V003', 'V030', 'V036', 'V042' # American Express
200
-
201
- # Check the recurring flag to detect a normal or recurring transaction.
202
- if params[:brq_recurring] && params[:brq_recurring].casecmp("true").zero?
203
- TransactionType::PAYMENT_RECURRENT
204
- else
205
- TransactionType::PAYMENT
206
- end
207
- when 'C121', # iDEAL
208
- 'C102', 'C500', # (SEPA) Direct Debit
209
- 'V110', # PayPal
210
- 'C092', 'V092', # Bancontact
211
- 'C101', # Transfer
212
-
213
- 'C080', 'C194', 'C281', 'C290', 'C315', 'C342',
214
- 'C881', 'C961', 'V068', 'V074', 'V080', 'V102',
215
- 'V194', 'V281', 'V290', 'V315', 'V342', 'V694', # Visa
216
-
217
- 'C079', 'C197', 'C300', 'C325', 'C352', 'C371',
218
- 'C877', 'C967', 'V067', 'V073', 'V079', 'V101',
219
- 'V149', 'V197', 'V300', 'V325', 'V352', 'V371',
220
- 'V700', # MasterCard
221
-
222
- 'C082', 'C252', 'C286', 'C305', 'C330', 'C873',
223
- 'C970', 'V070', 'V076', 'V082', 'V246', 'V286',
224
- 'V305', 'V330', 'V703', # Maestro
225
-
226
- 'V066', 'V072', 'V078', 'V103' # American Express
227
- TransactionType::REFUND
228
- when 'C501', 'C502', 'C562', # (SEPA) Direct Debit
229
- 'V111', # PayPal
230
-
231
- 'C554', 'C593', 'C882', 'V132', 'V138', 'V144',
232
- 'V544', 'V592', # Visa
233
-
234
- 'C553', 'C589', 'C878', 'V131', 'V137', 'V143',
235
- 'V543', 'V589', # MasterCard
236
-
237
- 'C546', 'C551', 'C874', 'V134', 'V140', 'V146',
238
- 'V545', 'V546', # Maestro
239
-
240
- 'V130', 'V136', 'V142' # American Express
241
- TransactionType::REVERSAL
242
- end
243
- else
244
- # Fallback when transaction type is not known (cancelling credit card)
245
- TransactionType::PAYMENT
246
- end
167
+ TransactionType.parse(params[:brq_transaction_type], params[:brq_recurring])
247
168
  end
248
169
 
249
170
  def transaction_status
@@ -348,4 +269,19 @@ module Buckaruby
348
269
  class CallbackResponse < Response
349
270
  include TransactionResponse
350
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
351
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,91 @@
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
+ 'C075', 'N074', # Sofort
22
+ 'C241', 'N031', # Giropay
23
+ 'C001', # Transfer
24
+
25
+ 'C044', 'C192', 'C283', 'C293', 'C318', 'C345',
26
+ 'C880', 'C963', 'V002', 'V032', 'V038', 'V044',
27
+ 'V192', 'V283', 'V293', 'V313', 'V318', 'V345',
28
+ 'V696', # Visa
29
+
30
+ 'C043', 'C089', 'C273', 'C303', 'C328', 'C355',
31
+ 'C876', 'C969', 'V001', 'V031', 'V037', 'V043',
32
+ 'V089', 'V273', 'V303', 'V328', 'V355', 'V702', # MasterCard
33
+
34
+ 'C046', 'C251', 'C288', 'C308', 'C333', 'C872',
35
+ 'C972', 'V034', 'V040', 'V046', 'V094', 'V245',
36
+ 'V288', 'V308', 'V333', 'V705', # Maestro
37
+
38
+ 'V003', 'V030', 'V036', 'V042' # American Express
39
+
40
+ # Check the recurring flag to detect a normal or recurring transaction.
41
+ if brq_recurring && brq_recurring.casecmp("true").zero?
42
+ TransactionType::PAYMENT_RECURRENT
43
+ else
44
+ TransactionType::PAYMENT
45
+ end
46
+ when 'C121', # iDEAL
47
+ 'C102', 'C500', # (SEPA) Direct Debit
48
+ 'V110', # PayPal
49
+ 'C092', 'V092', # Bancontact
50
+ 'C543', 'N540', # Sofort
51
+ 'C242', 'N541', # Giropay
52
+ 'C101', # Transfer
53
+
54
+ 'C080', 'C194', 'C281', 'C290', 'C315', 'C342',
55
+ 'C881', 'C961', 'V068', 'V074', 'V080', 'V102',
56
+ 'V194', 'V281', 'V290', 'V315', 'V342', 'V694', # Visa
57
+
58
+ 'C079', 'C197', 'C300', 'C325', 'C352', 'C371',
59
+ 'C877', 'C967', 'V067', 'V073', 'V079', 'V101',
60
+ 'V149', 'V197', 'V300', 'V325', 'V352', 'V371',
61
+ 'V700', # MasterCard
62
+
63
+ 'C082', 'C252', 'C286', 'C305', 'C330', 'C873',
64
+ 'C970', 'V070', 'V076', 'V082', 'V246', 'V286',
65
+ 'V305', 'V330', 'V703', # Maestro
66
+
67
+ 'V066', 'V072', 'V078', 'V103' # American Express
68
+ TransactionType::REFUND
69
+ when 'C501', 'C502', 'C562', # (SEPA) Direct Debit
70
+ 'V111', # PayPal
71
+ 'C544', # Sofort
72
+
73
+ 'C554', 'C593', 'C882', 'V132', 'V138', 'V144',
74
+ 'V544', 'V592', # Visa
75
+
76
+ 'C553', 'C589', 'C878', 'V131', 'V137', 'V143',
77
+ 'V543', 'V589', # MasterCard
78
+
79
+ 'C546', 'C551', 'C874', 'V134', 'V140', 'V146',
80
+ 'V545', 'V546', # Maestro
81
+
82
+ 'V130', 'V136', 'V142' # American Express
83
+ TransactionType::REVERSAL
84
+ end
85
+ else
86
+ # Fallback when transaction type is not known (e.g. cancelling credit card)
87
+ TransactionType::PAYMENT
88
+ end
89
+ end
9
90
  end
10
91
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Buckaruby
4
- VERSION = "1.3.1"
4
+ VERSION = "1.7.0"
5
5
  end
@@ -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