diff --git a/.github/workflows/rubygems_release.yml b/.github/workflows/rubygems_release.yml new file mode 100644 index 00000000..e895053f --- /dev/null +++ b/.github/workflows/rubygems_release.yml @@ -0,0 +1,19 @@ +name: Publish Gem + +on: + release: + types: [published] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + + - name: Release Gem on RubyGems + if: contains(github.ref, 'refs/tags/v') + uses: cadwallion/publish-rubygems-action@master + env: + GITHUB_TOKEN: ${{secrets.TOKEN_RUBYGEMS_RELEASES_WITH_EXPIRATION}} + RUBYGEMS_API_KEY: ${{secrets.RUBYGEMS_API_KEY}} diff --git a/lib/adyen/client.rb b/lib/adyen/client.rb index e21e4a67..6396fea1 100644 --- a/lib/adyen/client.rb +++ b/lib/adyen/client.rb @@ -6,9 +6,9 @@ module Adyen class Client attr_accessor :ws_user, :ws_password, :api_key, :client, :adapter, :live_url_prefix - attr_reader :env + attr_reader :env, :connection_options - def initialize(ws_user: nil, ws_password: nil, api_key: nil, env: :live, adapter: nil, mock_port: 3001, live_url_prefix: nil, mock_service_url_base: nil) + def initialize(ws_user: nil, ws_password: nil, api_key: nil, env: :live, adapter: nil, mock_port: 3001, live_url_prefix: nil, mock_service_url_base: nil, connection_options: nil) @ws_user = ws_user @ws_password = ws_password @api_key = api_key @@ -16,6 +16,7 @@ def initialize(ws_user: nil, ws_password: nil, api_key: nil, env: :live, adapter @adapter = adapter || Faraday.default_adapter @mock_service_url_base = mock_service_url_base || "http://localhost:#{mock_port}" @live_url_prefix = live_url_prefix + @connection_options = connection_options || Faraday::ConnectionOptions.new end # make sure that env can only be :live, :test, or :mock @@ -96,7 +97,7 @@ def call_adyen_api(service, action, request_data, headers, version, with_applica end # initialize Faraday connection object - conn = Faraday.new(url: url) do |faraday| + conn = Faraday.new(url, @connection_options) do |faraday| faraday.adapter @adapter faraday.headers["Content-Type"] = "application/json" faraday.headers["User-Agent"] = Adyen::NAME + "/" + Adyen::VERSION diff --git a/lib/adyen/services/checkout.rb b/lib/adyen/services/checkout.rb index 2218659d..0f6f4e65 100644 --- a/lib/adyen/services/checkout.rb +++ b/lib/adyen/services/checkout.rb @@ -71,6 +71,10 @@ def orders(*args) def apple_pay @apple_pay ||= Adyen::CheckoutApplePay.new(@client, @version) end + + def modifications + @modifications ||= Adyen::Modifications.new(@client, @version) + end end class CheckoutDetail < Service @@ -147,4 +151,42 @@ def sessions(request, headers = {}) @client.call_adyen_api(@service, action, request, headers, @version) end end + + class Modifications < Service + def initialize(client, version = DEFAULT_VERSION) + @service = "Checkout" + @client = client + @version = version + end + + def capture(linkId, request, headers = {}) + action = "payments/" + linkId + "/captures" + @client.call_adyen_api(@service, action, request, headers, @version, false) + end + + def cancel(linkId, request, headers = {}) + action = "payments/" + linkId + "/cancels" + @client.call_adyen_api(@service, action, request, headers, @version, false) + end + + def genericCancel(request, headers = {}) + action = "cancels" + @client.call_adyen_api(@service, action, request, headers, @version) + end + + def refund(linkId, request, headers = {}) + action = "payments/" + linkId + "/refunds" + @client.call_adyen_api(@service, action, request, headers, @version, false) + end + + def reversal(linkId, request, headers = {}) + action = "payments/" + linkId + "/reversals" + @client.call_adyen_api(@service, action, request, headers, @version, false) + end + + def amountUpdate(linkId, request, headers = {}) + action = "payments/" + linkId + "/amountUpdates" + @client.call_adyen_api(@service, action, request, headers, @version, false) + end + end end diff --git a/lib/adyen/version.rb b/lib/adyen/version.rb index 50ca9537..d7e05b4a 100644 --- a/lib/adyen/version.rb +++ b/lib/adyen/version.rb @@ -1,4 +1,4 @@ module Adyen NAME = "adyen-ruby-api-library" - VERSION = "6.1.0".freeze + VERSION = "6.2.0".freeze end diff --git a/spec/checkout_spec.rb b/spec/checkout_spec.rb index f974c9a6..412fd7b9 100644 --- a/spec/checkout_spec.rb +++ b/spec/checkout_spec.rb @@ -405,6 +405,198 @@ to be_a_kind_of Hash end + it "makes a capture call" do + request_body = JSON.parse(json_from_file("mocks/requests/Checkout/capture.json")) + + response_body = json_from_file("mocks/responses/Checkout/capture.json") + + url = @shared_values[:client].service_url(@shared_values[:service], "payments/12345/captures", @shared_values[:client].checkout.version) + WebMock.stub_request(:post, url). + with( + body: request_body, + headers: { + "x-api-key" => @shared_values[:client].api_key + } + ) + .to_return(body: response_body, status: 201) + + result = @shared_values[:client].checkout.modifications.capture("12345", request_body) + response_hash = result.response + + expect(result.status). + to eq(201) + 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.reference). + to eq("123456789") + expect(response_hash.pspReference). + to eq("12345") + end + + it "makes a psp specific cancel call" do + request_body = JSON.parse(json_from_file("mocks/requests/Checkout/psp_cancel.json")) + + response_body = json_from_file("mocks/responses/Checkout/psp_cancel.json") + + url = @shared_values[:client].service_url(@shared_values[:service], "payments/12345/cancels", @shared_values[:client].checkout.version) + WebMock.stub_request(:post, url). + with( + body: request_body, + headers: { + "x-api-key" => @shared_values[:client].api_key + } + ) + .to_return(body: response_body, status: 201) + + result = @shared_values[:client].checkout.modifications.cancel("12345", request_body) + response_hash = result.response + + expect(result.status). + to eq(201) + 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.reference). + to eq("123456789") + expect(response_hash.pspReference). + to eq("12345") + end + + it "makes a psp specific refunds call" do + request_body = JSON.parse(json_from_file("mocks/requests/Checkout/refund.json")) + + response_body = json_from_file("mocks/responses/Checkout/refund.json") + + url = @shared_values[:client].service_url(@shared_values[:service], "payments/12345/refunds", @shared_values[:client].checkout.version) + WebMock.stub_request(:post, url). + with( + body: request_body, + headers: { + "x-api-key" => @shared_values[:client].api_key + } + ) + .to_return(body: response_body, status: 201) + + result = @shared_values[:client].checkout.modifications.refund("12345", request_body) + response_hash = result.response + + expect(result.status). + to eq(201) + 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.reference). + to eq("123456789") + expect(response_hash.pspReference). + to eq("12345") + end + + it "makes a psp specific reversals call" do + request_body = JSON.parse(json_from_file("mocks/requests/Checkout/psp_cancel.json")) + + response_body = json_from_file("mocks/responses/Checkout/psp_cancel.json") + + url = @shared_values[:client].service_url(@shared_values[:service], "payments/12345/reversals", @shared_values[:client].checkout.version) + WebMock.stub_request(:post, url). + with( + body: request_body, + headers: { + "x-api-key" => @shared_values[:client].api_key + } + ) + .to_return(body: response_body, status: 201) + + result = @shared_values[:client].checkout.modifications.reversal("12345", request_body) + response_hash = result.response + + expect(result.status). + to eq(201) + 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.reference). + to eq("123456789") + expect(response_hash.pspReference). + to eq("12345") + end + + it "makes a psp specific amountUpdates call" do + request_body = JSON.parse(json_from_file("mocks/requests/Checkout/amount_updates.json")) + + response_body = json_from_file("mocks/responses/Checkout/amount_updates.json") + + url = @shared_values[:client].service_url(@shared_values[:service], "payments/12345/amountUpdates", @shared_values[:client].checkout.version) + WebMock.stub_request(:post, url). + with( + body: request_body, + headers: { + "x-api-key" => @shared_values[:client].api_key + } + ) + .to_return(body: response_body, status: 201) + + result = @shared_values[:client].checkout.modifications.amountUpdate("12345", request_body) + response_hash = result.response + + expect(result.status). + to eq(201) + 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.reference). + to eq("123456789") + expect(response_hash.pspReference). + to eq("12345") + end + + it "makes a generic cancel call" do + request_body = JSON.parse(json_from_file("mocks/requests/Checkout/generic_cancel.json")) + + response_body = json_from_file("mocks/responses/Checkout/generic_cancel.json") + + url = @shared_values[:client].service_url(@shared_values[:service], "cancels", @shared_values[:client].checkout.version) + WebMock.stub_request(:post, url). + with( + body: request_body, + headers: { + "x-api-key" => @shared_values[:client].api_key + } + ) + .to_return(body: response_body, status: 201) + + result = @shared_values[:client].checkout.modifications.genericCancel(request_body) + response_hash = result.response + + expect(result.status). + to eq(201) + 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.reference). + to eq("123456789") + expect(response_hash.pspReference). + to eq("12345") + end + # create client for automated tests client = create_client(:api_key) diff --git a/spec/client_spec.rb b/spec/client_spec.rb index 4d074bdd..e2c72349 100644 --- a/spec/client_spec.rb +++ b/spec/client_spec.rb @@ -83,4 +83,31 @@ expect(client.service_url_base("Payment")). to eq("https://abcdef1234567890-TestCompany-pal-live.adyenpayments.com/pal/servlet") end + + it "generates a new set of ConnectionOptions when none are provided" do + expect(Faraday::ConnectionOptions).to receive(:new).and_call_original + client = Adyen::Client.new(env: :test) + end + + it "uses the ConnectionOptions provided" do + connection_options = Faraday::ConnectionOptions.new + expect(Faraday::ConnectionOptions).not_to receive(:new) + client = Adyen::Client.new(env: :test, connection_options: connection_options) + end + + it "initiates a Faraday connection with the provided options" do + connection_options = Faraday::ConnectionOptions.new + expect(Faraday::ConnectionOptions).not_to receive(:new) + client = Adyen::Client.new(api_key: "api_key", env: :mock, connection_options: connection_options) + + mock_faraday_connection = double(Faraday::Connection) + url = client.service_url(@shared_values[:service], "payments/details", client.checkout.version) + request_body = JSON.parse(json_from_file("mocks/requests/Checkout/payment-details.json")) + 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(mock_faraday_connection).to receive(:post).and_return(mock_response) + client.checkout.payments.details(request_body) + end end diff --git a/spec/mocks/requests/Checkout/amount_updates.json b/spec/mocks/requests/Checkout/amount_updates.json new file mode 100644 index 00000000..d04a96cd --- /dev/null +++ b/spec/mocks/requests/Checkout/amount_updates.json @@ -0,0 +1,22 @@ +{ + "amount": { + "currency": "str", + "value": 0 + }, + "merchantAccount": "TestMerchant", + "reason": "delayedCharge", + "reference": "123456789", + "splits": [ + { + "account": "string", + "amount": { + "currency": "str", + "value": 0 + }, + "description": "string", + "reference": "string", + "type": "BalanceAccount" + } + ] +} + \ No newline at end of file diff --git a/spec/mocks/requests/Checkout/capture.json b/spec/mocks/requests/Checkout/capture.json new file mode 100644 index 00000000..0ffbfe1a --- /dev/null +++ b/spec/mocks/requests/Checkout/capture.json @@ -0,0 +1,34 @@ +{ + "amount": { + "currency": "str", + "value": 0 + }, + "lineItems": [ + { + "amountExcludingTax": 0, + "amountIncludingTax": 0, + "description": "string", + "id": "string", + "imageUrl": "string", + "itemCategory": "string", + "productUrl": "string", + "quantity": 0, + "taxAmount": 0, + "taxPercentage": 0 + } + ], + "merchantAccount": "string", + "reference": "string", + "splits": [ + { + "account": "string", + "amount": { + "currency": "str", + "value": 0 + }, + "description": "string", + "reference": "string", + "type": "BalanceAccount" + } + ] +} \ No newline at end of file diff --git a/spec/mocks/requests/Checkout/generic_cancel.json b/spec/mocks/requests/Checkout/generic_cancel.json new file mode 100644 index 00000000..2f5fdad7 --- /dev/null +++ b/spec/mocks/requests/Checkout/generic_cancel.json @@ -0,0 +1,5 @@ +{ + "merchantAccount": "TestMerchant", + "paymentReference": "12345", + "reference": "123456789" +} \ No newline at end of file diff --git a/spec/mocks/requests/Checkout/modifications_request.json b/spec/mocks/requests/Checkout/modifications_request.json new file mode 100644 index 00000000..e69de29b diff --git a/spec/mocks/requests/Checkout/psp_cancel.json b/spec/mocks/requests/Checkout/psp_cancel.json new file mode 100644 index 00000000..6459c3c8 --- /dev/null +++ b/spec/mocks/requests/Checkout/psp_cancel.json @@ -0,0 +1,4 @@ +{ + "merchantAccount": "TestMerchant", + "reference": "123456789" +} \ No newline at end of file diff --git a/spec/mocks/requests/Checkout/refund.json b/spec/mocks/requests/Checkout/refund.json new file mode 100644 index 00000000..9a030960 --- /dev/null +++ b/spec/mocks/requests/Checkout/refund.json @@ -0,0 +1,34 @@ +{ + "amount": { + "currency": "str", + "value": 0 + }, + "lineItems": [ + { + "amountExcludingTax": 0, + "amountIncludingTax": 0, + "description": "string", + "id": "string", + "imageUrl": "string", + "itemCategory": "string", + "productUrl": "string", + "quantity": 0, + "taxAmount": 0, + "taxPercentage": 0 + } + ], + "merchantAccount": "TestMerchant", + "reference": "123456789", + "splits": [ + { + "account": "string", + "amount": { + "currency": "str", + "value": 0 + }, + "description": "string", + "reference": "string", + "type": "BalanceAccount" + } + ] +} \ No newline at end of file diff --git a/spec/mocks/responses/Checkout/amount_updates.json b/spec/mocks/responses/Checkout/amount_updates.json new file mode 100644 index 00000000..c532a14a --- /dev/null +++ b/spec/mocks/responses/Checkout/amount_updates.json @@ -0,0 +1,24 @@ +{ + "amount": { + "currency": "str", + "value": 0 + }, + "merchantAccount": "TestMerchant", + "paymentPspReference": "123456789", + "pspReference": "12345", + "reason": "delayedCharge", + "reference": "123456789", + "splits": [ + { + "account": "string", + "amount": { + "currency": "str", + "value": 0 + }, + "description": "string", + "reference": "string", + "type": "BalanceAccount" + } + ], + "status": "received" +} \ No newline at end of file diff --git a/spec/mocks/responses/Checkout/capture.json b/spec/mocks/responses/Checkout/capture.json new file mode 100644 index 00000000..f874e722 --- /dev/null +++ b/spec/mocks/responses/Checkout/capture.json @@ -0,0 +1,37 @@ +{ + "amount": { + "currency": "str", + "value": 0 + }, + "lineItems": [ + { + "amountExcludingTax": 0, + "amountIncludingTax": 0, + "description": "string", + "id": "string", + "imageUrl": "string", + "itemCategory": "string", + "productUrl": "string", + "quantity": 0, + "taxAmount": 0, + "taxPercentage": 0 + } + ], + "merchantAccount": "string", + "paymentPspReference": "string", + "pspReference": "12345", + "reference": "123456789", + "splits": [ + { + "account": "string", + "amount": { + "currency": "str", + "value": 0 + }, + "description": "string", + "reference": "string", + "type": "BalanceAccount" + } + ], + "status": "received" +} \ No newline at end of file diff --git a/spec/mocks/responses/Checkout/generic_cancel.json b/spec/mocks/responses/Checkout/generic_cancel.json new file mode 100644 index 00000000..2b62c1e5 --- /dev/null +++ b/spec/mocks/responses/Checkout/generic_cancel.json @@ -0,0 +1,7 @@ +{ + "merchantAccount": "string", + "paymentReference": "123456789", + "pspReference": "12345", + "reference": "123456789", + "status": "received" +} \ No newline at end of file diff --git a/spec/mocks/responses/Checkout/modifications.json b/spec/mocks/responses/Checkout/modifications.json new file mode 100644 index 00000000..e69de29b diff --git a/spec/mocks/responses/Checkout/psp_cancel.json b/spec/mocks/responses/Checkout/psp_cancel.json new file mode 100644 index 00000000..18789077 --- /dev/null +++ b/spec/mocks/responses/Checkout/psp_cancel.json @@ -0,0 +1,7 @@ +{ + "merchantAccount": "TestMerchant", + "paymentPspReference": "string", + "pspReference": "12345", + "reference": "123456789", + "status": "received" +} \ No newline at end of file diff --git a/spec/mocks/responses/Checkout/refund.json b/spec/mocks/responses/Checkout/refund.json new file mode 100644 index 00000000..416f0b4d --- /dev/null +++ b/spec/mocks/responses/Checkout/refund.json @@ -0,0 +1,37 @@ +{ + "amount": { + "currency": "str", + "value": 0 + }, + "lineItems": [ + { + "amountExcludingTax": 0, + "amountIncludingTax": 0, + "description": "string", + "id": "string", + "imageUrl": "string", + "itemCategory": "string", + "productUrl": "string", + "quantity": 0, + "taxAmount": 0, + "taxPercentage": 0 + } + ], + "merchantAccount": "TestMerchant", + "paymentPspReference": "123456789", + "pspReference": "12345", + "reference": "123456789", + "splits": [ + { + "account": "string", + "amount": { + "currency": "str", + "value": 0 + }, + "description": "string", + "reference": "string", + "type": "BalanceAccount" + } + ], + "status": "received" +} \ No newline at end of file