From 21a451bd53f5090340e8eeed81349f90b6ef2f03 Mon Sep 17 00:00:00 2001 From: Jose Marchant Date: Tue, 31 Mar 2026 12:38:42 -0300 Subject: [PATCH 1/3] feat: add account_statements resource under v2 accounts Add AccountStatement resource and AccountStatementsManager to support the new GET /v2/accounts/{account_id}/account_statements endpoint. --- .../v2/managers/account_statements_manager.rb | 28 +++++++++++ lib/fintoc/v2/resources/account.rb | 5 ++ lib/fintoc/v2/resources/account_statement.rb | 48 +++++++++++++++++++ 3 files changed, 81 insertions(+) create mode 100644 lib/fintoc/v2/managers/account_statements_manager.rb create mode 100644 lib/fintoc/v2/resources/account_statement.rb diff --git a/lib/fintoc/v2/managers/account_statements_manager.rb b/lib/fintoc/v2/managers/account_statements_manager.rb new file mode 100644 index 0000000..982a372 --- /dev/null +++ b/lib/fintoc/v2/managers/account_statements_manager.rb @@ -0,0 +1,28 @@ +require 'fintoc/v2/resources/account_statement' + +module Fintoc + module V2 + module Managers + class AccountStatementsManager + def initialize(client, account_id) + @client = client + @account_id = account_id + end + + def list(**params) + _list_account_statements(**params).map { |data| build_account_statement(data) } + end + + private + + def _list_account_statements(**params) + @client.get(version: :v2).call("accounts/#{@account_id}/account_statements", **params) + end + + def build_account_statement(data) + Fintoc::V2::AccountStatement.new(**data) + end + end + end + end +end diff --git a/lib/fintoc/v2/resources/account.rb b/lib/fintoc/v2/resources/account.rb index 327b518..a864c0b 100644 --- a/lib/fintoc/v2/resources/account.rb +++ b/lib/fintoc/v2/resources/account.rb @@ -1,4 +1,5 @@ require 'money' +require 'fintoc/v2/managers/account_statements_manager' module Fintoc module V2 @@ -81,6 +82,10 @@ def simulate_receive_transfer(amount:, idempotency_key: nil) ) end + def account_statements + @account_statements_manager ||= Managers::AccountStatementsManager.new(@client, @id) + end + private def refresh_from_account(account) diff --git a/lib/fintoc/v2/resources/account_statement.rb b/lib/fintoc/v2/resources/account_statement.rb new file mode 100644 index 0000000..f78dae9 --- /dev/null +++ b/lib/fintoc/v2/resources/account_statement.rb @@ -0,0 +1,48 @@ +module Fintoc + module V2 + class AccountStatement + attr_reader :id, :object, :start_date, :end_date, :total_debited_cents, + :total_credited_cents, :initial_balance_cents, :final_balance_cents, + :download_url, :created_at + + def initialize( + id:, + object:, + start_date:, + end_date:, + total_debited_cents:, + total_credited_cents:, + initial_balance_cents:, + final_balance_cents:, + download_url:, + created_at:, + ** + ) + @id = id + @object = object + @start_date = start_date + @end_date = end_date + @total_debited_cents = total_debited_cents + @total_credited_cents = total_credited_cents + @initial_balance_cents = initial_balance_cents + @final_balance_cents = final_balance_cents + @download_url = download_url + @created_at = created_at + end + + def ==(other) + @id == other.id + end + + alias eql? == + + def hash + @id.hash + end + + def to_s + "AccountStatement(#{@id}) #{@start_date} - #{@end_date}" + end + end + end +end From f963451a06026e43dbb96c6ab2dd6acc4ef45a7c Mon Sep 17 00:00:00 2001 From: Jose Marchant Date: Tue, 31 Mar 2026 12:51:49 -0300 Subject: [PATCH 2/3] test: add tests for account_statements resource and manager Add specs for AccountStatement resource, AccountStatementsManager, and account_statements lazy accessor on Account. --- lib/fintoc/v2/resources/account.rb | 2 +- spec/lib/fintoc/v2/account_spec.rb | 12 ++++ spec/lib/fintoc/v2/account_statement_spec.rb | 66 +++++++++++++++++++ .../account_statements_manager_spec.rb | 57 ++++++++++++++++ 4 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 spec/lib/fintoc/v2/account_statement_spec.rb create mode 100644 spec/lib/fintoc/v2/managers/account_statements_manager_spec.rb diff --git a/lib/fintoc/v2/resources/account.rb b/lib/fintoc/v2/resources/account.rb index a864c0b..1a525c5 100644 --- a/lib/fintoc/v2/resources/account.rb +++ b/lib/fintoc/v2/resources/account.rb @@ -83,7 +83,7 @@ def simulate_receive_transfer(amount:, idempotency_key: nil) end def account_statements - @account_statements_manager ||= Managers::AccountStatementsManager.new(@client, @id) + @account_statements ||= Managers::AccountStatementsManager.new(@client, @id) end private diff --git a/spec/lib/fintoc/v2/account_spec.rb b/spec/lib/fintoc/v2/account_spec.rb index c1fac89..133aad1 100644 --- a/spec/lib/fintoc/v2/account_spec.rb +++ b/spec/lib/fintoc/v2/account_spec.rb @@ -189,6 +189,18 @@ end end + describe '#account_statements' do + it 'returns an AccountStatementsManager instance' do + expect(account.account_statements).to be_an_instance_of(Fintoc::V2::Managers::AccountStatementsManager) + end + + it 'memoizes the manager instance' do + first_call = account.account_statements + second_call = account.account_statements + expect(first_call).to be(second_call) + end + end + describe '#simulate_receive_transfer' do let(:expected_transfer) { instance_double(Fintoc::V2::Transfer) } diff --git a/spec/lib/fintoc/v2/account_statement_spec.rb b/spec/lib/fintoc/v2/account_statement_spec.rb new file mode 100644 index 0000000..782351c --- /dev/null +++ b/spec/lib/fintoc/v2/account_statement_spec.rb @@ -0,0 +1,66 @@ +require 'fintoc/v2/resources/account_statement' + +RSpec.describe Fintoc::V2::AccountStatement do + let(:data) do + { + id: 'acst_12345', + object: 'account_statement', + start_date: '2026-01-01', + end_date: '2026-01-31', + total_debited_cents: 500000, + total_credited_cents: 750000, + initial_balance_cents: 1000000, + final_balance_cents: 1250000, + download_url: 'https://api.fintoc.com/v2/account_statements/acst_12345/download', + created_at: '2026-02-01T00:00:00Z' + } + end + + let(:account_statement) { described_class.new(**data) } + + describe '#new' do + it 'creates an instance of AccountStatement' do + expect(account_statement).to be_an_instance_of(described_class) + end + + it 'sets all attributes correctly' do # rubocop:disable RSpec/ExampleLength + expect(account_statement).to have_attributes( + id: 'acst_12345', + object: 'account_statement', + start_date: '2026-01-01', + end_date: '2026-01-31', + total_debited_cents: 500000, + total_credited_cents: 750000, + initial_balance_cents: 1000000, + final_balance_cents: 1250000, + download_url: 'https://api.fintoc.com/v2/account_statements/acst_12345/download', + created_at: '2026-02-01T00:00:00Z' + ) + end + end + + describe '#to_s' do + it 'returns a formatted string representation' do + expect(account_statement.to_s).to eq('AccountStatement(acst_12345) 2026-01-01 - 2026-01-31') + end + end + + describe '#==' do + it 'returns true when comparing statements with the same id' do + other = described_class.new(**data) + expect(account_statement).to eq(other) + end + + it 'returns false when comparing statements with different ids' do + other = described_class.new(**data, id: 'acst_67890') + expect(account_statement).not_to eq(other) + end + end + + describe '#hash' do + it 'returns the same hash for statements with the same id' do + other = described_class.new(**data) + expect(account_statement.hash).to eq(other.hash) + end + end +end diff --git a/spec/lib/fintoc/v2/managers/account_statements_manager_spec.rb b/spec/lib/fintoc/v2/managers/account_statements_manager_spec.rb new file mode 100644 index 0000000..aaba982 --- /dev/null +++ b/spec/lib/fintoc/v2/managers/account_statements_manager_spec.rb @@ -0,0 +1,57 @@ +require 'fintoc/v2/managers/account_statements_manager' + +RSpec.describe Fintoc::V2::Managers::AccountStatementsManager do + let(:client) { instance_double(Fintoc::BaseClient) } + let(:get_proc) { instance_double(Proc) } + let(:account_id) { 'acc_12345' } + let(:manager) { described_class.new(client, account_id) } + let(:first_statement_data) do + { + id: 'acst_12345', + object: 'account_statement', + start_date: '2026-01-01', + end_date: '2026-01-31', + total_debited_cents: 500000, + total_credited_cents: 750000, + initial_balance_cents: 1000000, + final_balance_cents: 1250000, + download_url: 'https://api.fintoc.com/v2/account_statements/acst_12345/download', + created_at: '2026-02-01T00:00:00Z' + } + end + let(:second_statement_data) do + { + id: 'acst_67890', + object: 'account_statement', + start_date: '2026-02-01', + end_date: '2026-02-28', + total_debited_cents: 300000, + total_credited_cents: 400000, + initial_balance_cents: 1250000, + final_balance_cents: 1350000, + download_url: 'https://api.fintoc.com/v2/account_statements/acst_67890/download', + created_at: '2026-03-01T00:00:00Z' + } + end + + before do + allow(client).to receive(:get).with(version: :v2).and_return(get_proc) + + allow(get_proc) + .to receive(:call) + .with("accounts/#{account_id}/account_statements") + .and_return([first_statement_data, second_statement_data]) + + allow(Fintoc::V2::AccountStatement).to receive(:new) + end + + describe '#list' do + it 'calls build_account_statement for each response' do + manager.list + expect(Fintoc::V2::AccountStatement) + .to have_received(:new).with(**first_statement_data) + expect(Fintoc::V2::AccountStatement) + .to have_received(:new).with(**second_statement_data) + end + end +end From 458b6557f89f517ec7003cc2d9333da8a11f7c41 Mon Sep 17 00:00:00 2001 From: Jose Marchant Date: Tue, 31 Mar 2026 16:46:51 -0300 Subject: [PATCH 3/3] feat: add movements resource under v2 accounts Add Movement resource and MovementsManager to support the GET /v2/accounts/{account_id}/movements endpoint. --- lib/fintoc/v2/managers/movements_manager.rb | 38 ++++++++++ lib/fintoc/v2/resources/account.rb | 5 ++ lib/fintoc/v2/resources/movement.rb | 52 +++++++++++++ spec/lib/fintoc/v2/account_spec.rb | 12 +++ .../v2/managers/movements_manager_spec.rb | 75 +++++++++++++++++++ spec/lib/fintoc/v2/movement_spec.rb | 70 +++++++++++++++++ 6 files changed, 252 insertions(+) create mode 100644 lib/fintoc/v2/managers/movements_manager.rb create mode 100644 lib/fintoc/v2/resources/movement.rb create mode 100644 spec/lib/fintoc/v2/managers/movements_manager_spec.rb create mode 100644 spec/lib/fintoc/v2/movement_spec.rb diff --git a/lib/fintoc/v2/managers/movements_manager.rb b/lib/fintoc/v2/managers/movements_manager.rb new file mode 100644 index 0000000..12728f3 --- /dev/null +++ b/lib/fintoc/v2/managers/movements_manager.rb @@ -0,0 +1,38 @@ +require 'fintoc/v2/resources/movement' + +module Fintoc + module V2 + module Managers + class MovementsManager + def initialize(client, account_id) + @client = client + @account_id = account_id + end + + def list(**params) + _list_movements(**params).map { |data| build_movement(data) } + end + + def get(movement_id, **params) + data = _get_movement(movement_id, **params) + build_movement(data) + end + + private + + def _list_movements(**params) + @client.get(version: :v2).call("accounts/#{@account_id}/movements", **params) + end + + def _get_movement(movement_id, **params) + path = "accounts/#{@account_id}/movements/#{movement_id}" + @client.get(version: :v2).call(path, **params) + end + + def build_movement(data) + Fintoc::V2::Movement.new(**data) + end + end + end + end +end diff --git a/lib/fintoc/v2/resources/account.rb b/lib/fintoc/v2/resources/account.rb index 1a525c5..18ec4be 100644 --- a/lib/fintoc/v2/resources/account.rb +++ b/lib/fintoc/v2/resources/account.rb @@ -1,5 +1,6 @@ require 'money' require 'fintoc/v2/managers/account_statements_manager' +require 'fintoc/v2/managers/movements_manager' module Fintoc module V2 @@ -86,6 +87,10 @@ def account_statements @account_statements ||= Managers::AccountStatementsManager.new(@client, @id) end + def movements + @movements ||= Managers::MovementsManager.new(@client, @id) + end + private def refresh_from_account(account) diff --git a/lib/fintoc/v2/resources/movement.rb b/lib/fintoc/v2/resources/movement.rb new file mode 100644 index 0000000..73cb2f0 --- /dev/null +++ b/lib/fintoc/v2/resources/movement.rb @@ -0,0 +1,52 @@ +module Fintoc + module V2 + class Movement + attr_reader :id, :object, :type, :direction, :resource_id, :mode, + :amount, :currency, :transaction_date, :return_pair_id, + :balance, :account_id + + def initialize( + id:, + object:, + type:, + direction:, + mode:, + amount:, + currency:, + transaction_date:, + balance:, + account_id:, + resource_id: nil, + return_pair_id: nil, + ** + ) + @id = id + @object = object + @type = type + @direction = direction + @resource_id = resource_id + @mode = mode + @amount = amount + @currency = currency + @transaction_date = transaction_date + @return_pair_id = return_pair_id + @balance = balance + @account_id = account_id + end + + def ==(other) + @id == other.id + end + + alias eql? == + + def hash + @id.hash + end + + def to_s + "#{@direction} #{@amount} #{@currency} (#{@type})" + end + end + end +end diff --git a/spec/lib/fintoc/v2/account_spec.rb b/spec/lib/fintoc/v2/account_spec.rb index 133aad1..f018036 100644 --- a/spec/lib/fintoc/v2/account_spec.rb +++ b/spec/lib/fintoc/v2/account_spec.rb @@ -201,6 +201,18 @@ end end + describe '#movements' do + it 'returns a MovementsManager instance' do + expect(account.movements).to be_an_instance_of(Fintoc::V2::Managers::MovementsManager) + end + + it 'memoizes the manager instance' do + first_call = account.movements + second_call = account.movements + expect(first_call).to be(second_call) + end + end + describe '#simulate_receive_transfer' do let(:expected_transfer) { instance_double(Fintoc::V2::Transfer) } diff --git a/spec/lib/fintoc/v2/managers/movements_manager_spec.rb b/spec/lib/fintoc/v2/managers/movements_manager_spec.rb new file mode 100644 index 0000000..ff446e8 --- /dev/null +++ b/spec/lib/fintoc/v2/managers/movements_manager_spec.rb @@ -0,0 +1,75 @@ +require 'fintoc/v2/managers/movements_manager' + +RSpec.describe Fintoc::V2::Managers::MovementsManager do + let(:client) { instance_double(Fintoc::BaseClient) } + let(:get_proc) { instance_double(Proc) } + let(:account_id) { 'acc_12345' } + let(:manager) { described_class.new(client, account_id) } + let(:movement_id) { 'mov_12345' } + let(:first_movement_data) do + { + id: 'mov_12345', + object: 'movement', + type: 'transfer', + direction: 'outbound', + resource_id: 'trf_12345', + mode: 'live', + amount: 150_000, + currency: 'MXN', + transaction_date: '2026-03-15T12:00:00Z', + return_pair_id: nil, + balance: 1_000_000, + account_id: account_id + } + end + let(:second_movement_data) do + { + id: 'mov_67890', + object: 'movement', + type: 'transfer', + direction: 'inbound', + resource_id: 'trf_67890', + mode: 'live', + amount: 200_000, + currency: 'MXN', + transaction_date: '2026-03-16T12:00:00Z', + return_pair_id: nil, + balance: 1_200_000, + account_id: account_id + } + end + + before do + allow(client).to receive(:get).with(version: :v2).and_return(get_proc) + + allow(get_proc) + .to receive(:call) + .with("accounts/#{account_id}/movements") + .and_return([first_movement_data, second_movement_data]) + + allow(get_proc) + .to receive(:call) + .with("accounts/#{account_id}/movements/#{movement_id}") + .and_return(first_movement_data) + + allow(Fintoc::V2::Movement).to receive(:new) + end + + describe '#list' do + it 'calls build_movement for each response' do + manager.list + expect(Fintoc::V2::Movement) + .to have_received(:new).with(**first_movement_data) + expect(Fintoc::V2::Movement) + .to have_received(:new).with(**second_movement_data) + end + end + + describe '#get' do + it 'calls build_movement with the response' do + manager.get(movement_id) + expect(Fintoc::V2::Movement) + .to have_received(:new).with(**first_movement_data) + end + end +end diff --git a/spec/lib/fintoc/v2/movement_spec.rb b/spec/lib/fintoc/v2/movement_spec.rb new file mode 100644 index 0000000..1ddf6b8 --- /dev/null +++ b/spec/lib/fintoc/v2/movement_spec.rb @@ -0,0 +1,70 @@ +require 'fintoc/v2/resources/movement' + +RSpec.describe Fintoc::V2::Movement do + let(:data) do + { + id: 'mov_12345', + object: 'movement', + type: 'transfer', + direction: 'outbound', + resource_id: 'trf_12345', + mode: 'live', + amount: 150_000, + currency: 'MXN', + transaction_date: '2026-03-15T12:00:00Z', + return_pair_id: nil, + balance: 1_000_000, + account_id: 'acc_12345' + } + end + + let(:movement) { described_class.new(**data) } + + describe '#new' do + it 'creates an instance of Movement' do + expect(movement).to be_an_instance_of(described_class) + end + + it 'sets all attributes correctly' do # rubocop:disable RSpec/ExampleLength + expect(movement).to have_attributes( + id: 'mov_12345', + object: 'movement', + type: 'transfer', + direction: 'outbound', + resource_id: 'trf_12345', + mode: 'live', + amount: 150_000, + currency: 'MXN', + transaction_date: '2026-03-15T12:00:00Z', + return_pair_id: nil, + balance: 1_000_000, + account_id: 'acc_12345' + ) + end + end + + describe '#to_s' do + it 'returns a formatted string representation' do + expect(movement.to_s).to eq('outbound 150000 MXN (transfer)') + end + end + + describe '#==' do + it 'returns true when comparing movements with the same id' do + other = described_class.new(**data) + expect(movement).to eq(other) + end + + it 'returns false when comparing movements with different ids' do + other = described_class.new(**data, id: 'mov_67890') + expect(movement).not_to eq(other) + end + end + + describe '#hash' do + it 'returns the same hash for movements with the same id' do + other = described_class.new(**data) + expect(movement.hash).to eq(other.hash) + end + end +end