@vesant-sdk/transaction 0.1.1-dev.f9faca4 → 0.1.1-dev.fe84882

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 CHANGED
@@ -1,4 +1,4 @@
1
- import { BaseClient, BaseClientConfig, RequestOptions, 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
@@ -177,11 +177,85 @@ declare class PendingWithdrawal {
177
177
  _fail(err: unknown): void;
178
178
  }
179
179
 
180
+ type CustomerType = 'US_PERSON' | 'NON_US_PERSON' | 'NON_US_ENTITY';
181
+ type CTPFormStatus = 'requested' | 'signed' | 'manual' | 'bounced' | 'uploaded' | 'expired' | 're_requested';
182
+ type CTPTINStatus = 'not_required' | 'pending' | 'submitted' | 'verified' | 'rejected' | 'error' | 'expired';
183
+ interface CustomerTaxProfileRecord {
184
+ id: string;
185
+ tenant_id: string;
186
+ customer_id: string;
187
+ customer_type: CustomerType;
188
+ form_type: string;
189
+ form_status: CTPFormStatus;
190
+ tin_status: CTPTINStatus;
191
+ tin_value?: string;
192
+ recipient_name?: string;
193
+ business_name?: string;
194
+ account_number?: string;
195
+ address?: string;
196
+ address_city?: string;
197
+ address_state?: string;
198
+ address_zip?: string;
199
+ address_country?: string;
200
+ customer_name?: string;
201
+ avalara_form_id?: string;
202
+ avalara_company_id?: string;
203
+ signed_at?: string;
204
+ verified_at?: string;
205
+ expiry_date?: string;
206
+ backup_withholding: boolean;
207
+ b_notice_received: boolean;
208
+ e_delivery_consent: boolean;
209
+ withholding_enabled_by?: string;
210
+ withholding_enabled_at?: string;
211
+ tin_due_date?: string;
212
+ last_tin_check?: string;
213
+ request_count: number;
214
+ reminders_sent: number;
215
+ last_reminder_at?: string;
216
+ created_at: string;
217
+ updated_at: string;
218
+ }
219
+ type TaxFormTrigger = 'trigger_account_creation' | 'trigger_first_withdrawal' | 'trigger_threshold' | 'trigger_manual' | 'trigger_tin_invalid' | 'trigger_w8ben_expiry' | 'trigger_tin_expired';
220
+ interface CustomerTaxProfileDocuments {
221
+ w_form: string;
222
+ '1099_form': string;
223
+ }
224
+ interface RequestTaxFormWithProfileInput {
225
+ customer_id: string;
226
+ email: string;
227
+ trigger: TaxFormTrigger;
228
+ first_name?: string;
229
+ last_name?: string;
230
+ date_of_birth?: string;
231
+ phone_number?: string;
232
+ country?: string;
233
+ state?: string;
234
+ city?: string;
235
+ zip_code?: string;
236
+ address?: string;
237
+ ip_address?: string;
238
+ form_type?: string;
239
+ }
240
+ interface RequestTaxFormWithProfileOutput {
241
+ profile?: CustomerTaxProfileRecord;
242
+ request_id?: string;
243
+ reference_id?: string;
244
+ form_id?: string;
245
+ public_url?: string;
246
+ }
247
+
248
+ declare class TaxTransactionClient extends BaseClient {
249
+ getCustomerDocuments(customerID: string): Promise<CustomerTaxProfileDocuments>;
250
+ requestTaxFormWithProfile(input: RequestTaxFormWithProfileInput, requestOptions?: RequestOptions): Promise<RequestTaxFormWithProfileOutput>;
251
+ }
252
+
180
253
  /**
181
254
  * TransactionClient — SDK for the Transaction Monitoring service
182
255
  */
183
256
 
184
257
  declare class TransactionClient extends BaseClient {
258
+ readonly tax: TaxTransactionClient;
185
259
  constructor(config: BaseClientConfig);
186
260
  createTransaction(request: TransactionCreateDTO, requestOptions?: RequestOptions): Promise<TransactionCreateResponse>;
187
261
  submitWithdrawal(request: TransactionCreateDTO, requestOptions?: RequestOptions): Promise<PendingWithdrawal>;
@@ -208,13 +282,15 @@ interface TransactionWebhookHandlerConfig {
208
282
  replayProtection?: boolean;
209
283
  /** How long (ms) to remember seen events for replay detection. Default: 300_000 (5 min). */
210
284
  replayWindow?: number;
285
+ /** Pluggable dedup store for replay protection (default: in-memory, process-local). */
286
+ dedupStore?: WebhookDedupStore;
211
287
  }
212
288
  declare class TransactionWebhookHandler {
213
289
  private readonly handlers;
214
290
  private readonly secret;
215
291
  private readonly replayProtection;
216
292
  private readonly replayWindow;
217
- private readonly seenEvents;
293
+ private readonly dedupStore;
218
294
  private readonly pendingWithdrawals;
219
295
  private callbackHandler;
220
296
  constructor(config: TransactionWebhookHandlerConfig);
@@ -236,4 +312,4 @@ declare class TransactionWebhookHandler {
236
312
  handle(body: string, signature: string): Promise<void>;
237
313
  }
238
314
 
239
- export { DuplicateTransactionError, type HoldReason, PendingWithdrawal, TRANSACTION_SIGNATURE_HEADER, TaxHoldError, type TmTenantSettings, type Transaction, type TransactionCallbackEvent, type TransactionCallbackHandler, TransactionClient, type TransactionClientConfig, type TransactionCreateDTO, type TransactionCreateResponse, type TransactionHeldEvent, type TransactionMode, type TransactionStatus, type TransactionSubType, type TransactionType, type TransactionWebhookEvent, TransactionWebhookHandler, type TransactionWebhookHandlerConfig, type WithdrawalState, type WithholdingReason, type WithholdingType };
315
+ export { type CTPFormStatus, type CTPTINStatus, type CustomerTaxProfileDocuments, type CustomerTaxProfileRecord, type CustomerType, DuplicateTransactionError, type HoldReason, PendingWithdrawal, type RequestTaxFormWithProfileInput, type RequestTaxFormWithProfileOutput, TRANSACTION_SIGNATURE_HEADER, type TaxFormTrigger, TaxHoldError, TaxTransactionClient, type TmTenantSettings, type Transaction, type TransactionCallbackEvent, type TransactionCallbackHandler, TransactionClient, type TransactionClientConfig, type TransactionCreateDTO, type TransactionCreateResponse, type TransactionHeldEvent, type TransactionMode, type TransactionStatus, type TransactionSubType, type TransactionType, type TransactionWebhookEvent, TransactionWebhookHandler, type TransactionWebhookHandlerConfig, type WithdrawalState, type WithholdingReason, type WithholdingType };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { BaseClient, BaseClientConfig, RequestOptions, 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
@@ -177,11 +177,85 @@ declare class PendingWithdrawal {
177
177
  _fail(err: unknown): void;
178
178
  }
179
179
 
180
+ type CustomerType = 'US_PERSON' | 'NON_US_PERSON' | 'NON_US_ENTITY';
181
+ type CTPFormStatus = 'requested' | 'signed' | 'manual' | 'bounced' | 'uploaded' | 'expired' | 're_requested';
182
+ type CTPTINStatus = 'not_required' | 'pending' | 'submitted' | 'verified' | 'rejected' | 'error' | 'expired';
183
+ interface CustomerTaxProfileRecord {
184
+ id: string;
185
+ tenant_id: string;
186
+ customer_id: string;
187
+ customer_type: CustomerType;
188
+ form_type: string;
189
+ form_status: CTPFormStatus;
190
+ tin_status: CTPTINStatus;
191
+ tin_value?: string;
192
+ recipient_name?: string;
193
+ business_name?: string;
194
+ account_number?: string;
195
+ address?: string;
196
+ address_city?: string;
197
+ address_state?: string;
198
+ address_zip?: string;
199
+ address_country?: string;
200
+ customer_name?: string;
201
+ avalara_form_id?: string;
202
+ avalara_company_id?: string;
203
+ signed_at?: string;
204
+ verified_at?: string;
205
+ expiry_date?: string;
206
+ backup_withholding: boolean;
207
+ b_notice_received: boolean;
208
+ e_delivery_consent: boolean;
209
+ withholding_enabled_by?: string;
210
+ withholding_enabled_at?: string;
211
+ tin_due_date?: string;
212
+ last_tin_check?: string;
213
+ request_count: number;
214
+ reminders_sent: number;
215
+ last_reminder_at?: string;
216
+ created_at: string;
217
+ updated_at: string;
218
+ }
219
+ type TaxFormTrigger = 'trigger_account_creation' | 'trigger_first_withdrawal' | 'trigger_threshold' | 'trigger_manual' | 'trigger_tin_invalid' | 'trigger_w8ben_expiry' | 'trigger_tin_expired';
220
+ interface CustomerTaxProfileDocuments {
221
+ w_form: string;
222
+ '1099_form': string;
223
+ }
224
+ interface RequestTaxFormWithProfileInput {
225
+ customer_id: string;
226
+ email: string;
227
+ trigger: TaxFormTrigger;
228
+ first_name?: string;
229
+ last_name?: string;
230
+ date_of_birth?: string;
231
+ phone_number?: string;
232
+ country?: string;
233
+ state?: string;
234
+ city?: string;
235
+ zip_code?: string;
236
+ address?: string;
237
+ ip_address?: string;
238
+ form_type?: string;
239
+ }
240
+ interface RequestTaxFormWithProfileOutput {
241
+ profile?: CustomerTaxProfileRecord;
242
+ request_id?: string;
243
+ reference_id?: string;
244
+ form_id?: string;
245
+ public_url?: string;
246
+ }
247
+
248
+ declare class TaxTransactionClient extends BaseClient {
249
+ getCustomerDocuments(customerID: string): Promise<CustomerTaxProfileDocuments>;
250
+ requestTaxFormWithProfile(input: RequestTaxFormWithProfileInput, requestOptions?: RequestOptions): Promise<RequestTaxFormWithProfileOutput>;
251
+ }
252
+
180
253
  /**
181
254
  * TransactionClient — SDK for the Transaction Monitoring service
182
255
  */
183
256
 
184
257
  declare class TransactionClient extends BaseClient {
258
+ readonly tax: TaxTransactionClient;
185
259
  constructor(config: BaseClientConfig);
186
260
  createTransaction(request: TransactionCreateDTO, requestOptions?: RequestOptions): Promise<TransactionCreateResponse>;
187
261
  submitWithdrawal(request: TransactionCreateDTO, requestOptions?: RequestOptions): Promise<PendingWithdrawal>;
@@ -208,13 +282,15 @@ interface TransactionWebhookHandlerConfig {
208
282
  replayProtection?: boolean;
209
283
  /** How long (ms) to remember seen events for replay detection. Default: 300_000 (5 min). */
210
284
  replayWindow?: number;
285
+ /** Pluggable dedup store for replay protection (default: in-memory, process-local). */
286
+ dedupStore?: WebhookDedupStore;
211
287
  }
212
288
  declare class TransactionWebhookHandler {
213
289
  private readonly handlers;
214
290
  private readonly secret;
215
291
  private readonly replayProtection;
216
292
  private readonly replayWindow;
217
- private readonly seenEvents;
293
+ private readonly dedupStore;
218
294
  private readonly pendingWithdrawals;
219
295
  private callbackHandler;
220
296
  constructor(config: TransactionWebhookHandlerConfig);
@@ -236,4 +312,4 @@ declare class TransactionWebhookHandler {
236
312
  handle(body: string, signature: string): Promise<void>;
237
313
  }
238
314
 
239
- export { DuplicateTransactionError, type HoldReason, PendingWithdrawal, TRANSACTION_SIGNATURE_HEADER, TaxHoldError, type TmTenantSettings, type Transaction, type TransactionCallbackEvent, type TransactionCallbackHandler, TransactionClient, type TransactionClientConfig, type TransactionCreateDTO, type TransactionCreateResponse, type TransactionHeldEvent, type TransactionMode, type TransactionStatus, type TransactionSubType, type TransactionType, type TransactionWebhookEvent, TransactionWebhookHandler, type TransactionWebhookHandlerConfig, type WithdrawalState, type WithholdingReason, type WithholdingType };
315
+ export { type CTPFormStatus, type CTPTINStatus, type CustomerTaxProfileDocuments, type CustomerTaxProfileRecord, type CustomerType, DuplicateTransactionError, type HoldReason, PendingWithdrawal, type RequestTaxFormWithProfileInput, type RequestTaxFormWithProfileOutput, TRANSACTION_SIGNATURE_HEADER, type TaxFormTrigger, TaxHoldError, TaxTransactionClient, type TmTenantSettings, type Transaction, type TransactionCallbackEvent, type TransactionCallbackHandler, TransactionClient, type TransactionClientConfig, type TransactionCreateDTO, type TransactionCreateResponse, type TransactionHeldEvent, type TransactionMode, type TransactionStatus, type TransactionSubType, type TransactionType, type TransactionWebhookEvent, TransactionWebhookHandler, type TransactionWebhookHandlerConfig, type WithdrawalState, type WithholdingReason, type WithholdingType };
package/dist/index.js CHANGED
@@ -89,11 +89,31 @@ var PendingWithdrawal = class {
89
89
  this._rejectWait?.(err);
90
90
  }
91
91
  };
92
+ var TaxTransactionClient = class extends core.BaseClient {
93
+ async getCustomerDocuments(customerID) {
94
+ return this.request(
95
+ `/api/v1/tm/customer-tax-profiles/${customerID}/documents`
96
+ );
97
+ }
98
+ async requestTaxFormWithProfile(input, requestOptions) {
99
+ return this.requestWithRetry(
100
+ "/api/v1/tax/customer-tax-profiles/request-form-with-profile",
101
+ {
102
+ method: "POST",
103
+ body: JSON.stringify(input)
104
+ },
105
+ void 0,
106
+ void 0,
107
+ requestOptions
108
+ );
109
+ }
110
+ };
92
111
 
93
112
  // src/client.ts
94
113
  var TransactionClient = class extends core.BaseClient {
95
114
  constructor(config) {
96
115
  super(config);
116
+ this.tax = new TaxTransactionClient(config);
97
117
  }
98
118
  async createTransaction(request, requestOptions) {
99
119
  try {
@@ -134,12 +154,12 @@ var TransactionWebhookHandler = class {
134
154
  "tm.transaction.callback": [],
135
155
  "tm.transaction.held": []
136
156
  };
137
- this.seenEvents = /* @__PURE__ */ new Map();
138
157
  this.pendingWithdrawals = /* @__PURE__ */ new Map();
139
158
  this.callbackHandler = null;
140
159
  this.secret = config.secret;
141
160
  this.replayProtection = config.replayProtection ?? true;
142
161
  this.replayWindow = config.replayWindow ?? 3e5;
162
+ this.dedupStore = config.dedupStore ?? new core.InMemoryDedupStore();
143
163
  }
144
164
  on(eventType, handler) {
145
165
  this.handlers[eventType].push(handler);
@@ -184,21 +204,13 @@ var TransactionWebhookHandler = class {
184
204
  const event = this.parse(body);
185
205
  if (this.replayProtection) {
186
206
  const key = `${event.event_type}:${event.tx_id}`;
187
- if (this.seenEvents.has(key)) {
207
+ if (await this.dedupStore.seen(key)) {
188
208
  throw new core.ValidationError(
189
209
  `Duplicate webhook event: ${event.event_type} for ${event.tx_id} has already been processed`,
190
210
  ["tx_id"]
191
211
  );
192
212
  }
193
- const now = Date.now();
194
- this.seenEvents.set(key, now);
195
- if (this.seenEvents.size > 1e3) {
196
- for (const [k, seenAt] of this.seenEvents) {
197
- if (now - seenAt > this.replayWindow) {
198
- this.seenEvents.delete(k);
199
- }
200
- }
201
- }
213
+ await this.dedupStore.mark(key, this.replayWindow);
202
214
  }
203
215
  return event;
204
216
  }
@@ -232,6 +244,7 @@ exports.DuplicateTransactionError = DuplicateTransactionError;
232
244
  exports.PendingWithdrawal = PendingWithdrawal;
233
245
  exports.TRANSACTION_SIGNATURE_HEADER = TRANSACTION_SIGNATURE_HEADER;
234
246
  exports.TaxHoldError = TaxHoldError;
247
+ exports.TaxTransactionClient = TaxTransactionClient;
235
248
  exports.TransactionClient = TransactionClient;
236
249
  exports.TransactionWebhookHandler = TransactionWebhookHandler;
237
250
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/pending-withdrawal.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","/**\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';\n\nexport class TransactionClient extends BaseClient {\n constructor(config: BaseClientConfig) {\n super(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 {
@@ -87,11 +87,31 @@ var PendingWithdrawal = class {
87
87
  this._rejectWait?.(err);
88
88
  }
89
89
  };
90
+ var TaxTransactionClient = class extends BaseClient {
91
+ async getCustomerDocuments(customerID) {
92
+ return this.request(
93
+ `/api/v1/tm/customer-tax-profiles/${customerID}/documents`
94
+ );
95
+ }
96
+ async requestTaxFormWithProfile(input, requestOptions) {
97
+ return this.requestWithRetry(
98
+ "/api/v1/tax/customer-tax-profiles/request-form-with-profile",
99
+ {
100
+ method: "POST",
101
+ body: JSON.stringify(input)
102
+ },
103
+ void 0,
104
+ void 0,
105
+ requestOptions
106
+ );
107
+ }
108
+ };
90
109
 
91
110
  // src/client.ts
92
111
  var TransactionClient = class extends BaseClient {
93
112
  constructor(config) {
94
113
  super(config);
114
+ this.tax = new TaxTransactionClient(config);
95
115
  }
96
116
  async createTransaction(request, requestOptions) {
97
117
  try {
@@ -132,12 +152,12 @@ var TransactionWebhookHandler = class {
132
152
  "tm.transaction.callback": [],
133
153
  "tm.transaction.held": []
134
154
  };
135
- this.seenEvents = /* @__PURE__ */ new Map();
136
155
  this.pendingWithdrawals = /* @__PURE__ */ new Map();
137
156
  this.callbackHandler = null;
138
157
  this.secret = config.secret;
139
158
  this.replayProtection = config.replayProtection ?? true;
140
159
  this.replayWindow = config.replayWindow ?? 3e5;
160
+ this.dedupStore = config.dedupStore ?? new InMemoryDedupStore();
141
161
  }
142
162
  on(eventType, handler) {
143
163
  this.handlers[eventType].push(handler);
@@ -182,21 +202,13 @@ var TransactionWebhookHandler = class {
182
202
  const event = this.parse(body);
183
203
  if (this.replayProtection) {
184
204
  const key = `${event.event_type}:${event.tx_id}`;
185
- if (this.seenEvents.has(key)) {
205
+ if (await this.dedupStore.seen(key)) {
186
206
  throw new ValidationError(
187
207
  `Duplicate webhook event: ${event.event_type} for ${event.tx_id} has already been processed`,
188
208
  ["tx_id"]
189
209
  );
190
210
  }
191
- const now = Date.now();
192
- this.seenEvents.set(key, now);
193
- if (this.seenEvents.size > 1e3) {
194
- for (const [k, seenAt] of this.seenEvents) {
195
- if (now - seenAt > this.replayWindow) {
196
- this.seenEvents.delete(k);
197
- }
198
- }
199
- }
211
+ await this.dedupStore.mark(key, this.replayWindow);
200
212
  }
201
213
  return event;
202
214
  }
@@ -226,6 +238,6 @@ var TransactionWebhookHandler = class {
226
238
  }
227
239
  };
228
240
 
229
- export { DuplicateTransactionError, PendingWithdrawal, TRANSACTION_SIGNATURE_HEADER, TaxHoldError, TransactionClient, TransactionWebhookHandler };
241
+ export { DuplicateTransactionError, PendingWithdrawal, TRANSACTION_SIGNATURE_HEADER, TaxHoldError, TaxTransactionClient, TransactionClient, TransactionWebhookHandler };
230
242
  //# sourceMappingURL=index.mjs.map
231
243
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/pending-withdrawal.ts","../src/client.ts","../src/webhook-handler.ts"],"names":["VesantError"],"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","/**\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';\n\nexport class TransactionClient extends BaseClient {\n constructor(config: BaseClientConfig) {\n super(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-dev.f9faca4",
3
+ "version": "0.1.1-dev.fe84882",
4
4
  "description": "Transaction monitoring client for the Vesant Compliance Platform",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -45,5 +45,8 @@
45
45
  "type": "git",
46
46
  "url": "https://github.com/botcalm-compliance/vesant-sdk.git",
47
47
  "directory": "typescript/packages/transaction"
48
+ },
49
+ "engines": {
50
+ "node": ">=18"
48
51
  }
49
52
  }