diff --git a/lib/gocardless_pro/resources/event.rb b/lib/gocardless_pro/resources/event.rb index a742f061..b8c4cf4d 100644 --- a/lib/gocardless_pro/resources/event.rb +++ b/lib/gocardless_pro/resources/event.rb @@ -132,6 +132,10 @@ def payment @links['payment'] end + def payment_account_transaction + @links['payment_account_transaction'] + end + def payment_request_payment @links['payment_request_payment'] end diff --git a/lib/gocardless_pro/resources/outbound_payment.rb b/lib/gocardless_pro/resources/outbound_payment.rb index f68a8fb5..b6bac8a8 100644 --- a/lib/gocardless_pro/resources/outbound_payment.rb +++ b/lib/gocardless_pro/resources/outbound_payment.rb @@ -88,6 +88,10 @@ def customer @links['customer'] end + def outbound_payment_import + @links['outbound_payment_import'] + end + def recipient_bank_account @links['recipient_bank_account'] end diff --git a/lib/gocardless_pro/services/payment_account_transactions_service.rb b/lib/gocardless_pro/services/payment_account_transactions_service.rb index d2f23b1e..2cba0e42 100644 --- a/lib/gocardless_pro/services/payment_account_transactions_service.rb +++ b/lib/gocardless_pro/services/payment_account_transactions_service.rb @@ -10,6 +10,27 @@ module GoCardlessPro module Services # Service for making requests to the PaymentAccountTransaction endpoints class PaymentAccountTransactionsService < BaseService + # Retrieves the details of an existing payment account transaction. + # Example URL: /payment_account_transactions/:identity + # + # @param identity # The unique ID of the [bank + # account](#core-endpoints-creditor-bank-accounts) which happens to be the + # payment account. + # @param options [Hash] parameters as a hash, under a params key. + def get(identity, options = {}) + path = sub_url('/payment_account_transactions/:identity', { + 'identity' => identity, + }) + + options[:retry_failures] = true + + response = make_request(:get, path, options) + + return if response.body.nil? + + Resources::PaymentAccountTransaction.new(unenvelope_body(response.body), response) + end + # List transactions for a given payment account. # Example URL: /payment_accounts/:identity/transactions # diff --git a/spec/resources/payment_account_transaction_spec.rb b/spec/resources/payment_account_transaction_spec.rb index 8d60a49a..7be99cec 100644 --- a/spec/resources/payment_account_transaction_spec.rb +++ b/spec/resources/payment_account_transaction_spec.rb @@ -9,6 +9,99 @@ let(:response_headers) { { 'Content-Type' => 'application/json' } } + describe '#get' do + let(:id) { 'ID123' } + + subject(:get_response) { client.payment_account_transactions.get(id) } + + context 'passing in a custom header' do + let!(:stub) do + stub_url = '/payment_account_transactions/:identity'.gsub(':identity', id) + stub_request(:get, /.*api.gocardless.com#{stub_url}/). + with(headers: { 'Foo' => 'Bar' }). + to_return( + body: { + 'payment_account_transactions' => { + + 'amount' => 'amount-input', + 'balance_after_transaction' => 'balance_after_transaction-input', + 'counterparty_name' => 'counterparty_name-input', + 'currency' => 'currency-input', + 'description' => 'description-input', + 'direction' => 'direction-input', + 'id' => 'id-input', + 'links' => 'links-input', + 'reference' => 'reference-input', + 'value_date' => 'value_date-input', + }, + }.to_json, + headers: response_headers + ) + end + + subject(:get_response) do + client.payment_account_transactions.get(id, headers: { + 'Foo' => 'Bar', + }) + end + + it 'includes the header' do + get_response + expect(stub).to have_been_requested + end + end + + context 'when there is a payment_account_transaction to return' do + before do + stub_url = '/payment_account_transactions/:identity'.gsub(':identity', id) + stub_request(:get, /.*api.gocardless.com#{stub_url}/).to_return( + body: { + 'payment_account_transactions' => { + + 'amount' => 'amount-input', + 'balance_after_transaction' => 'balance_after_transaction-input', + 'counterparty_name' => 'counterparty_name-input', + 'currency' => 'currency-input', + 'description' => 'description-input', + 'direction' => 'direction-input', + 'id' => 'id-input', + 'links' => 'links-input', + 'reference' => 'reference-input', + 'value_date' => 'value_date-input', + }, + }.to_json, + headers: response_headers + ) + end + + it 'wraps the response in a resource' do + expect(get_response).to be_a(GoCardlessPro::Resources::PaymentAccountTransaction) + end + end + + context 'when nothing is returned' do + before do + stub_url = '/payment_account_transactions/:identity'.gsub(':identity', id) + stub_request(:get, /.*api.gocardless.com#{stub_url}/).to_return( + body: '', + headers: response_headers + ) + end + + it 'returns nil' do + expect(get_response).to be_nil + end + end + + context "when an ID is specified which can't be included in a valid URI" do + let(:id) { '`' } + + it "doesn't raise an error" do + expect { get_response }.to_not raise_error(/bad URI/) + end + end + end + describe '#list' do describe 'with no filters' do let(:identity) { 'ID123' } diff --git a/spec/services/payment_account_transactions_service_spec.rb b/spec/services/payment_account_transactions_service_spec.rb index af096185..7a6dc852 100644 --- a/spec/services/payment_account_transactions_service_spec.rb +++ b/spec/services/payment_account_transactions_service_spec.rb @@ -9,6 +9,154 @@ let(:response_headers) { { 'Content-Type' => 'application/json' } } + describe '#get' do + let(:id) { 'ID123' } + + subject(:get_response) { client.payment_account_transactions.get(id) } + + context 'passing in a custom header' do + let!(:stub) do + stub_url = '/payment_account_transactions/:identity'.gsub(':identity', id) + stub_request(:get, /.*api.gocardless.com#{stub_url}/). + with(headers: { 'Foo' => 'Bar' }). + to_return( + body: { + 'payment_account_transactions' => { + + 'amount' => 'amount-input', + 'balance_after_transaction' => 'balance_after_transaction-input', + 'counterparty_name' => 'counterparty_name-input', + 'currency' => 'currency-input', + 'description' => 'description-input', + 'direction' => 'direction-input', + 'id' => 'id-input', + 'links' => 'links-input', + 'reference' => 'reference-input', + 'value_date' => 'value_date-input', + }, + }.to_json, + headers: response_headers + ) + end + + subject(:get_response) do + client.payment_account_transactions.get(id, headers: { + 'Foo' => 'Bar', + }) + end + + it 'includes the header' do + get_response + expect(stub).to have_been_requested + end + end + + context 'when there is a payment_account_transaction to return' do + before do + stub_url = '/payment_account_transactions/:identity'.gsub(':identity', id) + stub_request(:get, /.*api.gocardless.com#{stub_url}/).to_return( + body: { + 'payment_account_transactions' => { + + 'amount' => 'amount-input', + 'balance_after_transaction' => 'balance_after_transaction-input', + 'counterparty_name' => 'counterparty_name-input', + 'currency' => 'currency-input', + 'description' => 'description-input', + 'direction' => 'direction-input', + 'id' => 'id-input', + 'links' => 'links-input', + 'reference' => 'reference-input', + 'value_date' => 'value_date-input', + }, + }.to_json, + headers: response_headers + ) + end + + it 'wraps the response in a resource' do + expect(get_response).to be_a(GoCardlessPro::Resources::PaymentAccountTransaction) + end + end + + context 'when nothing is returned' do + before do + stub_url = '/payment_account_transactions/:identity'.gsub(':identity', id) + stub_request(:get, /.*api.gocardless.com#{stub_url}/).to_return( + body: '', + headers: response_headers + ) + end + + it 'returns nil' do + expect(get_response).to be_nil + end + end + + context "when an ID is specified which can't be included in a valid URI" do + let(:id) { '`' } + + it "doesn't raise an error" do + expect { get_response }.to_not raise_error(/bad URI/) + end + end + + describe 'retry behaviour' do + before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) } + + it 'retries timeouts' do + stub_url = '/payment_account_transactions/:identity'.gsub(':identity', id) + + stub = stub_request(:get, /.*api.gocardless.com#{stub_url}/). + to_timeout.then.to_return({ status: 200, headers: response_headers }) + + get_response + expect(stub).to have_been_requested.twice + end + + it 'retries 5XX errors, other than 500s' do + stub_url = '/payment_account_transactions/:identity'.gsub(':identity', id) + + stub = stub_request(:get, /.*api.gocardless.com#{stub_url}/). + to_return({ status: 502, + headers: { 'Content-Type' => 'text/html' }, + body: 'Response from Cloudflare' }). + then.to_return({ status: 200, headers: response_headers }) + + get_response + expect(stub).to have_been_requested.twice + end + + it 'retries 500 errors returned by the API' do + stub_url = '/payment_account_transactions/:identity'.gsub(':identity', id) + + gocardless_error = { + 'error' => { + 'message' => 'Internal server error', + 'documentation_url' => 'https://developer.gocardless.com/#gocardless', + 'errors' => [{ + 'message' => 'Internal server error', + 'reason' => 'internal_server_error', + }], + 'type' => 'gocardless', + 'code' => 500, + 'request_id' => 'dummy_request_id', + 'id' => 'dummy_exception_id', + }, + } + + stub = stub_request(:get, /.*api.gocardless.com#{stub_url}/). + to_return({ status: 500, + headers: response_headers, + body: gocardless_error.to_json }). + then.to_return({ status: 200, headers: response_headers }) + + get_response + expect(stub).to have_been_requested.twice + end + end + end + describe '#list' do describe 'with no filters' do let(:identity) { 'ID123' }