Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/adyen-ruby-api-library.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
require_relative "adyen/services/recurring"
require_relative "adyen/services/marketpay"
require_relative "adyen/services/service"
require_relative "adyen/utils/hmac_validator"

# add snake case to camel case converter to String
# to convert rubinic method names to Adyen API methods
Expand Down
48 changes: 48 additions & 0 deletions lib/adyen/utils/hmac_validator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
module Adyen
module Utils
class HmacValidator
HMAC_ALGORITHM = 'sha256'.freeze
DATA_SEPARATOR = ':'.freeze
NOTIFICATION_VALIDATION_KEYS = %w[
pspReference originalReference merchantAccountCode merchantReference
amount.value amount.currency eventCode success
].freeze

def valid_notification_hmac?(notification_request_item, hmac_key)
expected_sign = calculate_notification_hmac(notification_request_item, hmac_key)
merchant_sign = fetch(notification_request_item, 'additionalData.hmacSignature')

expected_sign == merchant_sign
end

def calculate_notification_hmac(notification_request_item, hmac_key)
data = data_to_sign(notification_request_item)

Base64.strict_encode64(OpenSSL::HMAC.digest(HMAC_ALGORITHM, [hmac_key].pack('H*'), data))
end

def data_to_sign(notification_request_item)
NOTIFICATION_VALIDATION_KEYS.map { |key| fetch(notification_request_item, key).to_s }
.map { |value| value.gsub('\\', '\\\\').gsub(':', '\\:') }
.join(DATA_SEPARATOR)
end

private

def fetch(hash, keys)
value = hash

keys.to_s.split('.').each do |key|
value = if key.to_i.to_s == key
value[key.to_i]
else
value[key].nil? ? value[key.to_sym] : value[key]
end
break if value.nil?
end

value
end
end
end
end
52 changes: 52 additions & 0 deletions spec/utils/hmac_validator_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
require 'spec_helper'

RSpec.describe Adyen::Utils::HmacValidator do
let(:validator) { described_class.new }
let(:key) { '44782DEF547AAA06C910C43932B1EB0C71FC68D9D0C057550C48EC2ACF6BA056' }
let(:expected_sign) { 'coqCmt/IZ4E3CzPvMY8zTjQVL5hYJUiBRg8UU+iCWo0=' }
let(:notification_request_item) do
{
additionalData: {
hmacSignature: expected_sign
},
amount: {
value: 1130,
currency: 'EUR'
},
pspReference: '7914073381342284',
eventCode: 'AUTHORISATION',
merchantAccountCode: 'TestMerchant',
merchantReference: 'TestPayment-1407325143704',
paymentMethod: 'visa',
success: 'true'
}
end

describe 'HMAC Validator' do
it 'should get correct data' do
data_to_sign = validator.data_to_sign(notification_request_item)
expect(data_to_sign).to eq '7914073381342284::TestMerchant:TestPayment-1407325143704:1130:EUR:AUTHORISATION:true'
end

it 'should get correct data with escaped characters' do
notification_request_item['merchantAccountCode'] = 'Test:\\Merchant'
data_to_sign = validator.data_to_sign(notification_request_item)
expect(data_to_sign).to eq '7914073381342284::Test\\:\\Merchant:TestPayment-1407325143704:1130:EUR:AUTHORISATION:true'
end

it 'should encrypt properly' do
encrypted = validator.calculate_notification_hmac(notification_request_item, key)
expect(encrypted).to eq expected_sign
end

it 'should have a valid hmac' do
expect(validator.valid_notification_hmac?(notification_request_item, key)).to be true
end

it 'should have an invalid hmac' do
notification_request_item['additionalData'] = { 'hmacSignature' => 'invalidHMACsign' }

expect(validator.valid_notification_hmac?(notification_request_item, key)).to be false
end
end
end