diff --git a/lib/fintoc/v1/client/client.rb b/lib/fintoc/v1/client/client.rb index b9c8c37..2b2d4a6 100644 --- a/lib/fintoc/v1/client/client.rb +++ b/lib/fintoc/v1/client/client.rb @@ -1,5 +1,6 @@ require 'fintoc/base_client' require 'fintoc/v1/managers/links_manager' +require 'fintoc/v1/managers/webhook_endpoints_manager' module Fintoc module V1 @@ -7,6 +8,10 @@ class Client < BaseClient def links @links ||= Managers::LinksManager.new(self) end + + def webhook_endpoints + @webhook_endpoints ||= Managers::WebhookEndpointsManager.new(self) + end end end end diff --git a/lib/fintoc/v1/managers/webhook_endpoints_manager.rb b/lib/fintoc/v1/managers/webhook_endpoints_manager.rb new file mode 100644 index 0000000..e297bc2 --- /dev/null +++ b/lib/fintoc/v1/managers/webhook_endpoints_manager.rb @@ -0,0 +1,77 @@ +require 'fintoc/v1/resources/webhook_endpoint' + +module Fintoc + module V1 + module Managers + class WebhookEndpointsManager + def initialize(client) + @client = client + end + + def create(url:, enabled_events:, idempotency_key: nil, **params) + data = _create_webhook_endpoint( + url:, enabled_events:, idempotency_key:, **params + ) + build_webhook_endpoint(data) + end + + def get(webhook_endpoint_id) + data = _get_webhook_endpoint(webhook_endpoint_id) + build_webhook_endpoint(data) + end + + def list(**params) + _list_webhook_endpoints(**params).map { |data| build_webhook_endpoint(data) } + end + + def update(webhook_endpoint_id, idempotency_key: nil, **params) + data = _update_webhook_endpoint(webhook_endpoint_id, idempotency_key:, **params) + build_webhook_endpoint(data) + end + + def delete(webhook_endpoint_id) + _delete_webhook_endpoint(webhook_endpoint_id) + end + + def test(webhook_endpoint_id, type:) + data = _test_webhook_endpoint(webhook_endpoint_id, type:) + build_webhook_endpoint(data) + end + + private + + def _create_webhook_endpoint(url:, enabled_events:, idempotency_key: nil, **params) + request_params = { url:, enabled_events: } + request_params.merge!(params) + + @client.post(version: :v1, idempotency_key:).call('webhook_endpoints', **request_params) + end + + def _get_webhook_endpoint(webhook_endpoint_id) + @client.get(version: :v1).call("webhook_endpoints/#{webhook_endpoint_id}") + end + + def _list_webhook_endpoints(**params) + @client.get(version: :v1).call('webhook_endpoints', **params) + end + + def _update_webhook_endpoint(webhook_endpoint_id, idempotency_key: nil, **params) + @client.patch(version: :v1, idempotency_key:) + .call("webhook_endpoints/#{webhook_endpoint_id}", **params) + end + + def _delete_webhook_endpoint(webhook_endpoint_id) + @client.delete(version: :v1).call("webhook_endpoints/#{webhook_endpoint_id}") + end + + def _test_webhook_endpoint(webhook_endpoint_id, type:) + @client.post(version: :v1).call("webhook_endpoints/#{webhook_endpoint_id}/test", type:) + end + + def build_webhook_endpoint(data) + Fintoc::V1::WebhookEndpoint.new(**data, client: @client) + end + end + end + end +end diff --git a/lib/fintoc/v1/resources/webhook_endpoint.rb b/lib/fintoc/v1/resources/webhook_endpoint.rb new file mode 100644 index 0000000..4f438ab --- /dev/null +++ b/lib/fintoc/v1/resources/webhook_endpoint.rb @@ -0,0 +1,38 @@ +module Fintoc + module V1 + class WebhookEndpoint + attr_reader :id, :object, :url, :mode, :enabled_events, :secret, :disabled, :created_at + + def initialize( + id:, + object:, + url:, + mode:, + enabled_events:, + secret:, + disabled:, + created_at:, + client: nil, + ** + ) + @id = id + @object = object + @url = url + @mode = mode + @enabled_events = enabled_events + @secret = secret + @disabled = disabled + @created_at = created_at + @client = client + end + + def to_s + "🔔 #{@url} (#{@id})" + end + + def disabled? + @disabled + end + end + end +end diff --git a/spec/lib/fintoc/v1/client_spec.rb b/spec/lib/fintoc/v1/client_spec.rb index 7754cae..446734d 100644 --- a/spec/lib/fintoc/v1/client_spec.rb +++ b/spec/lib/fintoc/v1/client_spec.rb @@ -26,4 +26,6 @@ end it_behaves_like 'a client with links manager' + + it_behaves_like 'a client with webhook endpoints manager' end diff --git a/spec/lib/fintoc/v1/managers/webhook_endpoints_manager_spec.rb b/spec/lib/fintoc/v1/managers/webhook_endpoints_manager_spec.rb new file mode 100644 index 0000000..94120eb --- /dev/null +++ b/spec/lib/fintoc/v1/managers/webhook_endpoints_manager_spec.rb @@ -0,0 +1,159 @@ +require 'fintoc/v1/managers/webhook_endpoints_manager' + +RSpec.describe Fintoc::V1::Managers::WebhookEndpointsManager do + let(:client) { instance_double(Fintoc::BaseClient) } + let(:get_proc) { instance_double(Proc) } + let(:post_proc) { instance_double(Proc) } + let(:patch_proc) { instance_double(Proc) } + let(:manager) { described_class.new(client) } + let(:webhook_endpoint_id) { 'we_123' } + let(:url) { 'https://example.com/webhook' } + let(:enabled_events) { ['account.refresh_intent.succeeded'] } + let(:type) { 'account.refresh_intent.succeeded' } + let(:first_webhook_endpoint_data) do + { + id: webhook_endpoint_id, + object: 'webhook_endpoint', + url: url, + mode: 'test', + enabled_events: enabled_events, + secret: 'whsec_abc123', + disabled: false, + created_at: '2026-05-15T12:00:00.000Z' + } + end + let(:second_webhook_endpoint_data) do + { + id: 'we_456', + object: 'webhook_endpoint', + url: 'https://example.com/other-webhook', + mode: 'test', + enabled_events: enabled_events, + secret: 'whsec_def456', + disabled: false, + created_at: '2026-05-15T12:01:00.000Z' + } + end + let(:updated_webhook_endpoint_data) do + first_webhook_endpoint_data.merge(url: 'https://example.com/updated') + end + + before do + allow(client).to receive(:get).with(version: :v1).and_return(get_proc) + allow(client).to receive(:post).with(version: :v1, idempotency_key: nil).and_return(post_proc) + allow(client).to receive(:post).with(version: :v1).and_return(post_proc) + allow(client).to receive(:patch).with(version: :v1, idempotency_key: nil).and_return(patch_proc) + + allow(get_proc) + .to receive(:call) + .with("webhook_endpoints/#{webhook_endpoint_id}") + .and_return(first_webhook_endpoint_data) + allow(get_proc) + .to receive(:call) + .with('webhook_endpoints') + .and_return([first_webhook_endpoint_data, second_webhook_endpoint_data]) + allow(post_proc) + .to receive(:call) + .with('webhook_endpoints', url:, enabled_events:) + .and_return(first_webhook_endpoint_data) + allow(post_proc) + .to receive(:call) + .with("webhook_endpoints/#{webhook_endpoint_id}/test", type:) + .and_return(first_webhook_endpoint_data) + allow(patch_proc) + .to receive(:call) + .with("webhook_endpoints/#{webhook_endpoint_id}", url: 'https://example.com/updated') + .and_return(updated_webhook_endpoint_data) + + allow(Fintoc::V1::WebhookEndpoint).to receive(:new) + end + + describe '#create' do + it 'calls build_webhook_endpoint with the response' do + manager.create(url:, enabled_events:) + expect(Fintoc::V1::WebhookEndpoint) + .to have_received(:new).with(**first_webhook_endpoint_data, client:) + end + + context 'when idempotency_key is provided' do + let(:idempotency_key) { '123e4567-e89b-12d3-a456-426614174000' } + + before do + allow(client).to receive(:post).with(version: :v1, idempotency_key:).and_return(post_proc) + end + + it 'passes idempotency_key to the POST method' do + manager.create(url:, enabled_events:, idempotency_key:) + + expect(client).to have_received(:post).with(version: :v1, idempotency_key:) + expect(Fintoc::V1::WebhookEndpoint) + .to have_received(:new).with(**first_webhook_endpoint_data, client:) + end + end + end + + describe '#get' do + it 'calls build_webhook_endpoint with the response' do + manager.get(webhook_endpoint_id) + expect(Fintoc::V1::WebhookEndpoint) + .to have_received(:new).with(**first_webhook_endpoint_data, client:) + end + end + + describe '#list' do + it 'calls build_webhook_endpoint for each response' do + manager.list + expect(Fintoc::V1::WebhookEndpoint) + .to have_received(:new).with(**first_webhook_endpoint_data, client:) + expect(Fintoc::V1::WebhookEndpoint) + .to have_received(:new).with(**second_webhook_endpoint_data, client:) + end + end + + describe '#update' do + it 'calls build_webhook_endpoint with the response' do + manager.update(webhook_endpoint_id, url: 'https://example.com/updated') + expect(Fintoc::V1::WebhookEndpoint) + .to have_received(:new).with(**updated_webhook_endpoint_data, client:) + end + + context 'when idempotency_key is provided' do + let(:idempotency_key) { '123e4567-e89b-12d3-a456-426614174000' } + + before do + allow(client).to receive(:patch).with(version: :v1, idempotency_key:).and_return(patch_proc) + end + + it 'passes idempotency_key to the PATCH method' do + manager.update(webhook_endpoint_id, url: 'https://example.com/updated', idempotency_key:) + + expect(client).to have_received(:patch).with(version: :v1, idempotency_key:) + end + end + end + + describe '#delete' do + let(:delete_proc) { instance_double(Proc) } + + before do + allow(client).to receive(:delete).with(version: :v1).and_return(delete_proc) + allow(delete_proc) + .to receive(:call) + .with("webhook_endpoints/#{webhook_endpoint_id}") + .and_return(true) + end + + it 'calls delete on the client' do + expect(manager.delete(webhook_endpoint_id)).to be true + end + end + + describe '#test' do + it 'calls build_webhook_endpoint with the response' do + manager.test(webhook_endpoint_id, type:) + + expect(Fintoc::V1::WebhookEndpoint) + .to have_received(:new).with(**first_webhook_endpoint_data, client:) + end + end +end diff --git a/spec/lib/fintoc/v1/webhook_endpoint_spec.rb b/spec/lib/fintoc/v1/webhook_endpoint_spec.rb new file mode 100644 index 0000000..5d283a0 --- /dev/null +++ b/spec/lib/fintoc/v1/webhook_endpoint_spec.rb @@ -0,0 +1,62 @@ +require 'fintoc/v1/resources/webhook_endpoint' + +RSpec.describe Fintoc::V1::WebhookEndpoint do + subject(:webhook_endpoint) { described_class.new(**data) } + + let(:api_key) { 'sk_test_SeCreT-aPi_KeY' } + let(:client) { Fintoc::V1::Client.new(api_key) } + + let(:data) do + { + id: 'we_Kasf91034gj1AD', + object: 'webhook_endpoint', + url: 'https://example.com/webhook', + mode: 'test', + enabled_events: ['account.refresh_intent.succeeded'], + secret: 'whsec_abc123', + disabled: false, + created_at: '2026-05-15T12:00:00.000Z', + client: client + } + end + + describe '#initialize' do + it 'assigns all attributes correctly' do + expect(webhook_endpoint).to have_attributes( + id: 'we_Kasf91034gj1AD', + object: 'webhook_endpoint', + url: 'https://example.com/webhook', + mode: 'test', + enabled_events: ['account.refresh_intent.succeeded'], + secret: 'whsec_abc123', + disabled: false, + created_at: '2026-05-15T12:00:00.000Z' + ) + end + end + + describe '#to_s' do + it 'returns a string representation' do + expected = '🔔 https://example.com/webhook (we_Kasf91034gj1AD)' + expect(webhook_endpoint.to_s).to eq(expected) + end + end + + describe '#disabled?' do + context 'when disabled is true' do + let(:data) do + super().merge(disabled: true) + end + + it 'returns true' do + expect(webhook_endpoint.disabled?).to be true + end + end + + context 'when disabled is false' do + it 'returns false' do + expect(webhook_endpoint.disabled?).to be false + end + end + end +end diff --git a/spec/support/shared_examples/clients/webhook_endpoints_client_examples.rb b/spec/support/shared_examples/clients/webhook_endpoints_client_examples.rb new file mode 100644 index 0000000..ad8206d --- /dev/null +++ b/spec/support/shared_examples/clients/webhook_endpoints_client_examples.rb @@ -0,0 +1,13 @@ +RSpec.shared_examples 'a client with webhook endpoints manager' do + it 'responds to webhook_endpoints-specific methods' do + expect(client).to respond_to(:webhook_endpoints) + expect(client.webhook_endpoints).to be_a(Fintoc::V1::Managers::WebhookEndpointsManager) + expect(client.webhook_endpoints) + .to respond_to(:create) + .and respond_to(:get) + .and respond_to(:list) + .and respond_to(:update) + .and respond_to(:delete) + .and respond_to(:test) + end +end