diff --git a/README.md b/README.md index 40d1be9..b5e11ae 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ - [update](#update) - [delete](#delete) - [Entity onboardings](#entity-onboardings) + - [Transfer batches](#transfer-batches) - [Webhook Signature Validation](#webhook-signature-validation) - [Idempotency Keys](#idempotency-keys) - [Serialization](#serialization) @@ -255,6 +256,38 @@ const submitted = await fintocClient.v2.entities.onboardings.submit( ); ``` +### Transfer batches + +Creating a transfer batch requires a JWS private key, just like transfers: + +```javascript +const fintocClient = new Fintoc('your_api_key', 'path/to/private_key.pem'); + +const transferBatch = await fintocClient.v2.transferBatches.create({ + idempotency_key: '12345678', + description: 'Payroll', + currency: 'MXN', + transfers: [ + { + amount: 54123, + account_id: 'acc_12345678', + counterparty: { + account_number: '012969100000000026', + }, + }, + ], +}); +``` + +You can list batches filtering by `status`, `since` and `until`: + +```javascript +const transferBatches = await fintocClient.v2.transferBatches.list({ + status: ['succeeded', 'partially_succeeded'], + since: '2025-01-01', +}); +``` + ### Webhook Signature Validation To ensure the authenticity of incoming webhooks from Fintoc, you should always validate the signature. The SDK provides a `WebhookSignature` class to verify the `Fintoc-Signature` header diff --git a/src/lib/core.ts b/src/lib/core.ts index ba014e6..fac5b04 100644 --- a/src/lib/core.ts +++ b/src/lib/core.ts @@ -27,6 +27,7 @@ import { PaymentMethodsManager, ProductsManager, SimulateManager, + TransferBatchesManager, TransfersManager, AccountsManager as V2AccountsManager, CheckoutSessionsManager as V2CheckoutSessionsManager, @@ -50,6 +51,7 @@ class FintocV2 { paymentMethods: PaymentMethodsManager; products: ProductsManager; subscriptions: V2SubscriptionsManager; + transferBatches: TransferBatchesManager; constructor(client: Client) { this.accounts = new V2AccountsManager('/v2/accounts', client); @@ -65,6 +67,7 @@ class FintocV2 { this.paymentMethods = new PaymentMethodsManager('/v2/payment_methods', client); this.products = new ProductsManager('/v2/products', client); this.subscriptions = new V2SubscriptionsManager('/v2/subscriptions', client); + this.transferBatches = new TransferBatchesManager('/v2/transfer_batches', client); } } diff --git a/src/lib/managers/v2/index.ts b/src/lib/managers/v2/index.ts index cc34e3b..36e4764 100644 --- a/src/lib/managers/v2/index.ts +++ b/src/lib/managers/v2/index.ts @@ -16,3 +16,4 @@ export * from './subscriptionsManager'; export * from './subscriptionItemsManager'; export * from './accountStatementsManager'; export * from './onboardingsManager'; +export * from './transferBatchesManager'; diff --git a/src/lib/managers/v2/transferBatchesManager.ts b/src/lib/managers/v2/transferBatchesManager.ts new file mode 100644 index 0000000..1683a92 --- /dev/null +++ b/src/lib/managers/v2/transferBatchesManager.ts @@ -0,0 +1,7 @@ +import { ManagerMixin } from '../../mixins'; +import { TransferBatch } from '../../resources/v2/transferBatch'; + +export class TransferBatchesManager extends ManagerMixin { + static resource = 'transferBatch'; + static methods = ['list', 'get', 'create']; +} diff --git a/src/lib/resources/v2/index.ts b/src/lib/resources/v2/index.ts index c3e458e..d5dec20 100644 --- a/src/lib/resources/v2/index.ts +++ b/src/lib/resources/v2/index.ts @@ -18,3 +18,4 @@ export * from './onboarding'; export * from './onboardingShareholder'; export * from './onboardingLegalRepresentative'; export * from './onboardingDocument'; +export * from './transferBatch'; diff --git a/src/lib/resources/v2/transferBatch.ts b/src/lib/resources/v2/transferBatch.ts new file mode 100644 index 0000000..7d650a7 --- /dev/null +++ b/src/lib/resources/v2/transferBatch.ts @@ -0,0 +1,4 @@ +import { ResourceMixin } from '../../mixins/resourceMixin'; + +export class TransferBatch extends ResourceMixin { +} diff --git a/src/spec/integration.spec.ts b/src/spec/integration.spec.ts index 0bb59c3..b245954 100644 --- a/src/spec/integration.spec.ts +++ b/src/spec/integration.spec.ts @@ -1045,6 +1045,103 @@ test('fintoc.v2.transfers.return()', async (t) => { t.is(result.json.transfer_id, returnData.transfer_id); }); +test('fintoc.v2.transferBatches.list()', async (t) => { + const ctx: any = t.context; + const transferBatches = await ctx.fintoc.v2.transferBatches.list(); + + let count = 0; + for await (const transferBatch of transferBatches) { + count += 1; + t.is(transferBatch.method, 'get'); + t.is(transferBatch.url, 'v2/transfer_batches'); + } + + t.true(count > 0); +}); + +test('fintoc.v2.transferBatches.list() with filters', async (t) => { + const ctx: any = t.context; + const filters = { + status: ['succeeded', 'partially_succeeded'], + since: '2025-01-01', + until: '2025-01-31', + }; + const transferBatches = await ctx.fintoc.v2.transferBatches.list(filters); + + let count = 0; + for await (const transferBatch of transferBatches) { + count += 1; + t.is(transferBatch.method, 'get'); + t.is(transferBatch.url, 'v2/transfer_batches'); + t.deepEqual(transferBatch.params.status, filters.status); + t.deepEqual(transferBatch.params.since, new Date(filters.since)); + t.deepEqual(transferBatch.params.until, new Date(filters.until)); + } + + t.true(count > 0); +}); + +test('fintoc.v2.transferBatches.get()', async (t) => { + const ctx: any = t.context; + const transferBatchId = 'transfer_batch_id'; + const transferBatch = await ctx.fintoc.v2.transferBatches.get(transferBatchId); + + t.is(transferBatch.method, 'get'); + t.is(transferBatch.url, `v2/transfer_batches/${transferBatchId}`); +}); + +test('fintoc.v2.transferBatches.create()', async (t) => { + const ctx: any = t.context; + const transferBatchData = { + description: 'Payroll', + currency: 'MXN', + transfers: [ + { + amount: 10000, + account_id: 'acc_123', + counterparty: { + account_number: '012969100000000026', + }, + }, + ], + }; + const transferBatch = await ctx.fintoc.v2.transferBatches.create(transferBatchData); + + t.is(transferBatch.method, 'post'); + t.is(transferBatch.url, 'v2/transfer_batches'); + t.is(transferBatch.json.description, transferBatchData.description); + t.is(transferBatch.json.currency, transferBatchData.currency); + t.is(transferBatch.json.transfers[0].amount, transferBatchData.transfers[0].amount); + t.is( + transferBatch.json.transfers[0].counterparty.account_number, + transferBatchData.transfers[0].counterparty.account_number, + ); + t.true(transferBatch.headers['Idempotency-Key'] && transferBatch.headers['Idempotency-Key'].length > 0); +}); + +test('fintoc.v2.transferBatches.create() with idempotency key', async (t) => { + const ctx: any = t.context; + const transferBatchData = { + description: 'Payroll', + currency: 'MXN', + idempotency_key: 'idempotency_key_123', + transfers: [ + { + amount: 10000, + account_id: 'acc_123', + counterparty: { + account_number: '012969100000000026', + }, + }, + ], + }; + const transferBatch = await ctx.fintoc.v2.transferBatches.create(transferBatchData); + + t.is(transferBatch.method, 'post'); + t.is(transferBatch.url, 'v2/transfer_batches'); + t.is(transferBatch.headers['Idempotency-Key'], transferBatchData.idempotency_key); +}); + test('fintoc.v2.simulate.receiveTransfer()', async (t) => { const ctx: any = t.context; const transferData = {