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
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1 +1 @@
* @crrood @wboereboom @AlexandrosMor @michaelpaul
* @Adyen/api-libraries-reviewers
41 changes: 41 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: "CodeQL"

on:
push:
branches: [ "develop", "main" ]
pull_request:
branches: [ "develop" ]
schedule:
- cron: "40 12 * * 0"

jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write

strategy:
fail-fast: false
matrix:
language: [ javascript ]

steps:
- name: Checkout
uses: actions/checkout@v3

- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
queries: +security-and-quality

- name: Autobuild
uses: github/codeql-action/autobuild@v2

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{ matrix.language }}"
2 changes: 1 addition & 1 deletion .github/workflows/ruby.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
ruby: [2.5, 2.6, 2.7, '3.0', head]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/rubygems_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v3

- name: Release Gem on RubyGems
if: contains(github.ref, 'refs/tags/v')
Expand Down
20 changes: 16 additions & 4 deletions lib/adyen/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,13 @@ def call_adyen_api(service, action, request_data, headers, version, with_applica
raise connection_error, "Connection to #{url} failed"
end
end
if action.fetch(:method) == "delete"
begin
response = conn.delete
rescue Faraday::ConnectionFailed => connection_error
raise connection_error, "Connection to #{url} failed"
end
end
if action.fetch(:method) == "patch"
begin
response = conn.patch do |req|
Expand All @@ -169,11 +176,16 @@ def call_adyen_api(service, action, request_data, headers, version, with_applica
when 401
raise Adyen::AuthenticationError.new("Invalid API authentication; https://docs.adyen.com/user-management/how-to-get-the-api-key", request_data)
when 403
raise Adyen::PermissionError.new("Missing user permissions; https://docs.adyen.com/user-management/user-roles", request_data)
raise Adyen::PermissionError.new("Missing user permissions; https://docs.adyen.com/user-management/user-roles", request_data, response.body)
end

formatted_response = AdyenResult.new(response.body, response.headers, response.status)


# delete has no response.body (unless it throws an error)
if response.body == nil
formatted_response = AdyenResult.new("{}", response.headers, response.status)
else
formatted_response = AdyenResult.new(response.body, response.headers, response.status)
end

formatted_response
end

Expand Down
4 changes: 2 additions & 2 deletions lib/adyen/errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ def initialize(msg, request)
end

class PermissionError < AdyenError
def initialize(msg, request)
super(request, nil, msg, 403)
def initialize(msg, request, response)
super(request, response, msg, 403)
end
end

Expand Down
54 changes: 43 additions & 11 deletions lib/adyen/services/checkout.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

module Adyen
class Checkout < Service
DEFAULT_VERSION = 68
DEFAULT_VERSION = 70

def initialize(client, version = DEFAULT_VERSION)
service = "Checkout"
Expand All @@ -13,7 +13,7 @@ def initialize(client, version = DEFAULT_VERSION)
]

with_application_info = [
:payment_session,
:payment_session
]

super(client, version, service, method_names, with_application_info)
Expand Down Expand Up @@ -42,7 +42,7 @@ def payment_links(*args)
else
action = "paymentLinks"
args[1] ||= {} # optional headers arg
@client.call_adyen_api(@service, action, args[0], args[1], @version, true)
@client.call_adyen_api(@service, action, args[0], args[1], @version)
end
end

Expand Down Expand Up @@ -75,6 +75,10 @@ def apple_pay
def modifications
@modifications ||= Adyen::Modifications.new(@client, @version)
end

def stored_payment_methods
@stored_payment_methods ||= Adyen::StoredPaymentMethods.new(@client, @version)
end
end

class CheckoutDetail < Service
Expand All @@ -93,6 +97,16 @@ def result(request, headers = {})
action = "payments/result"
@client.call_adyen_api(@service, action, request, headers, @version)
end

def donations(request, headers = {})
action = "donations"
@client.call_adyen_api(@service, action, request, headers, @version)
end

def card_details(request, headers = {})
action = "cardDetails"
@client.call_adyen_api(@service, action, request, headers, @version)
end
end

class CheckoutLink < Service
Expand All @@ -104,12 +118,12 @@ def initialize(client, version = DEFAULT_VERSION)

def get(linkId, headers = {})
action = { method: 'get', url: "paymentLinks/" + linkId }
@client.call_adyen_api(@service, action, {}, headers, @version, true)
@client.call_adyen_api(@service, action, {}, headers, @version)
end

def update(linkId, request, headers = {})
action = { method: 'patch', url: "paymentLinks/" + linkId }
@client.call_adyen_api(@service, action, request, headers, @version, false)
@client.call_adyen_api(@service, action, request, headers, @version)
end
end

Expand Down Expand Up @@ -161,12 +175,12 @@ def initialize(client, version = DEFAULT_VERSION)

def capture(linkId, request, headers = {})
action = "payments/" + linkId + "/captures"
@client.call_adyen_api(@service, action, request, headers, @version, false)
@client.call_adyen_api(@service, action, request, headers, @version)
end

def cancel(linkId, request, headers = {})
action = "payments/" + linkId + "/cancels"
@client.call_adyen_api(@service, action, request, headers, @version, false)
@client.call_adyen_api(@service, action, request, headers, @version)
end

def genericCancel(request, headers = {})
Expand All @@ -176,17 +190,35 @@ def genericCancel(request, headers = {})

def refund(linkId, request, headers = {})
action = "payments/" + linkId + "/refunds"
@client.call_adyen_api(@service, action, request, headers, @version, false)
@client.call_adyen_api(@service, action, request, headers, @version)
end

def reversal(linkId, request, headers = {})
action = "payments/" + linkId + "/reversals"
@client.call_adyen_api(@service, action, request, headers, @version, false)
@client.call_adyen_api(@service, action, request, headers, @version)
end

def amountUpdate(linkId, request, headers = {})
action = "payments/" + linkId + "/amountUpdates"
@client.call_adyen_api(@service, action, request, headers, @version, false)
@client.call_adyen_api(@service, action, request, headers, @version)
end
end

class StoredPaymentMethods < Service
def initialize(client, version = DEFAULT_VERSION)
@service = "Checkout"
@client = client
@version = version
end

def get(query_array={}, headers = {})
action = { method: 'get', url: "storedPaymentMethods" + create_query_string(query_array)}
@client.call_adyen_api(@service, action, {}, headers, @version)
end

def delete(recurringId, query_array={}, headers = {})
action = { method: 'delete', url: "storedPaymentMethods/%s" % recurringId + create_query_string(query_array)}
@client.call_adyen_api(@service, action, {}, headers, @version)
end
end
end
end
5 changes: 5 additions & 0 deletions lib/adyen/services/service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,10 @@ def initialize(client, version, service, method_names, with_application_info = [
end
end
end

# create query parameter from an array
def create_query_string(arr)
"?" + URI.encode_www_form(arr)
end
end
end
5 changes: 2 additions & 3 deletions lib/adyen/utils/hmac_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,15 @@ def calculate_notification_hmac(notification_request_item, hmac_key)
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(':', '\\:') }
data = NOTIFICATION_VALIDATION_KEYS.map { |key| fetch(notification_request_item, key).to_s }
.join(DATA_SEPARATOR)
return data
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]
Expand Down
4 changes: 2 additions & 2 deletions lib/adyen/version.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module Adyen
NAME = "adyen-ruby-api-library"
VERSION = "6.2.0".freeze
end
VERSION = "6.3.0".freeze
end
50 changes: 50 additions & 0 deletions spec/checkout_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,56 @@
to eq("12345")
end

it "makes a get storedPaymentMethods call" do
response_body = json_from_file("mocks/responses/Checkout/stored_payment_methods.json")

url = @shared_values[:client].service_url(@shared_values[:service], "storedPaymentMethods?merchantAccount=TestMerchantAccount&shopperReference=test-1234", @shared_values[:client].checkout.version)
WebMock.stub_request(:get, url).
with(
headers: {
"x-api-key" => @shared_values[:client].api_key
}
).
to_return(
body: response_body
)

result = @shared_values[:client].checkout.stored_payment_methods.get({"merchantAccount" => "TestMerchantAccount", "shopperReference" => "test-1234"})
response_hash = result.response

expect(result.status).
to eq(200)
expect(response_hash).
to eq(JSON.parse(response_body))
expect(response_hash).
to be_a Adyen::HashWithAccessors
expect(response_hash).
to be_a_kind_of Hash
expect(response_hash["shopperReference"]).
to eq("test-1234")
end

it "makes a delete storedPaymentMethods call" do
response_body = json_from_file("mocks/responses/Checkout/stored_payment_methods.json")

url = @shared_values[:client].service_url(@shared_values[:service], "storedPaymentMethods/RL8FW7WZM6KXWD82?merchantAccount=TestMerchantAccount&shopperReference=test-1234", @shared_values[:client].checkout.version)
WebMock.stub_request(:delete, url).
with(
headers: {
"x-api-key" => @shared_values[:client].api_key
}
).
to_return(
body: response_body
)

result = @shared_values[:client].checkout.stored_payment_methods.delete("RL8FW7WZM6KXWD82", {"merchantAccount" => "TestMerchantAccount", "shopperReference" => "test-1234"})
response_hash = result.response

expect(result.status).
to eq(200)
end

# create client for automated tests
client = create_client(:api_key)

Expand Down
2 changes: 1 addition & 1 deletion spec/client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@
mock_response = Faraday::Response.new(status: 200)

expect(Adyen::AdyenResult).to receive(:new)
expect(Faraday).to receive(:new).with("http://localhost:3001/v68/payments/details", connection_options).and_return(mock_faraday_connection)
expect(Faraday).to receive(:new).with("http://localhost:3001/v70/payments/details", connection_options).and_return(mock_faraday_connection)
expect(mock_faraday_connection).to receive(:post).and_return(mock_response)
client.checkout.payments.details(request_body)
end
Expand Down
2 changes: 1 addition & 1 deletion spec/errors_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
expect(Adyen::AdyenError.new(@shared_values[:request], nil, nil, 'code').to_s).to eq("Adyen::AdyenError code:code, request:#{@shared_values[:request]}")
end
it 'uses the proper error class name' do
expect(Adyen::PermissionError.new('message', @shared_values[:request]).to_s).to eq("Adyen::PermissionError code:403, msg:message, request:#{@shared_values[:request]}")
expect(Adyen::PermissionError.new('message', @shared_values[:request], 'response').to_s).to eq("Adyen::PermissionError code:403, msg:message, request:#{@shared_values[:request]}, response:response")
end
end
describe '#masking' do
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"merchantAccount":"TestMerchantAccount", "shopperReference":"test-1234"}
41 changes: 41 additions & 0 deletions spec/mocks/responses/Webhooks/backslash_notification.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"additionalData": {
"acquirerCode": "TestPmmAcquirer",
"acquirerReference": "DZMKWLXW6N6",
"authCode": "076181",
"avsResult": "5 No AVS data provided",
"avsResultRaw": "5",
"cardSummary": "1111",
"checkout.cardAddedBrand": "visa",
"cvcResult": "1 Matches",
"cvcResultRaw": "M",
"expiryDate": "03/2030",
"hmacSignature": "nIgT81gaB5oJpn2jPXupDq68iRo2wUlBsuYjtYfwKqo=",
"paymentMethod": "visa",
"refusalReasonRaw": "AUTHORISED",
"retry.attempt1.acquirer": "TestPmmAcquirer",
"retry.attempt1.acquirerAccount": "TestPmmAcquirerAccount",
"retry.attempt1.avsResultRaw": "5",
"retry.attempt1.rawResponse": "AUTHORISED",
"retry.attempt1.responseCode": "Approved",
"retry.attempt1.scaExemptionRequested": "lowValue",
"scaExemptionRequested": "lowValue"
},
"amount": {
"currency": "EUR",
"value": 1000
},
"eventCode": "AUTHORISATION",
"eventDate": "2023-01-09T16:27:29+01:00",
"merchantAccountCode": "AntoniStroinski",
"merchantReference": "\\\\slashes are fun",
"operations": [
"CANCEL",
"CAPTURE",
"REFUND"
],
"paymentMethod": "visa",
"pspReference": "T7FD4VM4D3RZNN82",
"reason": "076181:1111:03/2030",
"success": "true"
}
Loading