@vesant-sdk/transaction 0.1.0-dev.40d1c39 → 0.1.0-dev.51108af

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
@@ -4,9 +4,9 @@ import { Timestamp, BaseClient, BaseClientConfig, RequestOptions } from '@vesant
4
4
  * TypeScript types for the Transaction Monitoring (TM) Service
5
5
  */
6
6
 
7
- type TransactionType = "deposit" | "withdrawal" | "transfer" | "payment" | "refund" | "fee" | "adjustment";
8
- type TransactionMode = "fiat" | "crypto" | "bank_transfer" | "card" | "wallet";
9
- type TransactionStatus = "pending" | "processing" | "completed" | "failed" | "cancelled" | "reversed";
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";
10
10
  interface TransactionCreateDTO {
11
11
  tx_id: string;
12
12
  reference: string;
@@ -16,16 +16,29 @@ interface TransactionCreateDTO {
16
16
  transaction_mode: TransactionMode;
17
17
  amount: string;
18
18
  currency: string;
19
- status: TransactionStatus;
20
- source_account: string;
21
- destination_account: string;
22
- country: string;
23
- ip_address: string;
24
- metadata: Record<string, any>;
25
- benificiary_comment: 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
+ source_account?: string;
27
+ destination_account?: string;
28
+ country?: string;
29
+ ip_address?: string;
30
+ metadata?: Record<string, any>;
31
+ beneficiary_comment?: string;
26
32
  transaction_date: Timestamp;
27
33
  }
28
34
  type WithholdingType = "none" | "backup_us" | "backup_non_us" | "treaty";
35
+ /** Reason backup withholding or treaty rate was applied. */
36
+ type WithholdingReason = "tin_not_verified" | "customer_tax_form_not_verified" | "b_notice_backup_withholding";
37
+ /**
38
+ * Reason a withdrawal was blocked (status = "suspend" or "hold").
39
+ * Reflects TIN/W-9 deficiencies that triggered the tenant's configured transaction action.
40
+ */
41
+ type HoldReason = "no_tax_profile_found" | "tin_rejected" | "tin_expired" | "tin_not_provided" | "tin_not_verified" | "form_not_certified";
29
42
  type JSONB = Record<string, any>;
30
43
  type Transaction = {
31
44
  id: string;
@@ -39,8 +52,24 @@ type Transaction = {
39
52
  withheld_amount: string;
40
53
  released_amount: string;
41
54
  withholding_rate: number;
42
- withholding_reason?: string;
43
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;
66
+ /**
67
+ * 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.
71
+ */
72
+ is_first_withdrawal?: boolean;
44
73
  tax_year: number;
45
74
  currency: string;
46
75
  status: TransactionStatus;
@@ -50,7 +79,7 @@ type Transaction = {
50
79
  ip_address?: string;
51
80
  risk_score: number;
52
81
  metadata?: JSONB;
53
- benificiary_comment?: string;
82
+ beneficiary_comment?: string;
54
83
  transaction_date: string;
55
84
  created_at: string;
56
85
  updated_at: string;
@@ -66,10 +95,65 @@ interface TransactionClientConfig {
66
95
  debug?: boolean;
67
96
  environment?: 'production' | 'sandbox';
68
97
  }
98
+ /**
99
+ * Response from POST /api/v1/tm/transactions.
100
+ *
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
106
+ */
69
107
  type TransactionCreateResponse = {
70
108
  transaction: Transaction;
71
109
  message: string;
72
110
  };
111
+ /**
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.
115
+ */
116
+ interface TransactionCallbackEvent {
117
+ event_type: "tm.transaction.callback";
118
+ tx_id: string;
119
+ reference: string;
120
+ customer_id: string;
121
+ transaction_type: TransactionType;
122
+ transaction_mode: TransactionMode;
123
+ status: TransactionStatus;
124
+ amount: string;
125
+ currency: string;
126
+ withheld_amount: string;
127
+ released_amount: string;
128
+ withholding_rate: number;
129
+ withholding_type: WithholdingType;
130
+ /** Null when withholding_type is "none". */
131
+ withholding_reason: WithholdingReason | null;
132
+ amount_usd: number;
133
+ withheld_usd: number;
134
+ released_usd: number;
135
+ is_first_withdrawal: boolean;
136
+ /** Set when status is "suspend" or "hold"; null otherwise. */
137
+ hold_reason: HoldReason | null;
138
+ transaction_date: string;
139
+ }
140
+ /**
141
+ * Fired when a withdrawal is blocked due to missing or unverified W-9/TIN
142
+ * (status = "suspend" or "hold").
143
+ * Use this to notify the customer that TIN verification or W-9 submission is required.
144
+ */
145
+ interface TransactionHeldEvent {
146
+ event_type: "tm.transaction.held";
147
+ tx_id: string;
148
+ customer_id: string;
149
+ customer_email: string;
150
+ customer_name: string;
151
+ /** Reflects the tenant's configured transaction action that was applied. */
152
+ action: "suspend" | "hold";
153
+ hold_reason: HoldReason;
154
+ }
155
+ /** Discriminated union of all TM service webhook event payloads. */
156
+ type TransactionWebhookEvent = TransactionCallbackEvent | TransactionHeldEvent;
73
157
 
74
158
  /**
75
159
  * TransactionClient — SDK for the Transaction Monitoring service
@@ -78,7 +162,34 @@ type TransactionCreateResponse = {
78
162
  declare class TransactionClient extends BaseClient {
79
163
  constructor(config: BaseClientConfig);
80
164
  createTransaction(request: TransactionCreateDTO, requestOptions?: RequestOptions): Promise<TransactionCreateResponse>;
81
- getTransaction(transactionId: string, requestOptions?: RequestOptions): Promise<void>;
165
+ getTransaction(transactionId: string, requestOptions?: RequestOptions): Promise<Transaction>;
166
+ }
167
+
168
+ type EventHandler<T> = (event: T) => void | Promise<void>;
169
+ interface TransactionWebhookHandlerConfig {
170
+ secret: string;
171
+ /** Reject duplicate event_type+tx_id pairs within replayWindow. Default: true. */
172
+ replayProtection?: boolean;
173
+ /** How long (ms) to remember seen events for replay detection. Default: 300_000 (5 min). */
174
+ replayWindow?: number;
175
+ }
176
+ declare class TransactionWebhookHandler {
177
+ private readonly handlers;
178
+ private readonly secret;
179
+ private readonly replayProtection;
180
+ private readonly replayWindow;
181
+ private seenEvents;
182
+ constructor(config: TransactionWebhookHandlerConfig);
183
+ on(eventType: 'tm.transaction.callback', handler: EventHandler<TransactionCallbackEvent>): this;
184
+ on(eventType: 'tm.transaction.held', handler: EventHandler<TransactionHeldEvent>): this;
185
+ /** Verify HMAC-SHA256 signature only. Returns false instead of throwing. */
186
+ verify(rawBody: string, signature: string): Promise<boolean>;
187
+ /** Parse and validate required fields. Does not verify signature — use verifyAndParse in production. */
188
+ parse(body: string): TransactionWebhookEvent;
189
+ /** Verify signature, parse, and check for replays. Throws ValidationError on any failure. */
190
+ verifyAndParse(body: string, signature: string): Promise<TransactionWebhookEvent>;
191
+ /** Verify signature, parse, and dispatch to registered handlers. */
192
+ handle(body: string, signature: string): Promise<void>;
82
193
  }
83
194
 
84
- export { type JSONB, type Transaction, TransactionClient, type TransactionClientConfig, type TransactionCreateDTO, type TransactionCreateResponse, type TransactionMode, type TransactionStatus, type TransactionType, type WithholdingType };
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 };
package/dist/index.d.ts CHANGED
@@ -4,9 +4,9 @@ import { Timestamp, BaseClient, BaseClientConfig, RequestOptions } from '@vesant
4
4
  * TypeScript types for the Transaction Monitoring (TM) Service
5
5
  */
6
6
 
7
- type TransactionType = "deposit" | "withdrawal" | "transfer" | "payment" | "refund" | "fee" | "adjustment";
8
- type TransactionMode = "fiat" | "crypto" | "bank_transfer" | "card" | "wallet";
9
- type TransactionStatus = "pending" | "processing" | "completed" | "failed" | "cancelled" | "reversed";
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";
10
10
  interface TransactionCreateDTO {
11
11
  tx_id: string;
12
12
  reference: string;
@@ -16,16 +16,29 @@ interface TransactionCreateDTO {
16
16
  transaction_mode: TransactionMode;
17
17
  amount: string;
18
18
  currency: string;
19
- status: TransactionStatus;
20
- source_account: string;
21
- destination_account: string;
22
- country: string;
23
- ip_address: string;
24
- metadata: Record<string, any>;
25
- benificiary_comment: 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
+ source_account?: string;
27
+ destination_account?: string;
28
+ country?: string;
29
+ ip_address?: string;
30
+ metadata?: Record<string, any>;
31
+ beneficiary_comment?: string;
26
32
  transaction_date: Timestamp;
27
33
  }
28
34
  type WithholdingType = "none" | "backup_us" | "backup_non_us" | "treaty";
35
+ /** Reason backup withholding or treaty rate was applied. */
36
+ type WithholdingReason = "tin_not_verified" | "customer_tax_form_not_verified" | "b_notice_backup_withholding";
37
+ /**
38
+ * Reason a withdrawal was blocked (status = "suspend" or "hold").
39
+ * Reflects TIN/W-9 deficiencies that triggered the tenant's configured transaction action.
40
+ */
41
+ type HoldReason = "no_tax_profile_found" | "tin_rejected" | "tin_expired" | "tin_not_provided" | "tin_not_verified" | "form_not_certified";
29
42
  type JSONB = Record<string, any>;
30
43
  type Transaction = {
31
44
  id: string;
@@ -39,8 +52,24 @@ type Transaction = {
39
52
  withheld_amount: string;
40
53
  released_amount: string;
41
54
  withholding_rate: number;
42
- withholding_reason?: string;
43
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;
66
+ /**
67
+ * 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.
71
+ */
72
+ is_first_withdrawal?: boolean;
44
73
  tax_year: number;
45
74
  currency: string;
46
75
  status: TransactionStatus;
@@ -50,7 +79,7 @@ type Transaction = {
50
79
  ip_address?: string;
51
80
  risk_score: number;
52
81
  metadata?: JSONB;
53
- benificiary_comment?: string;
82
+ beneficiary_comment?: string;
54
83
  transaction_date: string;
55
84
  created_at: string;
56
85
  updated_at: string;
@@ -66,10 +95,65 @@ interface TransactionClientConfig {
66
95
  debug?: boolean;
67
96
  environment?: 'production' | 'sandbox';
68
97
  }
98
+ /**
99
+ * Response from POST /api/v1/tm/transactions.
100
+ *
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
106
+ */
69
107
  type TransactionCreateResponse = {
70
108
  transaction: Transaction;
71
109
  message: string;
72
110
  };
111
+ /**
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.
115
+ */
116
+ interface TransactionCallbackEvent {
117
+ event_type: "tm.transaction.callback";
118
+ tx_id: string;
119
+ reference: string;
120
+ customer_id: string;
121
+ transaction_type: TransactionType;
122
+ transaction_mode: TransactionMode;
123
+ status: TransactionStatus;
124
+ amount: string;
125
+ currency: string;
126
+ withheld_amount: string;
127
+ released_amount: string;
128
+ withholding_rate: number;
129
+ withholding_type: WithholdingType;
130
+ /** Null when withholding_type is "none". */
131
+ withholding_reason: WithholdingReason | null;
132
+ amount_usd: number;
133
+ withheld_usd: number;
134
+ released_usd: number;
135
+ is_first_withdrawal: boolean;
136
+ /** Set when status is "suspend" or "hold"; null otherwise. */
137
+ hold_reason: HoldReason | null;
138
+ transaction_date: string;
139
+ }
140
+ /**
141
+ * Fired when a withdrawal is blocked due to missing or unverified W-9/TIN
142
+ * (status = "suspend" or "hold").
143
+ * Use this to notify the customer that TIN verification or W-9 submission is required.
144
+ */
145
+ interface TransactionHeldEvent {
146
+ event_type: "tm.transaction.held";
147
+ tx_id: string;
148
+ customer_id: string;
149
+ customer_email: string;
150
+ customer_name: string;
151
+ /** Reflects the tenant's configured transaction action that was applied. */
152
+ action: "suspend" | "hold";
153
+ hold_reason: HoldReason;
154
+ }
155
+ /** Discriminated union of all TM service webhook event payloads. */
156
+ type TransactionWebhookEvent = TransactionCallbackEvent | TransactionHeldEvent;
73
157
 
74
158
  /**
75
159
  * TransactionClient — SDK for the Transaction Monitoring service
@@ -78,7 +162,34 @@ type TransactionCreateResponse = {
78
162
  declare class TransactionClient extends BaseClient {
79
163
  constructor(config: BaseClientConfig);
80
164
  createTransaction(request: TransactionCreateDTO, requestOptions?: RequestOptions): Promise<TransactionCreateResponse>;
81
- getTransaction(transactionId: string, requestOptions?: RequestOptions): Promise<void>;
165
+ getTransaction(transactionId: string, requestOptions?: RequestOptions): Promise<Transaction>;
166
+ }
167
+
168
+ type EventHandler<T> = (event: T) => void | Promise<void>;
169
+ interface TransactionWebhookHandlerConfig {
170
+ secret: string;
171
+ /** Reject duplicate event_type+tx_id pairs within replayWindow. Default: true. */
172
+ replayProtection?: boolean;
173
+ /** How long (ms) to remember seen events for replay detection. Default: 300_000 (5 min). */
174
+ replayWindow?: number;
175
+ }
176
+ declare class TransactionWebhookHandler {
177
+ private readonly handlers;
178
+ private readonly secret;
179
+ private readonly replayProtection;
180
+ private readonly replayWindow;
181
+ private seenEvents;
182
+ constructor(config: TransactionWebhookHandlerConfig);
183
+ on(eventType: 'tm.transaction.callback', handler: EventHandler<TransactionCallbackEvent>): this;
184
+ on(eventType: 'tm.transaction.held', handler: EventHandler<TransactionHeldEvent>): this;
185
+ /** Verify HMAC-SHA256 signature only. Returns false instead of throwing. */
186
+ verify(rawBody: string, signature: string): Promise<boolean>;
187
+ /** Parse and validate required fields. Does not verify signature — use verifyAndParse in production. */
188
+ parse(body: string): TransactionWebhookEvent;
189
+ /** Verify signature, parse, and check for replays. Throws ValidationError on any failure. */
190
+ verifyAndParse(body: string, signature: string): Promise<TransactionWebhookEvent>;
191
+ /** Verify signature, parse, and dispatch to registered handlers. */
192
+ handle(body: string, signature: string): Promise<void>;
82
193
  }
83
194
 
84
- export { type JSONB, type Transaction, TransactionClient, type TransactionClientConfig, type TransactionCreateDTO, type TransactionCreateResponse, type TransactionMode, type TransactionStatus, type TransactionType, type WithholdingType };
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 };
package/dist/index.js CHANGED
@@ -15,12 +15,80 @@ var TransactionClient = class extends core.BaseClient {
15
15
  return data;
16
16
  }
17
17
  async getTransaction(transactionId, requestOptions) {
18
- await this.requestWithRetry(`/api/v1/tm/transactions/status/${transactionId}`, {
18
+ const data = await this.requestWithRetry(`/api/v1/tm/transactions/status/${transactionId}`, {
19
19
  method: "GET"
20
20
  }, void 0, void 0, requestOptions);
21
+ return data;
22
+ }
23
+ };
24
+ var TransactionWebhookHandler = class {
25
+ constructor(config) {
26
+ this.handlers = {
27
+ "tm.transaction.callback": [],
28
+ "tm.transaction.held": []
29
+ };
30
+ this.seenEvents = /* @__PURE__ */ new Map();
31
+ this.secret = config.secret;
32
+ this.replayProtection = config.replayProtection ?? true;
33
+ this.replayWindow = config.replayWindow ?? 3e5;
34
+ }
35
+ on(eventType, handler) {
36
+ this.handlers[eventType].push(handler);
37
+ return this;
38
+ }
39
+ /** Verify HMAC-SHA256 signature only. Returns false instead of throwing. */
40
+ async verify(rawBody, signature) {
41
+ return core.verifyWebhookSignature(rawBody, signature, this.secret);
42
+ }
43
+ /** Parse and validate required fields. Does not verify signature — use verifyAndParse in production. */
44
+ parse(body) {
45
+ const event = JSON.parse(body);
46
+ if (!event.event_type || !event.tx_id) {
47
+ throw new core.ValidationError(
48
+ "Invalid TM webhook event: missing required fields (event_type, tx_id)",
49
+ ["event_type", "tx_id"]
50
+ );
51
+ }
52
+ return event;
53
+ }
54
+ /** Verify signature, parse, and check for replays. Throws ValidationError on any failure. */
55
+ async verifyAndParse(body, signature) {
56
+ const isValid = await core.verifyWebhookSignature(body, signature, this.secret);
57
+ if (!isValid) {
58
+ throw new core.ValidationError("Invalid webhook signature", ["signature"]);
59
+ }
60
+ const event = this.parse(body);
61
+ if (this.replayProtection) {
62
+ const key = `${event.event_type}:${event.tx_id}`;
63
+ if (this.seenEvents.has(key)) {
64
+ throw new core.ValidationError(
65
+ `Duplicate webhook event: ${event.event_type} for ${event.tx_id} has already been processed`,
66
+ ["tx_id"]
67
+ );
68
+ }
69
+ const now = Date.now();
70
+ this.seenEvents.set(key, now);
71
+ if (this.seenEvents.size > 1e3) {
72
+ for (const [k, seenAt] of this.seenEvents) {
73
+ if (now - seenAt > this.replayWindow) {
74
+ this.seenEvents.delete(k);
75
+ }
76
+ }
77
+ }
78
+ }
79
+ return event;
80
+ }
81
+ /** Verify signature, parse, and dispatch to registered handlers. */
82
+ async handle(body, signature) {
83
+ const event = await this.verifyAndParse(body, signature);
84
+ const handlers = this.handlers[event.event_type];
85
+ for (const handler of handlers) {
86
+ await handler(event);
87
+ }
21
88
  }
22
89
  };
23
90
 
24
91
  exports.TransactionClient = TransactionClient;
92
+ exports.TransactionWebhookHandler = TransactionWebhookHandler;
25
93
  //# sourceMappingURL=index.js.map
26
94
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts"],"names":["BaseClient"],"mappings":";;;;;AAQO,IAAM,iBAAA,GAAN,cAAgCA,eAAA,CAAW;AAAA,EAChD,YAAY,MAAA,EAA0B;AACpC,IAAA,KAAA,CAAM,MAAM,CAAA;AAAA,EACd;AAAA,EAEA,MAAM,iBAAA,CACJ,OAAA,EACA,cAAA,EACoC;AACpC,IAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,gBAAA,CAA4C,yBAAA,EAA2B;AAAA,MAC7F,MAAA,EAAQ,MAAA;AAAA,MACR,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,OAAO;AAAA,KAC9B,EAAG,MAAA,EAAW,MAAA,EAAW,cAAc,CAAA;AACvC,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEA,MAAM,cAAA,CAAe,aAAA,EAAuB,cAAA,EAAgD;AAC1F,IAAA,MAAM,IAAA,CAAK,gBAAA,CAAuB,CAAA,+BAAA,EAAkC,aAAa,CAAA,CAAA,EAAI;AAAA,MACnF,MAAA,EAAQ;AAAA,KACV,EAAG,MAAA,EAAW,MAAA,EAAW,cAAc,CAAA;AAAA,EACzC;AACF","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 { 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<void> {\n await this.requestWithRetry<void>(`/api/v1/tm/transactions/status/${transactionId}`, {\n method: 'GET',\n }, undefined, undefined, requestOptions);\n }\n}\n"]}
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"]}
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { BaseClient } from '@vesant-sdk/core';
1
+ import { BaseClient, verifyWebhookSignature, ValidationError } from '@vesant-sdk/core';
2
2
 
3
3
  // src/client.ts
4
4
  var TransactionClient = class extends BaseClient {
@@ -13,12 +13,79 @@ var TransactionClient = class extends BaseClient {
13
13
  return data;
14
14
  }
15
15
  async getTransaction(transactionId, requestOptions) {
16
- await this.requestWithRetry(`/api/v1/tm/transactions/status/${transactionId}`, {
16
+ const data = await this.requestWithRetry(`/api/v1/tm/transactions/status/${transactionId}`, {
17
17
  method: "GET"
18
18
  }, void 0, void 0, requestOptions);
19
+ return data;
20
+ }
21
+ };
22
+ var TransactionWebhookHandler = class {
23
+ constructor(config) {
24
+ this.handlers = {
25
+ "tm.transaction.callback": [],
26
+ "tm.transaction.held": []
27
+ };
28
+ this.seenEvents = /* @__PURE__ */ new Map();
29
+ this.secret = config.secret;
30
+ this.replayProtection = config.replayProtection ?? true;
31
+ this.replayWindow = config.replayWindow ?? 3e5;
32
+ }
33
+ on(eventType, handler) {
34
+ this.handlers[eventType].push(handler);
35
+ return this;
36
+ }
37
+ /** Verify HMAC-SHA256 signature only. Returns false instead of throwing. */
38
+ async verify(rawBody, signature) {
39
+ return verifyWebhookSignature(rawBody, signature, this.secret);
40
+ }
41
+ /** Parse and validate required fields. Does not verify signature — use verifyAndParse in production. */
42
+ parse(body) {
43
+ const event = JSON.parse(body);
44
+ if (!event.event_type || !event.tx_id) {
45
+ throw new ValidationError(
46
+ "Invalid TM webhook event: missing required fields (event_type, tx_id)",
47
+ ["event_type", "tx_id"]
48
+ );
49
+ }
50
+ return event;
51
+ }
52
+ /** Verify signature, parse, and check for replays. Throws ValidationError on any failure. */
53
+ async verifyAndParse(body, signature) {
54
+ const isValid = await verifyWebhookSignature(body, signature, this.secret);
55
+ if (!isValid) {
56
+ throw new ValidationError("Invalid webhook signature", ["signature"]);
57
+ }
58
+ const event = this.parse(body);
59
+ if (this.replayProtection) {
60
+ const key = `${event.event_type}:${event.tx_id}`;
61
+ if (this.seenEvents.has(key)) {
62
+ throw new ValidationError(
63
+ `Duplicate webhook event: ${event.event_type} for ${event.tx_id} has already been processed`,
64
+ ["tx_id"]
65
+ );
66
+ }
67
+ const now = Date.now();
68
+ this.seenEvents.set(key, now);
69
+ if (this.seenEvents.size > 1e3) {
70
+ for (const [k, seenAt] of this.seenEvents) {
71
+ if (now - seenAt > this.replayWindow) {
72
+ this.seenEvents.delete(k);
73
+ }
74
+ }
75
+ }
76
+ }
77
+ return event;
78
+ }
79
+ /** Verify signature, parse, and dispatch to registered handlers. */
80
+ async handle(body, signature) {
81
+ const event = await this.verifyAndParse(body, signature);
82
+ const handlers = this.handlers[event.event_type];
83
+ for (const handler of handlers) {
84
+ await handler(event);
85
+ }
19
86
  }
20
87
  };
21
88
 
22
- export { TransactionClient };
89
+ export { TransactionClient, TransactionWebhookHandler };
23
90
  //# sourceMappingURL=index.mjs.map
24
91
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts"],"names":[],"mappings":";;;AAQO,IAAM,iBAAA,GAAN,cAAgC,UAAA,CAAW;AAAA,EAChD,YAAY,MAAA,EAA0B;AACpC,IAAA,KAAA,CAAM,MAAM,CAAA;AAAA,EACd;AAAA,EAEA,MAAM,iBAAA,CACJ,OAAA,EACA,cAAA,EACoC;AACpC,IAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,gBAAA,CAA4C,yBAAA,EAA2B;AAAA,MAC7F,MAAA,EAAQ,MAAA;AAAA,MACR,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,OAAO;AAAA,KAC9B,EAAG,MAAA,EAAW,MAAA,EAAW,cAAc,CAAA;AACvC,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEA,MAAM,cAAA,CAAe,aAAA,EAAuB,cAAA,EAAgD;AAC1F,IAAA,MAAM,IAAA,CAAK,gBAAA,CAAuB,CAAA,+BAAA,EAAkC,aAAa,CAAA,CAAA,EAAI;AAAA,MACnF,MAAA,EAAQ;AAAA,KACV,EAAG,MAAA,EAAW,MAAA,EAAW,cAAc,CAAA;AAAA,EACzC;AACF","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 { 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<void> {\n await this.requestWithRetry<void>(`/api/v1/tm/transactions/status/${transactionId}`, {\n method: 'GET',\n }, undefined, undefined, requestOptions);\n }\n}\n"]}
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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vesant-sdk/transaction",
3
- "version": "0.1.0-dev.40d1c39",
3
+ "version": "0.1.0-dev.51108af",
4
4
  "description": "Transaction monitoring client for the Vesant Compliance Platform",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",