@vesant-sdk/transaction 0.1.1-next.f221d37 → 0.1.1-next.f504af3

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,45 +1,38 @@
1
- import { Timestamp, BaseClient, BaseClientConfig, RequestOptions } from '@vesant-sdk/core';
1
+ import { BaseClient, RequestOptions, BaseClientConfig, VesantError } from '@vesant-sdk/core';
2
2
 
3
3
  /**
4
4
  * TypeScript types for the Transaction Monitoring (TM) Service
5
5
  */
6
-
7
- type TransactionType = "deposit" | "withdrawal" | "transfer" | "payment" | "refund" | "fee" | "adjustment" | "bet" | "payout";
8
- type TransactionMode = "fiat" | "crypto" | "bank_transfer" | "card" | "wallet" | "ach" | "wire_transfer" | "swift";
9
- type TransactionStatus = "pending" | "processing" | "completed" | "failed" | "cancelled" | "reversed" | "suspend" | "hold";
6
+ type TransactionType = "withdrawal" | "deposit" | "transfer" | "bet" | "payment" | "adjustment" | "payout";
7
+ type TransactionMode = "wire_transfer" | "crypto" | "swift" | "ach" | "bank_transfer" | "card";
8
+ type TransactionSubType = "sweepstakes" | "cash";
9
+ type TransactionStatus = "pending" | "processing" | "completed" | "failed" | "cancelled" | "reversed" | "blocked" | "flagged" | "hold" | "suspend";
10
10
  interface TransactionCreateDTO {
11
11
  tx_id: string;
12
12
  reference: string;
13
- tenant_id?: string;
14
13
  customer_id: string;
15
14
  transaction_type: TransactionType;
16
15
  transaction_mode: TransactionMode;
16
+ sub_type?: TransactionSubType;
17
17
  amount: string;
18
+ amount_usd: number;
18
19
  currency: string;
19
- /**
20
- * Required for non-USD currencies. The backend only auto-converts when currency is "USD".
21
- * If omitted for crypto or foreign-currency withdrawals, withheld_usd and released_usd
22
- * will be 0 and the gateway will receive $0.
23
- */
24
- amount_usd?: number;
25
- status?: TransactionStatus;
26
20
  source_account?: string;
27
21
  destination_account?: string;
28
22
  country?: string;
29
23
  ip_address?: string;
30
- metadata?: Record<string, any>;
31
- beneficiary_comment?: string;
32
- transaction_date: Timestamp;
24
+ benificiary_comment?: string;
25
+ metadata?: Record<string, unknown>;
26
+ transaction_date: string;
33
27
  }
34
28
  type WithholdingType = "none" | "backup_us" | "backup_non_us" | "treaty";
35
29
  /** Reason backup withholding or treaty rate was applied. */
36
30
  type WithholdingReason = "tin_not_verified" | "customer_tax_form_not_verified" | "b_notice_backup_withholding";
37
31
  /**
38
- * Reason a withdrawal was blocked (status = "suspend" or "hold").
32
+ * Reason a withdrawal was suspended (status = "suspend").
39
33
  * Reflects TIN/W-9 deficiencies that triggered the tenant's configured transaction action.
40
34
  */
41
35
  type HoldReason = "no_tax_profile_found" | "tin_rejected" | "tin_expired" | "tin_not_provided" | "tin_not_verified" | "form_not_certified";
