@vesant-sdk/transaction 0.1.1-next.f504af3 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +3 -11
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4 -12
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -10
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BaseClient, RequestOptions, BaseClientConfig, VesantError } from '@vesant-sdk/core';
|
|
1
|
+
import { BaseClient, RequestOptions, BaseClientConfig, VesantError, WebhookDedupStore } from '@vesant-sdk/core';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* TypeScript types for the Transaction Monitoring (TM) Service
|
|
@@ -282,13 +282,15 @@ interface TransactionWebhookHandlerConfig {
|
|
|
282
282
|
replayProtection?: boolean;
|
|
283
283
|
/** How long (ms) to remember seen events for replay detection. Default: 300_000 (5 min). */
|
|
284
284
|
replayWindow?: number;
|
|
285
|
+
/** Pluggable dedup store for replay protection (default: in-memory, process-local). */
|
|
286
|
+
dedupStore?: WebhookDedupStore;
|
|
285
287
|
}
|
|
286
288
|
declare class TransactionWebhookHandler {
|
|
287
289
|
private readonly handlers;
|
|
288
290
|
private readonly secret;
|
|
289
291
|
private readonly replayProtection;
|
|
290
292
|
private readonly replayWindow;
|
|
291
|
-
private readonly
|
|
293
|
+
private readonly dedupStore;
|
|
292
294
|
private readonly pendingWithdrawals;
|
|
293
295
|
private callbackHandler;
|
|
294
296
|
constructor(config: TransactionWebhookHandlerConfig);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BaseClient, RequestOptions, BaseClientConfig, VesantError } from '@vesant-sdk/core';
|
|
1
|
+
import { BaseClient, RequestOptions, BaseClientConfig, VesantError, WebhookDedupStore } from '@vesant-sdk/core';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* TypeScript types for the Transaction Monitoring (TM) Service
|
|
@@ -282,13 +282,15 @@ interface TransactionWebhookHandlerConfig {
|
|
|
282
282
|
replayProtection?: boolean;
|
|
283
283
|
/** How long (ms) to remember seen events for replay detection. Default: 300_000 (5 min). */
|
|
284
284
|
replayWindow?: number;
|
|
285
|
+
/** Pluggable dedup store for replay protection (default: in-memory, process-local). */
|
|
286
|
+
dedupStore?: WebhookDedupStore;
|
|
285
287
|
}
|
|
286
288
|
declare class TransactionWebhookHandler {
|
|
287
289
|
private readonly handlers;
|
|
288
290
|
private readonly secret;
|
|
289
291
|
private readonly replayProtection;
|
|
290
292
|
private readonly replayWindow;
|
|
291
|
-
private readonly
|
|
293
|
+
private readonly dedupStore;
|
|
292
294
|
private readonly pendingWithdrawals;
|
|
293
295
|
private callbackHandler;
|
|
294
296
|
constructor(config: TransactionWebhookHandlerConfig);
|
package/dist/index.js
CHANGED
|
@@ -154,12 +154,12 @@ var TransactionWebhookHandler = class {
|
|
|
154
154
|
"tm.transaction.callback": [],
|
|
155
155
|
"tm.transaction.held": []
|
|
156
156
|
};
|
|
157
|
-
this.seenEvents = /* @__PURE__ */ new Map();
|
|
158
157
|
this.pendingWithdrawals = /* @__PURE__ */ new Map();
|
|
159
158
|
this.callbackHandler = null;
|
|
160
159
|
this.secret = config.secret;
|
|
161
160
|
this.replayProtection = config.replayProtection ?? true;
|
|
162
161
|
this.replayWindow = config.replayWindow ?? 3e5;
|
|
162
|
+
this.dedupStore = config.dedupStore ?? new core.InMemoryDedupStore();
|
|
163
163
|
}
|
|
164
164
|
on(eventType, handler) {
|
|
165
165
|
this.handlers[eventType].push(handler);
|
|
@@ -204,21 +204,13 @@ var TransactionWebhookHandler = class {
|
|
|
204
204
|
const event = this.parse(body);
|
|
205
205
|
if (this.replayProtection) {
|
|
206
206
|
const key = `${event.event_type}:${event.tx_id}`;
|
|
207
|
-
if (this.
|
|
207
|
+
if (await this.dedupStore.seen(key)) {
|
|
208
208
|
throw new core.ValidationError(
|
|
209
209
|
`Duplicate webhook event: ${event.event_type} for ${event.tx_id} has already been processed`,
|
|
210
210
|
["tx_id"]
|
|
211
211
|
);
|
|
212
212
|
}
|
|
213
|
-
|
|
214
|
-
this.seenEvents.set(key, now);
|
|
215
|
-
if (this.seenEvents.size > 1e3) {
|
|
216
|
-
for (const [k, seenAt] of this.seenEvents) {
|
|
217
|
-
if (now - seenAt > this.replayWindow) {
|
|
218
|
-
this.seenEvents.delete(k);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
213
|
+
await this.dedupStore.mark(key, this.replayWindow);
|
|
222
214
|
}
|
|
223
215
|
return event;
|
|
224
216
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/pending-withdrawal.ts","../src/tax/client.ts","../src/client.ts","../src/webhook-handler.ts"],"names":["VesantError","BaseClient","verifyWebhookSignature","ValidationError"],"mappings":"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","file":"index.js","sourcesContent":["import { VesantError } from '@vesant-sdk/core';\nimport type { TransactionCallbackEvent, HoldReason } from './types';\n\nexport class DuplicateTransactionError extends VesantError {\n readonly tx_id: string;\n\n constructor(tx_id: string, message = `Duplicate transaction: ${tx_id} already exists`) {\n super(message, 'DUPLICATE_TRANSACTION', 409);\n this.name = 'DuplicateTransactionError';\n this.tx_id = tx_id;\n Object.setPrototypeOf(this, DuplicateTransactionError.prototype);\n }\n}\n\nexport class TaxHoldError extends Error {\n readonly tx_id: string;\n readonly hold_reason: HoldReason;\n readonly event: TransactionCallbackEvent;\n\n constructor(event: TransactionCallbackEvent) {\n super(`Transaction ${event.tx_id} held: ${event.hold_reason}`);\n this.name = 'TaxHoldError';\n this.tx_id = event.tx_id;\n this.hold_reason = event.hold_reason as HoldReason;\n this.event = event;\n Object.setPrototypeOf(this, TaxHoldError.prototype);\n }\n}\n","import { VesantError } from '@vesant-sdk/core';\nimport { TaxHoldError } from './errors';\nimport type { Transaction, TransactionCallbackEvent } from './types';\n\nexport type WithdrawalState = 'vesant_pending' | 'withheld' | 'suspend' | 'released' | 'failed';\n\nexport class PendingWithdrawal {\n private _state: WithdrawalState = 'vesant_pending';\n private _event: TransactionCallbackEvent | null = null;\n private _error: unknown = undefined;\n\n // Deferred promise — created lazily only when wait() is called.\n private _waitPromise: Promise<TransactionCallbackEvent> | null = null;\n private _resolveWait?: (event: TransactionCallbackEvent) => void;\n private _rejectWait?: (err: unknown) => void;\n\n readonly tx_id: string;\n readonly initialTransaction: Transaction;\n\n constructor(tx_id: string, transaction: Transaction) {\n this.tx_id = tx_id;\n this.initialTransaction = transaction;\n }\n\n status(): WithdrawalState {\n return this._state;\n }\n\n callbackEvent(): TransactionCallbackEvent | null {\n return this._event;\n }\n\n wait(signal?: AbortSignal): Promise<TransactionCallbackEvent> {\n if (this._state !== 'vesant_pending') {\n if (this._state === 'suspend' || this._state === 'failed') {\n return Promise.reject(this._error);\n }\n if (this._event !== null) {\n return Promise.resolve(this._event);\n }\n }\n\n if (!this._waitPromise) {\n this._waitPromise = new Promise<TransactionCallbackEvent>((resolve, reject) => {\n this._resolveWait = resolve;\n this._rejectWait = reject;\n });\n }\n\n if (signal) {\n if (signal.aborted) {\n this._fail(new VesantError('Request aborted', 'REQUEST_ABORTED'));\n } else {\n signal.addEventListener('abort', () => {\n this._fail(new VesantError('Request aborted', 'REQUEST_ABORTED'));\n }, { once: true });\n }\n }\n\n return this._waitPromise;\n }\n\n /** Called by TransactionWebhookHandler when the tm.transaction.callback arrives for this tx_id. */\n _settle(event: TransactionCallbackEvent): void {\n if (this._state !== 'vesant_pending') return;\n this._event = event;\n if (event.status === 'hold' || event.status === 'suspend') {\n this._state = 'suspend';\n this._error = new TaxHoldError(event);\n this._rejectWait?.(this._error);\n } else if (event.withholding_type === 'none') {\n this._state = 'released';\n this._resolveWait?.(event);\n } else {\n this._state = 'withheld';\n this._resolveWait?.(event);\n }\n }\n\n /** Called to fail the pending withdrawal with a generic error (e.g. abort, network loss). */\n _fail(err: unknown): void {\n if (this._state !== 'vesant_pending') return;\n this._state = 'failed';\n this._error = err;\n this._rejectWait?.(err);\n }\n}\n","import { BaseClient } from '@vesant-sdk/core';\nimport type { RequestOptions } from '@vesant-sdk/core';\nimport type { RequestTaxFormWithProfileInput, RequestTaxFormWithProfileOutput, CustomerTaxProfileDocuments } from './types';\n\nexport class TaxTransactionClient extends BaseClient {\n async getCustomerDocuments(customerID: string): Promise<CustomerTaxProfileDocuments> {\n return this.request<CustomerTaxProfileDocuments>(\n `/api/v1/tm/customer-tax-profiles/${customerID}/documents`\n );\n }\n\n async requestTaxFormWithProfile(\n input: RequestTaxFormWithProfileInput,\n requestOptions?: RequestOptions\n ): Promise<RequestTaxFormWithProfileOutput> {\n return this.requestWithRetry<RequestTaxFormWithProfileOutput>(\n '/api/v1/tax/customer-tax-profiles/request-form-with-profile',\n {\n method: 'POST',\n body: JSON.stringify(input),\n },\n undefined,\n undefined,\n requestOptions\n );\n }\n}\n","/**\n * TransactionClient — SDK for the Transaction Monitoring service\n */\nimport { BaseClient, VesantError } from '@vesant-sdk/core';\nimport type { BaseClientConfig, RequestOptions } from '@vesant-sdk/core';\nimport type { Transaction, TransactionCreateDTO, TransactionCreateResponse, TmTenantSettings } from './types';\nimport { PendingWithdrawal } from './pending-withdrawal';\nimport { TaxTransactionClient } from './tax/client';\n\nexport class TransactionClient extends BaseClient {\n readonly tax: TaxTransactionClient;\n\n constructor(config: BaseClientConfig) {\n super(config);\n this.tax = new TaxTransactionClient(config);\n }\n\n async createTransaction(\n request: TransactionCreateDTO,\n requestOptions?: RequestOptions\n ): Promise<TransactionCreateResponse> {\n try {\n return await this.requestWithRetry<TransactionCreateResponse>('/api/v1/tm/transactions', {\n method: 'POST',\n body: JSON.stringify(request),\n }, undefined, undefined, requestOptions);\n } catch (error) {\n if (error instanceof VesantError && error.statusCode === 409) {\n const transaction = await this.getTransaction(request.tx_id, requestOptions);\n return { transaction, message: 'Transaction already exists' };\n }\n throw error;\n }\n }\n\n async submitWithdrawal(\n request: TransactionCreateDTO,\n requestOptions?: RequestOptions\n ): Promise<PendingWithdrawal> {\n const response = await this.createTransaction(\n { ...request, transaction_type: 'withdrawal' },\n requestOptions\n );\n return new PendingWithdrawal(response.transaction.tx_id, response.transaction);\n }\n\n async getTransaction(transactionId: string, requestOptions?: RequestOptions): Promise<Transaction> {\n return this.requestWithRetry<Transaction>(`/api/v1/tm/transactions/status/${transactionId}`, {\n method: 'GET',\n }, undefined, undefined, requestOptions);\n }\n\n async getSettings(): Promise<TmTenantSettings> {\n return this.requestWithRetry<TmTenantSettings>('/api/v1/tm/settings', {\n method: 'GET',\n });\n }\n}\n","import { verifyWebhookSignature, ValidationError } from '@vesant-sdk/core';\nimport { PendingWithdrawal } from './pending-withdrawal';\nimport type {\n TransactionCallbackEvent,\n TransactionCallbackHandler,\n TransactionHeldEvent,\n TransactionWebhookEvent,\n} from './types';\n\ntype EventHandler<T> = (event: T) => void | Promise<void>;\n\ntype HandlerMap = {\n 'tm.transaction.callback': Array<EventHandler<TransactionCallbackEvent>>;\n 'tm.transaction.held': Array<EventHandler<TransactionHeldEvent>>;\n};\n\nexport const TRANSACTION_SIGNATURE_HEADER = 'x-webhook-signature';\n\nexport interface TransactionWebhookHandlerConfig {\n secret: string;\n /** Reject duplicate event_type+tx_id pairs within replayWindow. Default: true. */\n replayProtection?: boolean;\n /** How long (ms) to remember seen events for replay detection. Default: 300_000 (5 min). */\n replayWindow?: number;\n}\n\nexport class TransactionWebhookHandler {\n private readonly handlers: HandlerMap = {\n 'tm.transaction.callback': [],\n 'tm.transaction.held': [],\n };\n private readonly secret: string;\n private readonly replayProtection: boolean;\n private readonly replayWindow: number;\n private readonly seenEvents = new Map<string, number>();\n private readonly pendingWithdrawals = new Map<string, PendingWithdrawal>();\n private callbackHandler: TransactionCallbackHandler | null = null;\n\n constructor(config: TransactionWebhookHandlerConfig) {\n this.secret = config.secret;\n this.replayProtection = config.replayProtection ?? true;\n this.replayWindow = config.replayWindow ?? 300_000;\n }\n\n on(eventType: 'tm.transaction.callback', handler: EventHandler<TransactionCallbackEvent>): this;\n on(eventType: 'tm.transaction.held', handler: EventHandler<TransactionHeldEvent>): this;\n on(eventType: keyof HandlerMap, handler: EventHandler<TransactionCallbackEvent> | EventHandler<TransactionHeldEvent>): this {\n (this.handlers[eventType] as Array<typeof handler>).push(handler);\n return this;\n }\n\n /** Register a semantic callback handler for the three withholding outcomes. Replaces any previous handler. */\n registerCallbackHandler(handler: TransactionCallbackHandler): this {\n this.callbackHandler = handler;\n return this;\n }\n\n /** Track a PendingWithdrawal so it is auto-settled when its tm.transaction.callback arrives. */\n link(pending: PendingWithdrawal): this {\n this.pendingWithdrawals.set(pending.tx_id, pending);\n return this;\n }\n\n /** Stop tracking a PendingWithdrawal by tx_id. */\n unlink(tx_id: string): this {\n this.pendingWithdrawals.delete(tx_id);\n return this;\n }\n\n /** Verify HMAC-SHA256 signature only. Returns false instead of throwing. */\n async verify(rawBody: string, signature: string): Promise<boolean> {\n return verifyWebhookSignature(rawBody, signature, this.secret);\n }\n\n /** Parse and validate required fields. Does not verify signature — use verifyAndParse in production. */\n parse(body: string): TransactionWebhookEvent {\n const event = JSON.parse(body) as TransactionWebhookEvent;\n if (!event.event_type || !event.tx_id) {\n throw new ValidationError(\n 'Invalid TM webhook event: missing required fields (event_type, tx_id)',\n ['event_type', 'tx_id']\n );\n }\n return event;\n }\n\n /** Verify signature, parse, and check for replays. Throws ValidationError on any failure. */\n async verifyAndParse(body: string, signature: string): Promise<TransactionWebhookEvent> {\n const isValid = await verifyWebhookSignature(body, signature, this.secret);\n if (!isValid) {\n throw new ValidationError('Invalid webhook signature', ['signature']);\n }\n\n const event = this.parse(body);\n\n if (this.replayProtection) {\n const key = `${event.event_type}:${event.tx_id}`;\n if (this.seenEvents.has(key)) {\n throw new ValidationError(\n `Duplicate webhook event: ${event.event_type} for ${event.tx_id} has already been processed`,\n ['tx_id']\n );\n }\n const now = Date.now();\n this.seenEvents.set(key, now);\n if (this.seenEvents.size > 1000) {\n for (const [k, seenAt] of this.seenEvents) {\n if (now - seenAt > this.replayWindow) {\n this.seenEvents.delete(k);\n }\n }\n }\n }\n\n return event;\n }\n\n /** Verify signature, parse, and dispatch to registered handlers. */\n async handle(body: string, signature: string): Promise<void> {\n const event = await this.verifyAndParse(body, signature);\n\n if (event.event_type === 'tm.transaction.callback') {\n const pending = this.pendingWithdrawals.get(event.tx_id);\n if (pending) {\n pending._settle(event);\n this.pendingWithdrawals.delete(event.tx_id);\n }\n\n if (this.callbackHandler) {\n if (event.status === 'hold' || event.status === 'suspend') {\n await this.callbackHandler.onHeld?.(event);\n } else if (event.withholding_type === 'none') {\n await this.callbackHandler.onReleased?.(event);\n } else {\n await this.callbackHandler.onWithheld?.(event);\n }\n }\n }\n\n const handlers = this.handlers[event.event_type] as Array<EventHandler<typeof event>>;\n for (const handler of handlers) {\n await handler(event);\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/pending-withdrawal.ts","../src/tax/client.ts","../src/client.ts","../src/webhook-handler.ts"],"names":["VesantError","BaseClient","InMemoryDedupStore","verifyWebhookSignature","ValidationError"],"mappings":"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","file":"index.js","sourcesContent":["import { VesantError } from '@vesant-sdk/core';\nimport type { TransactionCallbackEvent, HoldReason } from './types';\n\nexport class DuplicateTransactionError extends VesantError {\n readonly tx_id: string;\n\n constructor(tx_id: string, message = `Duplicate transaction: ${tx_id} already exists`) {\n super(message, 'DUPLICATE_TRANSACTION', 409);\n this.name = 'DuplicateTransactionError';\n this.tx_id = tx_id;\n Object.setPrototypeOf(this, DuplicateTransactionError.prototype);\n }\n}\n\nexport class TaxHoldError extends Error {\n readonly tx_id: string;\n readonly hold_reason: HoldReason;\n readonly event: TransactionCallbackEvent;\n\n constructor(event: TransactionCallbackEvent) {\n super(`Transaction ${event.tx_id} held: ${event.hold_reason}`);\n this.name = 'TaxHoldError';\n this.tx_id = event.tx_id;\n this.hold_reason = event.hold_reason as HoldReason;\n this.event = event;\n Object.setPrototypeOf(this, TaxHoldError.prototype);\n }\n}\n","import { VesantError } from '@vesant-sdk/core';\nimport { TaxHoldError } from './errors';\nimport type { Transaction, TransactionCallbackEvent } from './types';\n\nexport type WithdrawalState = 'vesant_pending' | 'withheld' | 'suspend' | 'released' | 'failed';\n\nexport class PendingWithdrawal {\n private _state: WithdrawalState = 'vesant_pending';\n private _event: TransactionCallbackEvent | null = null;\n private _error: unknown = undefined;\n\n // Deferred promise — created lazily only when wait() is called.\n private _waitPromise: Promise<TransactionCallbackEvent> | null = null;\n private _resolveWait?: (event: TransactionCallbackEvent) => void;\n private _rejectWait?: (err: unknown) => void;\n\n readonly tx_id: string;\n readonly initialTransaction: Transaction;\n\n constructor(tx_id: string, transaction: Transaction) {\n this.tx_id = tx_id;\n this.initialTransaction = transaction;\n }\n\n status(): WithdrawalState {\n return this._state;\n }\n\n callbackEvent(): TransactionCallbackEvent | null {\n return this._event;\n }\n\n wait(signal?: AbortSignal): Promise<TransactionCallbackEvent> {\n if (this._state !== 'vesant_pending') {\n if (this._state === 'suspend' || this._state === 'failed') {\n return Promise.reject(this._error);\n }\n if (this._event !== null) {\n return Promise.resolve(this._event);\n }\n }\n\n if (!this._waitPromise) {\n this._waitPromise = new Promise<TransactionCallbackEvent>((resolve, reject) => {\n this._resolveWait = resolve;\n this._rejectWait = reject;\n });\n }\n\n if (signal) {\n if (signal.aborted) {\n this._fail(new VesantError('Request aborted', 'REQUEST_ABORTED'));\n } else {\n signal.addEventListener('abort', () => {\n this._fail(new VesantError('Request aborted', 'REQUEST_ABORTED'));\n }, { once: true });\n }\n }\n\n return this._waitPromise;\n }\n\n /** Called by TransactionWebhookHandler when the tm.transaction.callback arrives for this tx_id. */\n _settle(event: TransactionCallbackEvent): void {\n if (this._state !== 'vesant_pending') return;\n this._event = event;\n if (event.status === 'hold' || event.status === 'suspend') {\n this._state = 'suspend';\n this._error = new TaxHoldError(event);\n this._rejectWait?.(this._error);\n } else if (event.withholding_type === 'none') {\n this._state = 'released';\n this._resolveWait?.(event);\n } else {\n this._state = 'withheld';\n this._resolveWait?.(event);\n }\n }\n\n /** Called to fail the pending withdrawal with a generic error (e.g. abort, network loss). */\n _fail(err: unknown): void {\n if (this._state !== 'vesant_pending') return;\n this._state = 'failed';\n this._error = err;\n this._rejectWait?.(err);\n }\n}\n","import { BaseClient } from '@vesant-sdk/core';\nimport type { RequestOptions } from '@vesant-sdk/core';\nimport type { RequestTaxFormWithProfileInput, RequestTaxFormWithProfileOutput, CustomerTaxProfileDocuments } from './types';\n\nexport class TaxTransactionClient extends BaseClient {\n async getCustomerDocuments(customerID: string): Promise<CustomerTaxProfileDocuments> {\n return this.request<CustomerTaxProfileDocuments>(\n `/api/v1/tm/customer-tax-profiles/${customerID}/documents`\n );\n }\n\n async requestTaxFormWithProfile(\n input: RequestTaxFormWithProfileInput,\n requestOptions?: RequestOptions\n ): Promise<RequestTaxFormWithProfileOutput> {\n return this.requestWithRetry<RequestTaxFormWithProfileOutput>(\n '/api/v1/tax/customer-tax-profiles/request-form-with-profile',\n {\n method: 'POST',\n body: JSON.stringify(input),\n },\n undefined,\n undefined,\n requestOptions\n );\n }\n}\n","/**\n * TransactionClient — SDK for the Transaction Monitoring service\n */\nimport { BaseClient, VesantError } from '@vesant-sdk/core';\nimport type { BaseClientConfig, RequestOptions } from '@vesant-sdk/core';\nimport type { Transaction, TransactionCreateDTO, TransactionCreateResponse, TmTenantSettings } from './types';\nimport { PendingWithdrawal } from './pending-withdrawal';\nimport { TaxTransactionClient } from './tax/client';\n\nexport class TransactionClient extends BaseClient {\n readonly tax: TaxTransactionClient;\n\n constructor(config: BaseClientConfig) {\n super(config);\n this.tax = new TaxTransactionClient(config);\n }\n\n async createTransaction(\n request: TransactionCreateDTO,\n requestOptions?: RequestOptions\n ): Promise<TransactionCreateResponse> {\n try {\n return await this.requestWithRetry<TransactionCreateResponse>('/api/v1/tm/transactions', {\n method: 'POST',\n body: JSON.stringify(request),\n }, undefined, undefined, requestOptions);\n } catch (error) {\n if (error instanceof VesantError && error.statusCode === 409) {\n const transaction = await this.getTransaction(request.tx_id, requestOptions);\n return { transaction, message: 'Transaction already exists' };\n }\n throw error;\n }\n }\n\n async submitWithdrawal(\n request: TransactionCreateDTO,\n requestOptions?: RequestOptions\n ): Promise<PendingWithdrawal> {\n const response = await this.createTransaction(\n { ...request, transaction_type: 'withdrawal' },\n requestOptions\n );\n return new PendingWithdrawal(response.transaction.tx_id, response.transaction);\n }\n\n async getTransaction(transactionId: string, requestOptions?: RequestOptions): Promise<Transaction> {\n return this.requestWithRetry<Transaction>(`/api/v1/tm/transactions/status/${transactionId}`, {\n method: 'GET',\n }, undefined, undefined, requestOptions);\n }\n\n async getSettings(): Promise<TmTenantSettings> {\n return this.requestWithRetry<TmTenantSettings>('/api/v1/tm/settings', {\n method: 'GET',\n });\n }\n}\n","import { verifyWebhookSignature, ValidationError, InMemoryDedupStore, type WebhookDedupStore } from '@vesant-sdk/core';\nimport { PendingWithdrawal } from './pending-withdrawal';\nimport type {\n TransactionCallbackEvent,\n TransactionCallbackHandler,\n TransactionHeldEvent,\n TransactionWebhookEvent,\n} from './types';\n\ntype EventHandler<T> = (event: T) => void | Promise<void>;\n\ntype HandlerMap = {\n 'tm.transaction.callback': Array<EventHandler<TransactionCallbackEvent>>;\n 'tm.transaction.held': Array<EventHandler<TransactionHeldEvent>>;\n};\n\nexport const TRANSACTION_SIGNATURE_HEADER = 'x-webhook-signature';\n\nexport interface TransactionWebhookHandlerConfig {\n secret: string;\n /** Reject duplicate event_type+tx_id pairs within replayWindow. Default: true. */\n replayProtection?: boolean;\n /** How long (ms) to remember seen events for replay detection. Default: 300_000 (5 min). */\n replayWindow?: number;\n /** Pluggable dedup store for replay protection (default: in-memory, process-local). */\n dedupStore?: WebhookDedupStore;\n}\n\nexport class TransactionWebhookHandler {\n private readonly handlers: HandlerMap = {\n 'tm.transaction.callback': [],\n 'tm.transaction.held': [],\n };\n private readonly secret: string;\n private readonly replayProtection: boolean;\n private readonly replayWindow: number;\n private readonly dedupStore: WebhookDedupStore;\n private readonly pendingWithdrawals = new Map<string, PendingWithdrawal>();\n private callbackHandler: TransactionCallbackHandler | null = null;\n\n constructor(config: TransactionWebhookHandlerConfig) {\n this.secret = config.secret;\n this.replayProtection = config.replayProtection ?? true;\n this.replayWindow = config.replayWindow ?? 300_000;\n this.dedupStore = config.dedupStore ?? new InMemoryDedupStore();\n }\n\n on(eventType: 'tm.transaction.callback', handler: EventHandler<TransactionCallbackEvent>): this;\n on(eventType: 'tm.transaction.held', handler: EventHandler<TransactionHeldEvent>): this;\n on(eventType: keyof HandlerMap, handler: EventHandler<TransactionCallbackEvent> | EventHandler<TransactionHeldEvent>): this {\n (this.handlers[eventType] as Array<typeof handler>).push(handler);\n return this;\n }\n\n /** Register a semantic callback handler for the three withholding outcomes. Replaces any previous handler. */\n registerCallbackHandler(handler: TransactionCallbackHandler): this {\n this.callbackHandler = handler;\n return this;\n }\n\n /** Track a PendingWithdrawal so it is auto-settled when its tm.transaction.callback arrives. */\n link(pending: PendingWithdrawal): this {\n this.pendingWithdrawals.set(pending.tx_id, pending);\n return this;\n }\n\n /** Stop tracking a PendingWithdrawal by tx_id. */\n unlink(tx_id: string): this {\n this.pendingWithdrawals.delete(tx_id);\n return this;\n }\n\n /** Verify HMAC-SHA256 signature only. Returns false instead of throwing. */\n async verify(rawBody: string, signature: string): Promise<boolean> {\n return verifyWebhookSignature(rawBody, signature, this.secret);\n }\n\n /** Parse and validate required fields. Does not verify signature — use verifyAndParse in production. */\n parse(body: string): TransactionWebhookEvent {\n const event = JSON.parse(body) as TransactionWebhookEvent;\n if (!event.event_type || !event.tx_id) {\n throw new ValidationError(\n 'Invalid TM webhook event: missing required fields (event_type, tx_id)',\n ['event_type', 'tx_id']\n );\n }\n return event;\n }\n\n /** Verify signature, parse, and check for replays. Throws ValidationError on any failure. */\n async verifyAndParse(body: string, signature: string): Promise<TransactionWebhookEvent> {\n const isValid = await verifyWebhookSignature(body, signature, this.secret);\n if (!isValid) {\n throw new ValidationError('Invalid webhook signature', ['signature']);\n }\n\n const event = this.parse(body);\n\n if (this.replayProtection) {\n const key = `${event.event_type}:${event.tx_id}`;\n if (await this.dedupStore.seen(key)) {\n throw new ValidationError(\n `Duplicate webhook event: ${event.event_type} for ${event.tx_id} has already been processed`,\n ['tx_id']\n );\n }\n await this.dedupStore.mark(key, this.replayWindow);\n }\n\n return event;\n }\n\n /** Verify signature, parse, and dispatch to registered handlers. */\n async handle(body: string, signature: string): Promise<void> {\n const event = await this.verifyAndParse(body, signature);\n\n if (event.event_type === 'tm.transaction.callback') {\n const pending = this.pendingWithdrawals.get(event.tx_id);\n if (pending) {\n pending._settle(event);\n this.pendingWithdrawals.delete(event.tx_id);\n }\n\n if (this.callbackHandler) {\n if (event.status === 'hold' || event.status === 'suspend') {\n await this.callbackHandler.onHeld?.(event);\n } else if (event.withholding_type === 'none') {\n await this.callbackHandler.onReleased?.(event);\n } else {\n await this.callbackHandler.onWithheld?.(event);\n }\n }\n }\n\n const handlers = this.handlers[event.event_type] as Array<EventHandler<typeof event>>;\n for (const handler of handlers) {\n await handler(event);\n }\n }\n}\n"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { VesantError, BaseClient, verifyWebhookSignature, ValidationError } from '@vesant-sdk/core';
|
|
1
|
+
import { VesantError, BaseClient, InMemoryDedupStore, verifyWebhookSignature, ValidationError } from '@vesant-sdk/core';
|
|
2
2
|
|
|
3
3
|
// src/client.ts
|
|
4
4
|
var DuplicateTransactionError = class _DuplicateTransactionError extends VesantError {
|
|
@@ -152,12 +152,12 @@ var TransactionWebhookHandler = class {
|
|
|
152
152
|
"tm.transaction.callback": [],
|
|
153
153
|
"tm.transaction.held": []
|
|
154
154
|
};
|
|
155
|
-
this.seenEvents = /* @__PURE__ */ new Map();
|
|
156
155
|
this.pendingWithdrawals = /* @__PURE__ */ new Map();
|
|
157
156
|
this.callbackHandler = null;
|
|
158
157
|
this.secret = config.secret;
|
|
159
158
|
this.replayProtection = config.replayProtection ?? true;
|
|
160
159
|
this.replayWindow = config.replayWindow ?? 3e5;
|
|
160
|
+
this.dedupStore = config.dedupStore ?? new InMemoryDedupStore();
|
|
161
161
|
}
|
|
162
162
|
on(eventType, handler) {
|
|
163
163
|
this.handlers[eventType].push(handler);
|
|
@@ -202,21 +202,13 @@ var TransactionWebhookHandler = class {
|
|
|
202
202
|
const event = this.parse(body);
|
|
203
203
|
if (this.replayProtection) {
|
|
204
204
|
const key = `${event.event_type}:${event.tx_id}`;
|
|
205
|
-
if (this.
|
|
205
|
+
if (await this.dedupStore.seen(key)) {
|
|
206
206
|
throw new ValidationError(
|
|
207
207
|
`Duplicate webhook event: ${event.event_type} for ${event.tx_id} has already been processed`,
|
|
208
208
|
["tx_id"]
|
|
209
209
|
);
|
|
210
210
|
}
|
|
211
|
-
|
|
212
|
-
this.seenEvents.set(key, now);
|
|
213
|
-
if (this.seenEvents.size > 1e3) {
|
|
214
|
-
for (const [k, seenAt] of this.seenEvents) {
|
|
215
|
-
if (now - seenAt > this.replayWindow) {
|
|
216
|
-
this.seenEvents.delete(k);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
211
|
+
await this.dedupStore.mark(key, this.replayWindow);
|
|
220
212
|
}
|
|
221
213
|
return event;
|
|
222
214
|
}
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/pending-withdrawal.ts","../src/tax/client.ts","../src/client.ts","../src/webhook-handler.ts"],"names":["VesantError","BaseClient"],"mappings":"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","file":"index.mjs","sourcesContent":["import { VesantError } from '@vesant-sdk/core';\nimport type { TransactionCallbackEvent, HoldReason } from './types';\n\nexport class DuplicateTransactionError extends VesantError {\n readonly tx_id: string;\n\n constructor(tx_id: string, message = `Duplicate transaction: ${tx_id} already exists`) {\n super(message, 'DUPLICATE_TRANSACTION', 409);\n this.name = 'DuplicateTransactionError';\n this.tx_id = tx_id;\n Object.setPrototypeOf(this, DuplicateTransactionError.prototype);\n }\n}\n\nexport class TaxHoldError extends Error {\n readonly tx_id: string;\n readonly hold_reason: HoldReason;\n readonly event: TransactionCallbackEvent;\n\n constructor(event: TransactionCallbackEvent) {\n super(`Transaction ${event.tx_id} held: ${event.hold_reason}`);\n this.name = 'TaxHoldError';\n this.tx_id = event.tx_id;\n this.hold_reason = event.hold_reason as HoldReason;\n this.event = event;\n Object.setPrototypeOf(this, TaxHoldError.prototype);\n }\n}\n","import { VesantError } from '@vesant-sdk/core';\nimport { TaxHoldError } from './errors';\nimport type { Transaction, TransactionCallbackEvent } from './types';\n\nexport type WithdrawalState = 'vesant_pending' | 'withheld' | 'suspend' | 'released' | 'failed';\n\nexport class PendingWithdrawal {\n private _state: WithdrawalState = 'vesant_pending';\n private _event: TransactionCallbackEvent | null = null;\n private _error: unknown = undefined;\n\n // Deferred promise — created lazily only when wait() is called.\n private _waitPromise: Promise<TransactionCallbackEvent> | null = null;\n private _resolveWait?: (event: TransactionCallbackEvent) => void;\n private _rejectWait?: (err: unknown) => void;\n\n readonly tx_id: string;\n readonly initialTransaction: Transaction;\n\n constructor(tx_id: string, transaction: Transaction) {\n this.tx_id = tx_id;\n this.initialTransaction = transaction;\n }\n\n status(): WithdrawalState {\n return this._state;\n }\n\n callbackEvent(): TransactionCallbackEvent | null {\n return this._event;\n }\n\n wait(signal?: AbortSignal): Promise<TransactionCallbackEvent> {\n if (this._state !== 'vesant_pending') {\n if (this._state === 'suspend' || this._state === 'failed') {\n return Promise.reject(this._error);\n }\n if (this._event !== null) {\n return Promise.resolve(this._event);\n }\n }\n\n if (!this._waitPromise) {\n this._waitPromise = new Promise<TransactionCallbackEvent>((resolve, reject) => {\n this._resolveWait = resolve;\n this._rejectWait = reject;\n });\n }\n\n if (signal) {\n if (signal.aborted) {\n this._fail(new VesantError('Request aborted', 'REQUEST_ABORTED'));\n } else {\n signal.addEventListener('abort', () => {\n this._fail(new VesantError('Request aborted', 'REQUEST_ABORTED'));\n }, { once: true });\n }\n }\n\n return this._waitPromise;\n }\n\n /** Called by TransactionWebhookHandler when the tm.transaction.callback arrives for this tx_id. */\n _settle(event: TransactionCallbackEvent): void {\n if (this._state !== 'vesant_pending') return;\n this._event = event;\n if (event.status === 'hold' || event.status === 'suspend') {\n this._state = 'suspend';\n this._error = new TaxHoldError(event);\n this._rejectWait?.(this._error);\n } else if (event.withholding_type === 'none') {\n this._state = 'released';\n this._resolveWait?.(event);\n } else {\n this._state = 'withheld';\n this._resolveWait?.(event);\n }\n }\n\n /** Called to fail the pending withdrawal with a generic error (e.g. abort, network loss). */\n _fail(err: unknown): void {\n if (this._state !== 'vesant_pending') return;\n this._state = 'failed';\n this._error = err;\n this._rejectWait?.(err);\n }\n}\n","import { BaseClient } from '@vesant-sdk/core';\nimport type { RequestOptions } from '@vesant-sdk/core';\nimport type { RequestTaxFormWithProfileInput, RequestTaxFormWithProfileOutput, CustomerTaxProfileDocuments } from './types';\n\nexport class TaxTransactionClient extends BaseClient {\n async getCustomerDocuments(customerID: string): Promise<CustomerTaxProfileDocuments> {\n return this.request<CustomerTaxProfileDocuments>(\n `/api/v1/tm/customer-tax-profiles/${customerID}/documents`\n );\n }\n\n async requestTaxFormWithProfile(\n input: RequestTaxFormWithProfileInput,\n requestOptions?: RequestOptions\n ): Promise<RequestTaxFormWithProfileOutput> {\n return this.requestWithRetry<RequestTaxFormWithProfileOutput>(\n '/api/v1/tax/customer-tax-profiles/request-form-with-profile',\n {\n method: 'POST',\n body: JSON.stringify(input),\n },\n undefined,\n undefined,\n requestOptions\n );\n }\n}\n","/**\n * TransactionClient — SDK for the Transaction Monitoring service\n */\nimport { BaseClient, VesantError } from '@vesant-sdk/core';\nimport type { BaseClientConfig, RequestOptions } from '@vesant-sdk/core';\nimport type { Transaction, TransactionCreateDTO, TransactionCreateResponse, TmTenantSettings } from './types';\nimport { PendingWithdrawal } from './pending-withdrawal';\nimport { TaxTransactionClient } from './tax/client';\n\nexport class TransactionClient extends BaseClient {\n readonly tax: TaxTransactionClient;\n\n constructor(config: BaseClientConfig) {\n super(config);\n this.tax = new TaxTransactionClient(config);\n }\n\n async createTransaction(\n request: TransactionCreateDTO,\n requestOptions?: RequestOptions\n ): Promise<TransactionCreateResponse> {\n try {\n return await this.requestWithRetry<TransactionCreateResponse>('/api/v1/tm/transactions', {\n method: 'POST',\n body: JSON.stringify(request),\n }, undefined, undefined, requestOptions);\n } catch (error) {\n if (error instanceof VesantError && error.statusCode === 409) {\n const transaction = await this.getTransaction(request.tx_id, requestOptions);\n return { transaction, message: 'Transaction already exists' };\n }\n throw error;\n }\n }\n\n async submitWithdrawal(\n request: TransactionCreateDTO,\n requestOptions?: RequestOptions\n ): Promise<PendingWithdrawal> {\n const response = await this.createTransaction(\n { ...request, transaction_type: 'withdrawal' },\n requestOptions\n );\n return new PendingWithdrawal(response.transaction.tx_id, response.transaction);\n }\n\n async getTransaction(transactionId: string, requestOptions?: RequestOptions): Promise<Transaction> {\n return this.requestWithRetry<Transaction>(`/api/v1/tm/transactions/status/${transactionId}`, {\n method: 'GET',\n }, undefined, undefined, requestOptions);\n }\n\n async getSettings(): Promise<TmTenantSettings> {\n return this.requestWithRetry<TmTenantSettings>('/api/v1/tm/settings', {\n method: 'GET',\n });\n }\n}\n","import { verifyWebhookSignature, ValidationError } from '@vesant-sdk/core';\nimport { PendingWithdrawal } from './pending-withdrawal';\nimport type {\n TransactionCallbackEvent,\n TransactionCallbackHandler,\n TransactionHeldEvent,\n TransactionWebhookEvent,\n} from './types';\n\ntype EventHandler<T> = (event: T) => void | Promise<void>;\n\ntype HandlerMap = {\n 'tm.transaction.callback': Array<EventHandler<TransactionCallbackEvent>>;\n 'tm.transaction.held': Array<EventHandler<TransactionHeldEvent>>;\n};\n\nexport const TRANSACTION_SIGNATURE_HEADER = 'x-webhook-signature';\n\nexport interface TransactionWebhookHandlerConfig {\n secret: string;\n /** Reject duplicate event_type+tx_id pairs within replayWindow. Default: true. */\n replayProtection?: boolean;\n /** How long (ms) to remember seen events for replay detection. Default: 300_000 (5 min). */\n replayWindow?: number;\n}\n\nexport class TransactionWebhookHandler {\n private readonly handlers: HandlerMap = {\n 'tm.transaction.callback': [],\n 'tm.transaction.held': [],\n };\n private readonly secret: string;\n private readonly replayProtection: boolean;\n private readonly replayWindow: number;\n private readonly seenEvents = new Map<string, number>();\n private readonly pendingWithdrawals = new Map<string, PendingWithdrawal>();\n private callbackHandler: TransactionCallbackHandler | null = null;\n\n constructor(config: TransactionWebhookHandlerConfig) {\n this.secret = config.secret;\n this.replayProtection = config.replayProtection ?? true;\n this.replayWindow = config.replayWindow ?? 300_000;\n }\n\n on(eventType: 'tm.transaction.callback', handler: EventHandler<TransactionCallbackEvent>): this;\n on(eventType: 'tm.transaction.held', handler: EventHandler<TransactionHeldEvent>): this;\n on(eventType: keyof HandlerMap, handler: EventHandler<TransactionCallbackEvent> | EventHandler<TransactionHeldEvent>): this {\n (this.handlers[eventType] as Array<typeof handler>).push(handler);\n return this;\n }\n\n /** Register a semantic callback handler for the three withholding outcomes. Replaces any previous handler. */\n registerCallbackHandler(handler: TransactionCallbackHandler): this {\n this.callbackHandler = handler;\n return this;\n }\n\n /** Track a PendingWithdrawal so it is auto-settled when its tm.transaction.callback arrives. */\n link(pending: PendingWithdrawal): this {\n this.pendingWithdrawals.set(pending.tx_id, pending);\n return this;\n }\n\n /** Stop tracking a PendingWithdrawal by tx_id. */\n unlink(tx_id: string): this {\n this.pendingWithdrawals.delete(tx_id);\n return this;\n }\n\n /** Verify HMAC-SHA256 signature only. Returns false instead of throwing. */\n async verify(rawBody: string, signature: string): Promise<boolean> {\n return verifyWebhookSignature(rawBody, signature, this.secret);\n }\n\n /** Parse and validate required fields. Does not verify signature — use verifyAndParse in production. */\n parse(body: string): TransactionWebhookEvent {\n const event = JSON.parse(body) as TransactionWebhookEvent;\n if (!event.event_type || !event.tx_id) {\n throw new ValidationError(\n 'Invalid TM webhook event: missing required fields (event_type, tx_id)',\n ['event_type', 'tx_id']\n );\n }\n return event;\n }\n\n /** Verify signature, parse, and check for replays. Throws ValidationError on any failure. */\n async verifyAndParse(body: string, signature: string): Promise<TransactionWebhookEvent> {\n const isValid = await verifyWebhookSignature(body, signature, this.secret);\n if (!isValid) {\n throw new ValidationError('Invalid webhook signature', ['signature']);\n }\n\n const event = this.parse(body);\n\n if (this.replayProtection) {\n const key = `${event.event_type}:${event.tx_id}`;\n if (this.seenEvents.has(key)) {\n throw new ValidationError(\n `Duplicate webhook event: ${event.event_type} for ${event.tx_id} has already been processed`,\n ['tx_id']\n );\n }\n const now = Date.now();\n this.seenEvents.set(key, now);\n if (this.seenEvents.size > 1000) {\n for (const [k, seenAt] of this.seenEvents) {\n if (now - seenAt > this.replayWindow) {\n this.seenEvents.delete(k);\n }\n }\n }\n }\n\n return event;\n }\n\n /** Verify signature, parse, and dispatch to registered handlers. */\n async handle(body: string, signature: string): Promise<void> {\n const event = await this.verifyAndParse(body, signature);\n\n if (event.event_type === 'tm.transaction.callback') {\n const pending = this.pendingWithdrawals.get(event.tx_id);\n if (pending) {\n pending._settle(event);\n this.pendingWithdrawals.delete(event.tx_id);\n }\n\n if (this.callbackHandler) {\n if (event.status === 'hold' || event.status === 'suspend') {\n await this.callbackHandler.onHeld?.(event);\n } else if (event.withholding_type === 'none') {\n await this.callbackHandler.onReleased?.(event);\n } else {\n await this.callbackHandler.onWithheld?.(event);\n }\n }\n }\n\n const handlers = this.handlers[event.event_type] as Array<EventHandler<typeof event>>;\n for (const handler of handlers) {\n await handler(event);\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/pending-withdrawal.ts","../src/tax/client.ts","../src/client.ts","../src/webhook-handler.ts"],"names":["VesantError","BaseClient"],"mappings":"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","file":"index.mjs","sourcesContent":["import { VesantError } from '@vesant-sdk/core';\nimport type { TransactionCallbackEvent, HoldReason } from './types';\n\nexport class DuplicateTransactionError extends VesantError {\n readonly tx_id: string;\n\n constructor(tx_id: string, message = `Duplicate transaction: ${tx_id} already exists`) {\n super(message, 'DUPLICATE_TRANSACTION', 409);\n this.name = 'DuplicateTransactionError';\n this.tx_id = tx_id;\n Object.setPrototypeOf(this, DuplicateTransactionError.prototype);\n }\n}\n\nexport class TaxHoldError extends Error {\n readonly tx_id: string;\n readonly hold_reason: HoldReason;\n readonly event: TransactionCallbackEvent;\n\n constructor(event: TransactionCallbackEvent) {\n super(`Transaction ${event.tx_id} held: ${event.hold_reason}`);\n this.name = 'TaxHoldError';\n this.tx_id = event.tx_id;\n this.hold_reason = event.hold_reason as HoldReason;\n this.event = event;\n Object.setPrototypeOf(this, TaxHoldError.prototype);\n }\n}\n","import { VesantError } from '@vesant-sdk/core';\nimport { TaxHoldError } from './errors';\nimport type { Transaction, TransactionCallbackEvent } from './types';\n\nexport type WithdrawalState = 'vesant_pending' | 'withheld' | 'suspend' | 'released' | 'failed';\n\nexport class PendingWithdrawal {\n private _state: WithdrawalState = 'vesant_pending';\n private _event: TransactionCallbackEvent | null = null;\n private _error: unknown = undefined;\n\n // Deferred promise — created lazily only when wait() is called.\n private _waitPromise: Promise<TransactionCallbackEvent> | null = null;\n private _resolveWait?: (event: TransactionCallbackEvent) => void;\n private _rejectWait?: (err: unknown) => void;\n\n readonly tx_id: string;\n readonly initialTransaction: Transaction;\n\n constructor(tx_id: string, transaction: Transaction) {\n this.tx_id = tx_id;\n this.initialTransaction = transaction;\n }\n\n status(): WithdrawalState {\n return this._state;\n }\n\n callbackEvent(): TransactionCallbackEvent | null {\n return this._event;\n }\n\n wait(signal?: AbortSignal): Promise<TransactionCallbackEvent> {\n if (this._state !== 'vesant_pending') {\n if (this._state === 'suspend' || this._state === 'failed') {\n return Promise.reject(this._error);\n }\n if (this._event !== null) {\n return Promise.resolve(this._event);\n }\n }\n\n if (!this._waitPromise) {\n this._waitPromise = new Promise<TransactionCallbackEvent>((resolve, reject) => {\n this._resolveWait = resolve;\n this._rejectWait = reject;\n });\n }\n\n if (signal) {\n if (signal.aborted) {\n this._fail(new VesantError('Request aborted', 'REQUEST_ABORTED'));\n } else {\n signal.addEventListener('abort', () => {\n this._fail(new VesantError('Request aborted', 'REQUEST_ABORTED'));\n }, { once: true });\n }\n }\n\n return this._waitPromise;\n }\n\n /** Called by TransactionWebhookHandler when the tm.transaction.callback arrives for this tx_id. */\n _settle(event: TransactionCallbackEvent): void {\n if (this._state !== 'vesant_pending') return;\n this._event = event;\n if (event.status === 'hold' || event.status === 'suspend') {\n this._state = 'suspend';\n this._error = new TaxHoldError(event);\n this._rejectWait?.(this._error);\n } else if (event.withholding_type === 'none') {\n this._state = 'released';\n this._resolveWait?.(event);\n } else {\n this._state = 'withheld';\n this._resolveWait?.(event);\n }\n }\n\n /** Called to fail the pending withdrawal with a generic error (e.g. abort, network loss). */\n _fail(err: unknown): void {\n if (this._state !== 'vesant_pending') return;\n this._state = 'failed';\n this._error = err;\n this._rejectWait?.(err);\n }\n}\n","import { BaseClient } from '@vesant-sdk/core';\nimport type { RequestOptions } from '@vesant-sdk/core';\nimport type { RequestTaxFormWithProfileInput, RequestTaxFormWithProfileOutput, CustomerTaxProfileDocuments } from './types';\n\nexport class TaxTransactionClient extends BaseClient {\n async getCustomerDocuments(customerID: string): Promise<CustomerTaxProfileDocuments> {\n return this.request<CustomerTaxProfileDocuments>(\n `/api/v1/tm/customer-tax-profiles/${customerID}/documents`\n );\n }\n\n async requestTaxFormWithProfile(\n input: RequestTaxFormWithProfileInput,\n requestOptions?: RequestOptions\n ): Promise<RequestTaxFormWithProfileOutput> {\n return this.requestWithRetry<RequestTaxFormWithProfileOutput>(\n '/api/v1/tax/customer-tax-profiles/request-form-with-profile',\n {\n method: 'POST',\n body: JSON.stringify(input),\n },\n undefined,\n undefined,\n requestOptions\n );\n }\n}\n","/**\n * TransactionClient — SDK for the Transaction Monitoring service\n */\nimport { BaseClient, VesantError } from '@vesant-sdk/core';\nimport type { BaseClientConfig, RequestOptions } from '@vesant-sdk/core';\nimport type { Transaction, TransactionCreateDTO, TransactionCreateResponse, TmTenantSettings } from './types';\nimport { PendingWithdrawal } from './pending-withdrawal';\nimport { TaxTransactionClient } from './tax/client';\n\nexport class TransactionClient extends BaseClient {\n readonly tax: TaxTransactionClient;\n\n constructor(config: BaseClientConfig) {\n super(config);\n this.tax = new TaxTransactionClient(config);\n }\n\n async createTransaction(\n request: TransactionCreateDTO,\n requestOptions?: RequestOptions\n ): Promise<TransactionCreateResponse> {\n try {\n return await this.requestWithRetry<TransactionCreateResponse>('/api/v1/tm/transactions', {\n method: 'POST',\n body: JSON.stringify(request),\n }, undefined, undefined, requestOptions);\n } catch (error) {\n if (error instanceof VesantError && error.statusCode === 409) {\n const transaction = await this.getTransaction(request.tx_id, requestOptions);\n return { transaction, message: 'Transaction already exists' };\n }\n throw error;\n }\n }\n\n async submitWithdrawal(\n request: TransactionCreateDTO,\n requestOptions?: RequestOptions\n ): Promise<PendingWithdrawal> {\n const response = await this.createTransaction(\n { ...request, transaction_type: 'withdrawal' },\n requestOptions\n );\n return new PendingWithdrawal(response.transaction.tx_id, response.transaction);\n }\n\n async getTransaction(transactionId: string, requestOptions?: RequestOptions): Promise<Transaction> {\n return this.requestWithRetry<Transaction>(`/api/v1/tm/transactions/status/${transactionId}`, {\n method: 'GET',\n }, undefined, undefined, requestOptions);\n }\n\n async getSettings(): Promise<TmTenantSettings> {\n return this.requestWithRetry<TmTenantSettings>('/api/v1/tm/settings', {\n method: 'GET',\n });\n }\n}\n","import { verifyWebhookSignature, ValidationError, InMemoryDedupStore, type WebhookDedupStore } from '@vesant-sdk/core';\nimport { PendingWithdrawal } from './pending-withdrawal';\nimport type {\n TransactionCallbackEvent,\n TransactionCallbackHandler,\n TransactionHeldEvent,\n TransactionWebhookEvent,\n} from './types';\n\ntype EventHandler<T> = (event: T) => void | Promise<void>;\n\ntype HandlerMap = {\n 'tm.transaction.callback': Array<EventHandler<TransactionCallbackEvent>>;\n 'tm.transaction.held': Array<EventHandler<TransactionHeldEvent>>;\n};\n\nexport const TRANSACTION_SIGNATURE_HEADER = 'x-webhook-signature';\n\nexport interface TransactionWebhookHandlerConfig {\n secret: string;\n /** Reject duplicate event_type+tx_id pairs within replayWindow. Default: true. */\n replayProtection?: boolean;\n /** How long (ms) to remember seen events for replay detection. Default: 300_000 (5 min). */\n replayWindow?: number;\n /** Pluggable dedup store for replay protection (default: in-memory, process-local). */\n dedupStore?: WebhookDedupStore;\n}\n\nexport class TransactionWebhookHandler {\n private readonly handlers: HandlerMap = {\n 'tm.transaction.callback': [],\n 'tm.transaction.held': [],\n };\n private readonly secret: string;\n private readonly replayProtection: boolean;\n private readonly replayWindow: number;\n private readonly dedupStore: WebhookDedupStore;\n private readonly pendingWithdrawals = new Map<string, PendingWithdrawal>();\n private callbackHandler: TransactionCallbackHandler | null = null;\n\n constructor(config: TransactionWebhookHandlerConfig) {\n this.secret = config.secret;\n this.replayProtection = config.replayProtection ?? true;\n this.replayWindow = config.replayWindow ?? 300_000;\n this.dedupStore = config.dedupStore ?? new InMemoryDedupStore();\n }\n\n on(eventType: 'tm.transaction.callback', handler: EventHandler<TransactionCallbackEvent>): this;\n on(eventType: 'tm.transaction.held', handler: EventHandler<TransactionHeldEvent>): this;\n on(eventType: keyof HandlerMap, handler: EventHandler<TransactionCallbackEvent> | EventHandler<TransactionHeldEvent>): this {\n (this.handlers[eventType] as Array<typeof handler>).push(handler);\n return this;\n }\n\n /** Register a semantic callback handler for the three withholding outcomes. Replaces any previous handler. */\n registerCallbackHandler(handler: TransactionCallbackHandler): this {\n this.callbackHandler = handler;\n return this;\n }\n\n /** Track a PendingWithdrawal so it is auto-settled when its tm.transaction.callback arrives. */\n link(pending: PendingWithdrawal): this {\n this.pendingWithdrawals.set(pending.tx_id, pending);\n return this;\n }\n\n /** Stop tracking a PendingWithdrawal by tx_id. */\n unlink(tx_id: string): this {\n this.pendingWithdrawals.delete(tx_id);\n return this;\n }\n\n /** Verify HMAC-SHA256 signature only. Returns false instead of throwing. */\n async verify(rawBody: string, signature: string): Promise<boolean> {\n return verifyWebhookSignature(rawBody, signature, this.secret);\n }\n\n /** Parse and validate required fields. Does not verify signature — use verifyAndParse in production. */\n parse(body: string): TransactionWebhookEvent {\n const event = JSON.parse(body) as TransactionWebhookEvent;\n if (!event.event_type || !event.tx_id) {\n throw new ValidationError(\n 'Invalid TM webhook event: missing required fields (event_type, tx_id)',\n ['event_type', 'tx_id']\n );\n }\n return event;\n }\n\n /** Verify signature, parse, and check for replays. Throws ValidationError on any failure. */\n async verifyAndParse(body: string, signature: string): Promise<TransactionWebhookEvent> {\n const isValid = await verifyWebhookSignature(body, signature, this.secret);\n if (!isValid) {\n throw new ValidationError('Invalid webhook signature', ['signature']);\n }\n\n const event = this.parse(body);\n\n if (this.replayProtection) {\n const key = `${event.event_type}:${event.tx_id}`;\n if (await this.dedupStore.seen(key)) {\n throw new ValidationError(\n `Duplicate webhook event: ${event.event_type} for ${event.tx_id} has already been processed`,\n ['tx_id']\n );\n }\n await this.dedupStore.mark(key, this.replayWindow);\n }\n\n return event;\n }\n\n /** Verify signature, parse, and dispatch to registered handlers. */\n async handle(body: string, signature: string): Promise<void> {\n const event = await this.verifyAndParse(body, signature);\n\n if (event.event_type === 'tm.transaction.callback') {\n const pending = this.pendingWithdrawals.get(event.tx_id);\n if (pending) {\n pending._settle(event);\n this.pendingWithdrawals.delete(event.tx_id);\n }\n\n if (this.callbackHandler) {\n if (event.status === 'hold' || event.status === 'suspend') {\n await this.callbackHandler.onHeld?.(event);\n } else if (event.withholding_type === 'none') {\n await this.callbackHandler.onReleased?.(event);\n } else {\n await this.callbackHandler.onWithheld?.(event);\n }\n }\n }\n\n const handlers = this.handlers[event.event_type] as Array<EventHandler<typeof event>>;\n for (const handler of handlers) {\n await handler(event);\n }\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vesant-sdk/transaction",
|
|
3
|
-
"version": "0.1.1
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Transaction monitoring client for the Vesant Compliance Platform",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -12,9 +12,7 @@
|
|
|
12
12
|
"require": "./dist/index.js"
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
|
-
"files": [
|
|
16
|
-
"dist"
|
|
17
|
-
],
|
|
15
|
+
"files": ["dist"],
|
|
18
16
|
"scripts": {
|
|
19
17
|
"build": "tsup",
|
|
20
18
|
"dev": "tsup --watch",
|
|
@@ -23,12 +21,7 @@
|
|
|
23
21
|
"type-check": "tsc --noEmit",
|
|
24
22
|
"lint": "eslint src --ext .ts"
|
|
25
23
|
},
|
|
26
|
-
"keywords": [
|
|
27
|
-
"vesant",
|
|
28
|
-
"compliance",
|
|
29
|
-
"transaction",
|
|
30
|
-
"transaction-monitoring"
|
|
31
|
-
],
|
|
24
|
+
"keywords": ["vesant", "compliance", "transaction", "transaction-monitoring"],
|
|
32
25
|
"author": "Vesant Compliance",
|
|
33
26
|
"license": "MIT",
|
|
34
27
|
"dependencies": {
|
|
@@ -45,5 +38,8 @@
|
|
|
45
38
|
"type": "git",
|
|
46
39
|
"url": "https://github.com/botcalm-compliance/vesant-sdk.git",
|
|
47
40
|
"directory": "typescript/packages/transaction"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=18"
|
|
48
44
|
}
|
|
49
45
|
}
|