42
- type JSONB = Record<string, any>;
43
36
  type Transaction = {
44
37
  id: string;
45
38
  tx_id: string;
@@ -48,26 +41,21 @@ type Transaction = {
48
41
  customer_id: string;
49
42
  transaction_type: TransactionType;
50
43
  transaction_mode: TransactionMode;
44
+ sub_type?: TransactionSubType;
51
45
  amount: string;
52
46
  withheld_amount: string;
53
47
  released_amount: string;
54
48
  withholding_rate: number;
55
- withholding_type?: WithholdingType;
56
- /** Set when withholding applies; null/absent when withholding_type is "none". */
57
- withholding_reason?: WithholdingReason;
58
- /** Set when status is "suspend" or "hold" due to TIN/W-9 deficiency. */
59
- hold_reason?: HoldReason;
60
- /** Gross transaction amount in USD. */
61
- amount_usd?: number;
62
- /** Withheld amount in USD. */
63
- withheld_usd?: number;
64
- /** Released (net) amount in USD. */
65
- released_usd?: number;
49
+ withholding_type: string;
50
+ withholding_reason?: string;
51
+ hold_reason?: string;
52
+ amount_usd: number;
53
+ withheld_usd: number;
54
+ released_usd: number;
66
55
  /**
67
56
  * True when this is the customer's first withdrawal.
68
- * Correct on the POST /transactions response and on tm.transaction.callback (both read
69
- * from the in-memory struct before any DB re-fetch). Not persisted — getTransaction()
70
- * always returns false regardless of actual history.
57
+ * Correct on the POST /transactions response and on tm.transaction.callback.
58
+ * Not persisted getTransaction() always returns false regardless of actual history.
71
59
  */
72
60
  is_first_withdrawal?: boolean;
73
61
  tax_year: number;
@@ -78,8 +66,8 @@ type Transaction = {
78
66
  country?: string;
79
67
  ip_address?: string;
80
68
  risk_score: number;
81
- metadata?: JSONB;
82
- beneficiary_comment?: string;
69
+ metadata?: Record<string, unknown>;
70
+ benificiary_comment?: string;
83
71
  transaction_date: string;
84
72
  created_at: string;
85
73
  updated_at: string;
@@ -98,43 +86,43 @@ interface TransactionClientConfig {
98
86
  /**
99
87
  * Response from POST /api/v1/tm/transactions.
100
88
  *
101
- * Status → outcome mapping:
102
- * "completed" — Vesant accepted; trigger the payment gateway (clean TIN,
103
- * backup withholding applied, or treaty rate applied)
104
- * "hold" — blocked, hold_reason set, no funds moved
105
- * "suspend" — blocked, hold_reason set, no funds moved
89
+ * Status → outcome mapping (withdrawal transactions only):
90
+ * "pending" — Vesant accepted; trigger the payment gateway (full release,
91
+ * backup withholding applied, or treaty rate applied)
92
+ * "suspend" — blocked, hold_reason set, no funds moved
106
93
  */
107
94
  type TransactionCreateResponse = {
108
95
  transaction: Transaction;
109
96
  message: string;
110
97
  };
111
98
  /**
112
- * Fired after every transaction is created.
113
- * Delivers the final transaction state including withholding outcome so clients
114
- * can mirror it without a separate polling call.
99
+ * Fired after every transaction is created and after a suspended transaction is released.
100
+ * Delivers the final compliance-evaluated state including withholding outcome.
115
101
  */
116
102
  interface TransactionCallbackEvent {
117
103
  event_type: "tm.transaction.callback";
104
+ id: string;
105
+ tenant_id: string;
106
+ entity_id: string;
107
+ user_id: string;
118
108
  tx_id: string;
119
109
  reference: string;
120
110
  customer_id: string;
121
- transaction_type: TransactionType;
122
- transaction_mode: TransactionMode;
123
- status: TransactionStatus;
111
+ transaction_type: string;
112
+ transaction_mode: string;
113
+ status: string;
124
114
  amount: string;
125
115
  currency: string;
126
116
  withheld_amount: string;
127
117
  released_amount: string;
128
118
  withholding_rate: number;
129
- withholding_type: WithholdingType;
130
- /** Null when withholding_type is "none". */
131
- withholding_reason: WithholdingReason | null;
119
+ withholding_type: string;
120
+ withholding_reason?: string | null;
121
+ hold_reason?: string | null;
132
122
  amount_usd: number;
133
123
  withheld_usd: number;
134
124
  released_usd: number;
135
125
  is_first_withdrawal: boolean;
136
- /** Set when status is "suspend" or "hold"; null otherwise. */
137
- hold_reason: HoldReason | null;
138
126
  transaction_date: string;
139
127
  }
140
128
  /**
@@ -154,18 +142,140 @@ interface TransactionHeldEvent {
154
142
  }
155
143
  /** Discriminated union of all TM service webhook event payloads. */
156
144
  type TransactionWebhookEvent = TransactionCallbackEvent | TransactionHeldEvent;
145
+ interface TmTenantSettings {
146
+ tenant_id: string;
147
+ transaction_monitoring_enabled: boolean;
148
+ tax_monitoring_enabled: boolean;
149
+ }
150
+ /**
151
+ * Semantic handler interface for the three withholding outcomes of a tm.transaction.callback.
152
+ * Register with TransactionWebhookHandler.registerCallbackHandler().
153
+ */
154
+ interface TransactionCallbackHandler {
155
+ onWithheld?(event: TransactionCallbackEvent): void | Promise<void>;
156
+ onHeld?(event: TransactionCallbackEvent): void | Promise<void>;
157
+ onReleased?(event: TransactionCallbackEvent): void | Promise<void>;
158
+ }
159
+
160
+ type WithdrawalState = 'vesant_pending' | 'withheld' | 'suspend' | 'released' | 'failed';
161
+ declare class PendingWithdrawal {
162
+ private _state;
163
+ private _event;
164
+ private _error;
165
+ private _waitPromise;
166
+ private _resolveWait?;
167
+ private _rejectWait?;
168
+ readonly tx_id: string;
169
+ readonly initialTransaction: Transaction;
170
+ constructor(tx_id: string, transaction: Transaction);
171
+ status(): WithdrawalState;
172
+ callbackEvent(): TransactionCallbackEvent | null;
173
+ wait(signal?: AbortSignal): Promise<TransactionCallbackEvent>;
174
+ /** Called by TransactionWebhookHandler when the tm.transaction.callback arrives for this tx_id. */
175
+ _settle(event: TransactionCallbackEvent): void;
176
+ /** Called to fail the pending withdrawal with a generic error (e.g. abort, network loss). */
177
+ _fail(err: unknown): void;
178
+ }
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
+ }
157
252
 
158
253
  /**
159
254
  * TransactionClient — SDK for the Transaction Monitoring service
160
255
  */
161
256
 
162
257
  declare class TransactionClient extends BaseClient {
258
+ readonly tax: TaxTransactionClient;
163
259
  constructor(config: BaseClientConfig);
164
260
  createTransaction(request: TransactionCreateDTO, requestOptions?: RequestOptions): Promise<TransactionCreateResponse>;
261
+ submitWithdrawal(request: TransactionCreateDTO, requestOptions?: RequestOptions): Promise<PendingWithdrawal>;
165
262
  getTransaction(transactionId: string, requestOptions?: RequestOptions): Promise<Transaction>;
263
+ getSettings(): Promise<TmTenantSettings>;
264
+ }
265
+
266
+ declare class DuplicateTransactionError extends VesantError {
267
+ readonly tx_id: string;
268
+ constructor(tx_id: string, message?: string);
269
+ }
270
+ declare class TaxHoldError extends Error {
271
+ readonly tx_id: string;
272
+ readonly hold_reason: HoldReason;
273
+ readonly event: TransactionCallbackEvent;
274
+ constructor(event: TransactionCallbackEvent);
166
275
  }
167
276
 
168
277
  type EventHandler<T> = (event: T) => void | Promise<void>;
278
+ declare const TRANSACTION_SIGNATURE_HEADER = "x-webhook-signature";
169
279
  interface TransactionWebhookHandlerConfig {
170
280
  secret: string;
171
281
  /** Reject duplicate event_type+tx_id pairs within replayWindow. Default: true. */
@@ -178,10 +288,18 @@ declare class TransactionWebhookHandler {
178
288
  private readonly secret;
179
289
  private readonly replayProtection;
180
290
  private readonly replayWindow;
181
- private seenEvents;
291
+ private readonly seenEvents;
292
+ private readonly pendingWithdrawals;
293
+ private callbackHandler;
182
294
  constructor(config: TransactionWebhookHandlerConfig);
183
295
  on(eventType: 'tm.transaction.callback', handler: EventHandler<TransactionCallbackEvent>): this;
184
296
  on(eventType: 'tm.transaction.held', handler: EventHandler<TransactionHeldEvent>): this;
297
+ /** Register a semantic callback handler for the three withholding outcomes. Replaces any previous handler. */
298
+ registerCallbackHandler(handler: TransactionCallbackHandler): this;
299
+ /** Track a PendingWithdrawal so it is auto-settled when its tm.transaction.callback arrives. */
300
+ link(pending: PendingWithdrawal): this;
301
+ /** Stop tracking a PendingWithdrawal by tx_id. */
302
+ unlink(tx_id: string): this;
185
303
  /** Verify HMAC-SHA256 signature only. Returns false instead of throwing. */
186
304
  verify(rawBody: string, signature: string): Promise<boolean>;
187
305
  /** Parse and validate required fields. Does not verify signature — use verifyAndParse in production. */
@@ -192,4 +310,4 @@ declare class TransactionWebhookHandler {
192
310
  handle(body: string, signature: string): Promise<void>;
193
311
  }
194
312
 
195
- export { type HoldReason, type JSONB, type Transaction, type TransactionCallbackEvent, TransactionClient, type TransactionClientConfig, type TransactionCreateDTO, type TransactionCreateResponse, type TransactionHeldEvent, type TransactionMode, type TransactionStatus, type TransactionType, type TransactionWebhookEvent, TransactionWebhookHandler, type TransactionWebhookHandlerConfig, type WithholdingReason, type WithholdingType };
313
+ 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,45 +1,38 @@
1
- import { Timestamp, BaseClient, BaseClientConfig, RequestOptions } from '@vesant-sdk/core';
1
+ import { BaseClient, RequestOptions, BaseClientConfig, VesantError } from '@vesant-sdk/core';
2
2
 
3
3
  /**
4
4
  * TypeScript types for the Transaction Monitoring (TM) Service
5
5
  */
6
-
7
- type TransactionType = "deposit" | "withdrawal" | "transfer" | "payment" | "refund" | "fee" | "adjustment" | "bet" | "payout";
8
- type TransactionMode = "fiat" | "crypto" | "bank_transfer" | "card" | "wallet" | "ach" | "wire_transfer" | "swift";
9
- type TransactionStatus = "pending" | "processing" | "completed" | "failed" | "cancelled" | "reversed" | "suspend" | "hold";
6
+ type TransactionType = "withdrawal" | "deposit" | "transfer" | "bet" | "payment" | "adjustment" | "payout";
7
+ type TransactionMode = "wire_transfer" | "crypto" | "swift" | "ach" | "bank_transfer" | "card";
8
+ type TransactionSubType = "sweepstakes" | "cash";
9
+ type TransactionStatus = "pending" | "processing" | "completed" | "failed" | "cancelled" | "reversed" | "blocked" | "flagged" | "hold" | "suspend";
10
10
  interface TransactionCreateDTO {
11
11
  tx_id: string;
12
12
  reference: string;
13
- tenant_id?: string;
14
13
  customer_id: string;
15
14
  transaction_type: TransactionType;
16
15
  transaction_mode: TransactionMode;
16
+ sub_type?: TransactionSubType;
17
17
  amount: string;
18
+ amount_usd: number;
18
19
  currency: string;
19
- /**
20
- * Required for non-USD currencies. The backend only auto-converts when currency is "USD".
21
- * If omitted for crypto or foreign-currency withdrawals, withheld_usd and released_usd
22
- * will be 0 and the gateway will receive $0.
23
- */
24
- amount_usd?: number;
25
- status?: TransactionStatus;
26
20
  source_account?: string;
27
21
  destination_account?: string;
28
22
  country?: string;
29
23
  ip_address?: string;
30
- metadata?: Record<string, any>;
31
- beneficiary_comment?: string;
32
- transaction_date: Timestamp;
24
+ benificiary_comment?: string;
25
+ metadata?: Record<string, unknown>;
26
+ transaction_date: string;
33
27
  }
34
28
  type WithholdingType = "none" | "backup_us" | "backup_non_us" | "treaty";
35
29
  /** Reason backup withholding or treaty rate was applied. */
36
30
  type WithholdingReason = "tin_not_verified" | "customer_tax_form_not_verified" | "b_notice_backup_withholding";
37
31
  /**
38
- * Reason a withdrawal was blocked (status = "suspend" or "hold").
32
+ * Reason a withdrawal was suspended (status = "suspend").
39
33
  * Reflects TIN/W-9 deficiencies that triggered the tenant's configured transaction action.
40
34
  */
41
35
  type HoldReason = "no_tax_profile_found" | "tin_rejected" | "tin_expired" | "tin_not_provided" | "tin_not_verified" | "form_not_certified";
42
- type JSONB = Record<string, any>;
43
36
  type Transaction = {
44
37
  id: string;
45
38
  tx_id: string;
@@ -48,26 +41,21 @@ type Transaction = {
48
41
  customer_id: string;
49
42
  transaction_type: TransactionType;
50
43
  transaction_mode: TransactionMode;
44
+ sub_type?: TransactionSubType;
51
45
  amount: string;
52
46
  withheld_amount: string;
53
47
  released_amount: string;
54
48
  withholding_rate: number;
55
- withholding_type?: WithholdingType;
56
- /** Set when withholding applies; null/absent when withholding_type is "none". */
57
- withholding_reason?: WithholdingReason;
58
- /** Set when status is "suspend" or "hold" due to TIN/W-9 deficiency. */
59
- hold_reason?: HoldReason;
60
- /** Gross transaction amount in USD. */
61
- amount_usd?: number;
62
- /** Withheld amount in USD. */
63
- withheld_usd?: number;
64
- /** Released (net) amount in USD. */
65
- released_usd?: number;
49
+ withholding_type: string;
50
+ withholding_reason?: string;
51
+ hold_reason?: string;
52
+ amount_usd: number;
53
+ withheld_usd: number;
54
+ released_usd: number;
66
55
  /**
67
56
  * True when this is the customer's first withdrawal.
68
- * Correct on the POST /transactions response and on tm.transaction.callback (both read
69
- * from the in-memory struct before any DB re-fetch). Not persisted — getTransaction()
70
- * always returns false regardless of actual history.
57
+ * Correct on the POST /transactions response and on tm.transaction.callback.
58
+ * Not persisted getTransaction() always returns false regardless of actual history.
71
59
  */
72
60
  is_first_withdrawal?: boolean;
73
61
  tax_year: number;
@@ -78,8 +66,8 @@ type Transaction = {
78
66
  country?: string;
79
67
  ip_address?: string;
80
68
  risk_score: number;
81
- metadata?: JSONB;
82
- beneficiary_comment?: string;
69
+ metadata?: Record<string, unknown>;
70
+ benificiary_comment?: string;
83
71
  transaction_date: string;
84
72
  created_at: string;
85
73
  updated_at: string;
@@ -98,43 +86,43 @@ interface TransactionClientConfig {
98
86
  /**
99
87
  * Response from POST /api/v1/tm/transactions.
100
88
  *
101
- * Status → outcome mapping:
102
- * "completed" — Vesant accepted; trigger the payment gateway (clean TIN,
103
- * backup withholding applied, or treaty rate applied)
104
- * "hold" — blocked, hold_reason set, no funds moved
105
- * "suspend" — blocked, hold_reason set, no funds moved
89
+ * Status → outcome mapping (withdrawal transactions only):
90
+ * "pending" — Vesant accepted; trigger the payment gateway (full release,
91
+ * backup withholding applied, or treaty rate applied)
92
+ * "suspend" — blocked, hold_reason set, no funds moved
106
93
  */
107
94
  type TransactionCreateResponse = {
108
95
  transaction: Transaction;
109
96
  message: string;
110
97
  };
111
98
  /**
112
- * Fired after every transaction is created.
113
- * Delivers the final transaction state including withholding outcome so clients
114
- * can mirror it without a separate polling call.
99
+ * Fired after every transaction is created and after a suspended transaction is released.
100
+ * Delivers the final compliance-evaluated state including withholding outcome.
115
101
  */
116
102
  interface TransactionCallbackEvent {
117
103
  event_type: "tm.transaction.callback";
104
+ id: string;
105
+ tenant_id: string;
106
+ entity_id: string;
107
+ user_id: string;
118
108
  tx_id: string;
119
109
  reference: string;
120
110
  customer_id: string;
121
- transaction_type: TransactionType;
122
- transaction_mode: TransactionMode;
123
- status: TransactionStatus;
111
+ transaction_type: string;
112
+ transaction_mode: string;
113
+ status: string;
124
114
  amount: string;
125
115
  currency: string;
126
116
  withheld_amount: string;
127
117
  released_amount: string;
128
118
  withholding_rate: number;
129
- withholding_type: WithholdingType;
130
- /** Null when withholding_type is "none". */
131
- withholding_reason: WithholdingReason | null;
119
+ withholding_type: string;
120
+ withholding_reason?: string | null;
121
+ hold_reason?: string | null;
132
122
  amount_usd: number;
133
123
  withheld_usd: number;
134
124
  released_usd: number;
135
125
  is_first_withdrawal: boolean;
136
- /** Set when status is "suspend" or "hold"; null otherwise. */
137
- hold_reason: HoldReason | null;
138
126
  transaction_date: string;
139
127
  }
140
128
  /**
@@ -154,18 +142,140 @@ interface TransactionHeldEvent {
154
142
  }
155
143
  /** Discriminated union of all TM service webhook event payloads. */
156
144
  type TransactionWebhookEvent = TransactionCallbackEvent | TransactionHeldEvent;
145
+ interface TmTenantSettings {
146
+ tenant_id: string;
147
+ transaction_monitoring_enabled: boolean;
148
+ tax_monitoring_enabled: boolean;
149
+ }
150
+ /**
151
+ * Semantic handler interface for the three withholding outcomes of a tm.transaction.callback.
152
+ * Register with TransactionWebhookHandler.registerCallbackHandler().
153
+ */
154
+ interface TransactionCallbackHandler {
155
+ onWithheld?(event: TransactionCallbackEvent): void | Promise<void>;
156
+ onHeld?(event: TransactionCallbackEvent): void | Promise<void>;
157
+ onReleased?(event: TransactionCallbackEvent): void | Promise<void>;
158
+ }
159
+
160
+ type WithdrawalState = 'vesant_pending' | 'withheld' | 'suspend' | 'released' | 'failed';
161
+ declare class PendingWithdrawal {
162
+ private _state;
163
+ private _event;
164
+ private _error;
165
+ private _waitPromise;
166
+ private _resolveWait?;
167
+ private _rejectWait?;
168
+ readonly tx_id: string;
169
+ readonly initialTransaction: Transaction;
170
+ constructor(tx_id: string, transaction: Transaction);
171
+ status(): WithdrawalState;
172
+ callbackEvent(): TransactionCallbackEvent | null;
173
+ wait(signal?: AbortSignal): Promise<TransactionCallbackEvent>;
174
+ /** Called by TransactionWebhookHandler when the tm.transaction.callback arrives for this tx_id. */
175
+ _settle(event: TransactionCallbackEvent): void;
176
+ /** Called to fail the pending withdrawal with a generic error (e.g. abort, network loss). */
177
+ _fail(err: unknown): void;
178
+ }
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
+ }
157
252
 
158
253
  /**
159
254
  * TransactionClient — SDK for the Transaction Monitoring service
160
255
  */
161
256
 
162
257
  declare class TransactionClient extends BaseClient {
258
+ readonly tax: TaxTransactionClient;
163
259
  constructor(config: BaseClientConfig);
164
260
  createTransaction(request: TransactionCreateDTO, requestOptions?: RequestOptions): Promise<TransactionCreateResponse>;
261
+ submitWithdrawal(request: TransactionCreateDTO, requestOptions?: RequestOptions): Promise<PendingWithdrawal>;
165
262
  getTransaction(transactionId: string, requestOptions?: RequestOptions): Promise<Transaction>;
263
+ getSettings(): Promise<TmTenantSettings>;
264
+ }
265
+
266
+ declare class DuplicateTransactionError extends VesantError {
267
+ readonly tx_id: string;
268
+ constructor(tx_id: string, message?: string);
269
+ }
270
+ declare class TaxHoldError extends Error {
271
+ readonly tx_id: string;
272
+ readonly hold_reason: HoldReason;
273
+ readonly event: TransactionCallbackEvent;
274
+ constructor(event: TransactionCallbackEvent);
166
275
  }
167
276
 
168
277
  type EventHandler<T> = (event: T) => void | Promise<void>;
278
+ declare const TRANSACTION_SIGNATURE_HEADER = "x-webhook-signature";
169
279
  interface TransactionWebhookHandlerConfig {
170
280
  secret: string;
171
281
  /** Reject duplicate event_type+tx_id pairs within replayWindow. Default: true. */
@@ -178,10 +288,18 @@ declare class TransactionWebhookHandler {
178
288
  private readonly secret;
179
289
  private readonly replayProtection;
180
290
  private readonly replayWindow;
181
- private seenEvents;
291
+ private readonly seenEvents;
292
+ private readonly pendingWithdrawals;
293
+ private callbackHandler;
182
294
  constructor(config: TransactionWebhookHandlerConfig);
183
295
  on(eventType: 'tm.transaction.callback', handler: EventHandler<TransactionCallbackEvent>): this;
184
296
  on(eventType: 'tm.transaction.held', handler: EventHandler<TransactionHeldEvent>): this;
297
+ /** Register a semantic callback handler for the three withholding outcomes. Replaces any previous handler. */
298
+ registerCallbackHandler(handler: TransactionCallbackHandler): this;
299
+ /** Track a PendingWithdrawal so it is auto-settled when its tm.transaction.callback arrives. */
300
+ link(pending: PendingWithdrawal): this;
301
+ /** Stop tracking a PendingWithdrawal by tx_id. */
302
+ unlink(tx_id: string): this;
185
303
  /** Verify HMAC-SHA256 signature only. Returns false instead of throwing. */
186
304
  verify(rawBody: string, signature: string): Promise<boolean>;
187
305
  /** Parse and validate required fields. Does not verify signature — use verifyAndParse in production. */
@@ -192,4 +310,4 @@ declare class TransactionWebhookHandler {
192
310
  handle(body: string, signature: string): Promise<void>;
193
311
  }
194
312
 
195
- export { type HoldReason, type JSONB, type Transaction, type TransactionCallbackEvent, TransactionClient, type TransactionClientConfig, type TransactionCreateDTO, type TransactionCreateResponse, type TransactionHeldEvent, type TransactionMode, type TransactionStatus, type TransactionType, type TransactionWebhookEvent, TransactionWebhookHandler, type TransactionWebhookHandlerConfig, type WithholdingReason, type WithholdingType };
313
+ 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
@@ -2,25 +2,152 @@
2
2
 
3
3
  var core = require('@vesant-sdk/core');
4
4
 
5
+ // src/client.ts
6
+ var DuplicateTransactionError = class _DuplicateTransactionError extends core.VesantError {
7
+ constructor(tx_id, message = `Duplicate transaction: ${tx_id} already exists`) {
8
+ super(message, "DUPLICATE_TRANSACTION", 409);
9
+ this.name = "DuplicateTransactionError";
10
+ this.tx_id = tx_id;
11
+ Object.setPrototypeOf(this, _DuplicateTransactionError.prototype);
12
+ }
13
+ };
14
+ var TaxHoldError = class _TaxHoldError extends Error {
15
+ constructor(event) {
16
+ super(`Transaction ${event.tx_id} held: ${event.hold_reason}`);
17
+ this.name = "TaxHoldError";
18
+ this.tx_id = event.tx_id;
19
+ this.hold_reason = event.hold_reason;
20
+ this.event = event;
21
+ Object.setPrototypeOf(this, _TaxHoldError.prototype);
22
+ }
23
+ };
24
+
25
+ // src/pending-withdrawal.ts
26
+ var PendingWithdrawal = class {
27
+ constructor(tx_id, transaction) {
28
+ this._state = "vesant_pending";
29
+ this._event = null;
30
+ this._error = void 0;
31
+ // Deferred promise — created lazily only when wait() is called.
32
+ this._waitPromise = null;
33
+ this.tx_id = tx_id;
34
+ this.initialTransaction = transaction;
35
+ }
36
+ status() {
37
+ return this._state;
38
+ }
39
+ callbackEvent() {
40
+ return this._event;
41
+ }
42
+ wait(signal) {
43
+ if (this._state !== "vesant_pending") {
44
+ if (this._state === "suspend" || this._state === "failed") {
45
+ return Promise.reject(this._error);
46
+ }
47
+ if (this._event !== null) {
48
+ return Promise.resolve(this._event);
49
+ }
50
+ }
51
+ if (!this._waitPromise) {
52
+ this._waitPromise = new Promise((resolve, reject) => {
53
+ this._resolveWait = resolve;
54
+ this._rejectWait = reject;
55
+ });
56
+ }
57
+ if (signal) {
58
+ if (signal.aborted) {
59
+ this._fail(new core.VesantError("Request aborted", "REQUEST_ABORTED"));
60
+ } else {
61
+ signal.addEventListener("abort", () => {
62
+ this._fail(new core.VesantError("Request aborted", "REQUEST_ABORTED"));
63
+ }, { once: true });
64
+ }
65
+ }
66
+ return this._waitPromise;
67
+ }
68
+ /** Called by TransactionWebhookHandler when the tm.transaction.callback arrives for this tx_id. */
69
+ _settle(event) {
70
+ if (this._state !== "vesant_pending") return;
71
+ this._event = event;
72
+ if (event.status === "hold" || event.status === "suspend") {
73
+ this._state = "suspend";
74
+ this._error = new TaxHoldError(event);
75
+ this._rejectWait?.(this._error);
76
+ } else if (event.withholding_type === "none") {
77
+ this._state = "released";
78
+ this._resolveWait?.(event);
79
+ } else {
80
+ this._state = "withheld";
81
+ this._resolveWait?.(event);
82
+ }
83
+ }
84
+ /** Called to fail the pending withdrawal with a generic error (e.g. abort, network loss). */
85
+ _fail(err) {
86
+ if (this._state !== "vesant_pending") return;
87
+ this._state = "failed";
88
+ this._error = err;
89
+ this._rejectWait?.(err);
90
+ }
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
+ };
111
+
5
112
  // src/client.ts
6
113
  var TransactionClient = class extends core.BaseClient {
7
114
  constructor(config) {
8
115
  super(config);
116
+ this.tax = new TaxTransactionClient(config);
9
117
  }
10
118
  async createTransaction(request, requestOptions) {
11
- const data = await this.requestWithRetry("/api/v1/tm/transactions", {
12
- method: "POST",
13
- body: JSON.stringify(request)
14
- }, void 0, void 0, requestOptions);
15
- return data;
119
+ try {
120
+ return await this.requestWithRetry("/api/v1/tm/transactions", {
121
+ method: "POST",
122
+ body: JSON.stringify(request)
123
+ }, void 0, void 0, requestOptions);
124
+ } catch (error) {
125
+ if (error instanceof core.VesantError && error.statusCode === 409) {
126
+ const transaction = await this.getTransaction(request.tx_id, requestOptions);
127
+ return { transaction, message: "Transaction already exists" };
128
+ }
129
+ throw error;
130
+ }
131
+ }
132
+ async submitWithdrawal(request, requestOptions) {
133
+ const response = await this.createTransaction(
134
+ { ...request, transaction_type: "withdrawal" },
135
+ requestOptions
136
+ );
137
+ return new PendingWithdrawal(response.transaction.tx_id, response.transaction);
16
138
  }
17
139
  async getTransaction(transactionId, requestOptions) {
18
- const data = await this.requestWithRetry(`/api/v1/tm/transactions/status/${transactionId}`, {
140
+ return this.requestWithRetry(`/api/v1/tm/transactions/status/${transactionId}`, {
19
141
  method: "GET"
20
142
  }, void 0, void 0, requestOptions);
21
- return data;
143
+ }
144
+ async getSettings() {
145
+ return this.requestWithRetry("/api/v1/tm/settings", {
146
+ method: "GET"
147
+ });
22
148
  }
23
149
  };
150
+ var TRANSACTION_SIGNATURE_HEADER = "x-webhook-signature";
24
151
  var TransactionWebhookHandler = class {
25
152
  constructor(config) {
26
153
  this.handlers = {
@@ -28,6 +155,8 @@ var TransactionWebhookHandler = class {
28
155
  "tm.transaction.held": []
29
156
  };
30
157
  this.seenEvents = /* @__PURE__ */ new Map();
158
+ this.pendingWithdrawals = /* @__PURE__ */ new Map();
159
+ this.callbackHandler = null;
31
160
  this.secret = config.secret;
32
161
  this.replayProtection = config.replayProtection ?? true;
33
162
  this.replayWindow = config.replayWindow ?? 3e5;
@@ -36,6 +165,21 @@ var TransactionWebhookHandler = class {
36
165
  this.handlers[eventType].push(handler);
37
166
  return this;
38
167
  }
168
+ /** Register a semantic callback handler for the three withholding outcomes. Replaces any previous handler. */
169
+ registerCallbackHandler(handler) {
170
+ this.callbackHandler = handler;
171
+ return this;
172
+ }
173
+ /** Track a PendingWithdrawal so it is auto-settled when its tm.transaction.callback arrives. */
174
+ link(pending) {
175
+ this.pendingWithdrawals.set(pending.tx_id, pending);
176
+ return this;
177
+ }
178
+ /** Stop tracking a PendingWithdrawal by tx_id. */
179
+ unlink(tx_id) {
180
+ this.pendingWithdrawals.delete(tx_id);
181
+ return this;
182
+ }
39
183
  /** Verify HMAC-SHA256 signature only. Returns false instead of throwing. */
40
184
  async verify(rawBody, signature) {
41
185
  return core.verifyWebhookSignature(rawBody, signature, this.secret);
@@ -81,6 +225,22 @@ var TransactionWebhookHandler = class {
81
225
  /** Verify signature, parse, and dispatch to registered handlers. */
82
226
  async handle(body, signature) {
83
227
  const event = await this.verifyAndParse(body, signature);
228
+ if (event.event_type === "tm.transaction.callback") {
229
+ const pending = this.pendingWithdrawals.get(event.tx_id);
230
+ if (pending) {
231
+ pending._settle(event);
232
+ this.pendingWithdrawals.delete(event.tx_id);
233
+ }
234
+ if (this.callbackHandler) {
235
+ if (event.status === "hold" || event.status === "suspend") {
236
+ await this.callbackHandler.onHeld?.(event);
237
+ } else if (event.withholding_type === "none") {
238
+ await this.callbackHandler.onReleased?.(event);
239
+ } else {
240
+ await this.callbackHandler.onWithheld?.(event);
241
+ }
242
+ }
243
+ }
84
244
  const handlers = this.handlers[event.event_type];
85
245
  for (const handler of handlers) {
86
246
  await handler(event);
@@ -88,6 +248,11 @@ var TransactionWebhookHandler = class {
88
248
  }
89
249
  };
90
250
 
251
+ exports.DuplicateTransactionError = DuplicateTransactionError;
252
+ exports.PendingWithdrawal = PendingWithdrawal;
253
+ exports.TRANSACTION_SIGNATURE_HEADER = TRANSACTION_SIGNATURE_HEADER;
254
+ exports.TaxHoldError = TaxHoldError;
255
+ exports.TaxTransactionClient = TaxTransactionClient;
91
256
  exports.TransactionClient = TransactionClient;
92
257
  exports.TransactionWebhookHandler = TransactionWebhookHandler;
93
258
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts","../src/webhook-handler.ts"],"names":["BaseClient","verifyWebhookSignature","ValidationError"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * TransactionClient — SDK for the Transaction Monitoring service\n */\n\nimport { BaseClient } from '@vesant-sdk/core';\nimport type { BaseClientConfig, RequestOptions } from '@vesant-sdk/core';\nimport type { Transaction, TransactionCreateDTO, TransactionCreateResponse } from './types';\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 const data = await this.requestWithRetry<TransactionCreateResponse>('/api/v1/tm/transactions', {\n method: 'POST',\n body: JSON.stringify(request),\n }, undefined, undefined, requestOptions);\n return data;\n }\n\n async getTransaction(transactionId: string, requestOptions?: RequestOptions): Promise<Transaction> {\n const data = await this.requestWithRetry<Transaction>(`/api/v1/tm/transactions/status/${transactionId}`, {\n method: 'GET',\n }, undefined, undefined, requestOptions);\n return data;\n }\n}\n","import { verifyWebhookSignature, ValidationError } from '@vesant-sdk/core';\nimport type {\n TransactionCallbackEvent,\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 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 seenEvents = new Map<string, number>();\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 /** 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 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","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"]}
package/dist/index.mjs CHANGED
@@ -1,24 +1,151 @@
1
- import { BaseClient, verifyWebhookSignature, ValidationError } from '@vesant-sdk/core';
1
+ import { VesantError, BaseClient, verifyWebhookSignature, ValidationError } from '@vesant-sdk/core';
2
+
3
+ // src/client.ts
4
+ var DuplicateTransactionError = class _DuplicateTransactionError extends VesantError {
5
+ constructor(tx_id, message = `Duplicate transaction: ${tx_id} already exists`) {
6
+ super(message, "DUPLICATE_TRANSACTION", 409);
7
+ this.name = "DuplicateTransactionError";
8
+ this.tx_id = tx_id;
9
+ Object.setPrototypeOf(this, _DuplicateTransactionError.prototype);
10
+ }
11
+ };
12
+ var TaxHoldError = class _TaxHoldError extends Error {
13
+ constructor(event) {
14
+ super(`Transaction ${event.tx_id} held: ${event.hold_reason}`);
15
+ this.name = "TaxHoldError";
16
+ this.tx_id = event.tx_id;
17
+ this.hold_reason = event.hold_reason;
18
+ this.event = event;
19
+ Object.setPrototypeOf(this, _TaxHoldError.prototype);
20
+ }
21
+ };
22
+
23
+ // src/pending-withdrawal.ts
24
+ var PendingWithdrawal = class {
25
+ constructor(tx_id, transaction) {
26
+ this._state = "vesant_pending";
27
+ this._event = null;
28
+ this._error = void 0;
29
+ // Deferred promise — created lazily only when wait() is called.
30
+ this._waitPromise = null;
31
+ this.tx_id = tx_id;
32
+ this.initialTransaction = transaction;
33
+ }
34
+ status() {
35
+ return this._state;
36
+ }
37
+ callbackEvent() {
38
+ return this._event;
39
+ }
40
+ wait(signal) {
41
+ if (this._state !== "vesant_pending") {
42
+ if (this._state === "suspend" || this._state === "failed") {
43
+ return Promise.reject(this._error);
44
+ }
45
+ if (this._event !== null) {
46
+ return Promise.resolve(this._event);
47
+ }
48
+ }
49
+ if (!this._waitPromise) {
50
+ this._waitPromise = new Promise((resolve, reject) => {
51
+ this._resolveWait = resolve;
52
+ this._rejectWait = reject;
53
+ });
54
+ }
55
+ if (signal) {
56
+ if (signal.aborted) {
57
+ this._fail(new VesantError("Request aborted", "REQUEST_ABORTED"));
58
+ } else {
59
+ signal.addEventListener("abort", () => {
60
+ this._fail(new VesantError("Request aborted", "REQUEST_ABORTED"));
61
+ }, { once: true });
62
+ }
63
+ }
64
+ return this._waitPromise;
65
+ }
66
+ /** Called by TransactionWebhookHandler when the tm.transaction.callback arrives for this tx_id. */
67
+ _settle(event) {
68
+ if (this._state !== "vesant_pending") return;
69
+ this._event = event;
70
+ if (event.status === "hold" || event.status === "suspend") {
71
+ this._state = "suspend";
72
+ this._error = new TaxHoldError(event);
73
+ this._rejectWait?.(this._error);
74
+ } else if (event.withholding_type === "none") {
75
+ this._state = "released";
76
+ this._resolveWait?.(event);
77
+ } else {
78
+ this._state = "withheld";
79
+ this._resolveWait?.(event);
80
+ }
81
+ }
82
+ /** Called to fail the pending withdrawal with a generic error (e.g. abort, network loss). */
83
+ _fail(err) {
84
+ if (this._state !== "vesant_pending") return;
85
+ this._state = "failed";
86
+ this._error = err;
87
+ this._rejectWait?.(err);
88
+ }
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
+ };
2
109
 
3
110
  // src/client.ts
4
111
  var TransactionClient = class extends BaseClient {
5
112
  constructor(config) {
6
113
  super(config);
114
+ this.tax = new TaxTransactionClient(config);
7
115
  }
8
116
  async createTransaction(request, requestOptions) {
9
- const data = await this.requestWithRetry("/api/v1/tm/transactions", {
10
- method: "POST",
11
- body: JSON.stringify(request)
12
- }, void 0, void 0, requestOptions);
13
- return data;
117
+ try {
118
+ return await this.requestWithRetry("/api/v1/tm/transactions", {
119
+ method: "POST",
120
+ body: JSON.stringify(request)
121
+ }, void 0, void 0, requestOptions);
122
+ } catch (error) {
123
+ if (error instanceof VesantError && error.statusCode === 409) {
124
+ const transaction = await this.getTransaction(request.tx_id, requestOptions);
125
+ return { transaction, message: "Transaction already exists" };
126
+ }
127
+ throw error;
128
+ }
129
+ }
130
+ async submitWithdrawal(request, requestOptions) {
131
+ const response = await this.createTransaction(
132
+ { ...request, transaction_type: "withdrawal" },
133
+ requestOptions
134
+ );
135
+ return new PendingWithdrawal(response.transaction.tx_id, response.transaction);
14
136
  }
15
137
  async getTransaction(transactionId, requestOptions) {
16
- const data = await this.requestWithRetry(`/api/v1/tm/transactions/status/${transactionId}`, {
138
+ return this.requestWithRetry(`/api/v1/tm/transactions/status/${transactionId}`, {
17
139
  method: "GET"
18
140
  }, void 0, void 0, requestOptions);
19
- return data;
141
+ }
142
+ async getSettings() {
143
+ return this.requestWithRetry("/api/v1/tm/settings", {
144
+ method: "GET"
145
+ });
20
146
  }
21
147
  };
148
+ var TRANSACTION_SIGNATURE_HEADER = "x-webhook-signature";
22
149
  var TransactionWebhookHandler = class {
23
150
  constructor(config) {
24
151
  this.handlers = {
@@ -26,6 +153,8 @@ var TransactionWebhookHandler = class {
26
153
  "tm.transaction.held": []
27
154
  };
28
155
  this.seenEvents = /* @__PURE__ */ new Map();
156
+ this.pendingWithdrawals = /* @__PURE__ */ new Map();
157
+ this.callbackHandler = null;
29
158
  this.secret = config.secret;
30
159
  this.replayProtection = config.replayProtection ?? true;
31
160
  this.replayWindow = config.replayWindow ?? 3e5;
@@ -34,6 +163,21 @@ var TransactionWebhookHandler = class {
34
163
  this.handlers[eventType].push(handler);
35
164
  return this;
36
165
  }
166
+ /** Register a semantic callback handler for the three withholding outcomes. Replaces any previous handler. */
167
+ registerCallbackHandler(handler) {
168
+ this.callbackHandler = handler;
169
+ return this;
170
+ }
171
+ /** Track a PendingWithdrawal so it is auto-settled when its tm.transaction.callback arrives. */
172
+ link(pending) {
173
+ this.pendingWithdrawals.set(pending.tx_id, pending);
174
+ return this;
175
+ }
176
+ /** Stop tracking a PendingWithdrawal by tx_id. */
177
+ unlink(tx_id) {
178
+ this.pendingWithdrawals.delete(tx_id);
179
+ return this;
180
+ }
37
181
  /** Verify HMAC-SHA256 signature only. Returns false instead of throwing. */
38
182
  async verify(rawBody, signature) {
39
183
  return verifyWebhookSignature(rawBody, signature, this.secret);
@@ -79,6 +223,22 @@ var TransactionWebhookHandler = class {
79
223
  /** Verify signature, parse, and dispatch to registered handlers. */
80
224
  async handle(body, signature) {
81
225
  const event = await this.verifyAndParse(body, signature);
226
+ if (event.event_type === "tm.transaction.callback") {
227
+ const pending = this.pendingWithdrawals.get(event.tx_id);
228
+ if (pending) {
229
+ pending._settle(event);
230
+ this.pendingWithdrawals.delete(event.tx_id);
231
+ }
232
+ if (this.callbackHandler) {
233
+ if (event.status === "hold" || event.status === "suspend") {
234
+ await this.callbackHandler.onHeld?.(event);
235
+ } else if (event.withholding_type === "none") {
236
+ await this.callbackHandler.onReleased?.(event);
237
+ } else {
238
+ await this.callbackHandler.onWithheld?.(event);
239
+ }
240
+ }
241
+ }
82
242
  const handlers = this.handlers[event.event_type];
83
243
  for (const handler of handlers) {
84
244
  await handler(event);
@@ -86,6 +246,6 @@ var TransactionWebhookHandler = class {
86
246
  }
87
247
  };
88
248
 
89
- export { TransactionClient, TransactionWebhookHandler };
249
+ export { DuplicateTransactionError, PendingWithdrawal, TRANSACTION_SIGNATURE_HEADER, TaxHoldError, TaxTransactionClient, TransactionClient, TransactionWebhookHandler };
90
250
  //# sourceMappingURL=index.mjs.map
91
251
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts","../src/webhook-handler.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * TransactionClient — SDK for the Transaction Monitoring service\n */\n\nimport { BaseClient } from '@vesant-sdk/core';\nimport type { BaseClientConfig, RequestOptions } from '@vesant-sdk/core';\nimport type { Transaction, TransactionCreateDTO, TransactionCreateResponse } from './types';\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 const data = await this.requestWithRetry<TransactionCreateResponse>('/api/v1/tm/transactions', {\n method: 'POST',\n body: JSON.stringify(request),\n }, undefined, undefined, requestOptions);\n return data;\n }\n\n async getTransaction(transactionId: string, requestOptions?: RequestOptions): Promise<Transaction> {\n const data = await this.requestWithRetry<Transaction>(`/api/v1/tm/transactions/status/${transactionId}`, {\n method: 'GET',\n }, undefined, undefined, requestOptions);\n return data;\n }\n}\n","import { verifyWebhookSignature, ValidationError } from '@vesant-sdk/core';\nimport type {\n TransactionCallbackEvent,\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 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 seenEvents = new Map<string, number>();\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 /** 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 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 } 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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vesant-sdk/transaction",
3
- "version": "0.1.1-next.f221d37",
3
+ "version": "0.1.1-next.f504af3",
4
4
  "description": "Transaction monitoring client for the Vesant Compliance Platform",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",