@vesant-sdk/transaction 0.1.2-next.8d7fa63 → 0.1.2-next.9770a24

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.js CHANGED
@@ -123,6 +123,18 @@ function assertWellFormedCurrency(currency) {
123
123
  );
124
124
  }
125
125
  }
126
+ function withRecognizedPaymentMethod(request) {
127
+ const paymentMethod = request.metadata?.payment_method;
128
+ if (paymentMethod === void 0) return request;
129
+ const paymentType = typeof paymentMethod === "string" ? core.normalizePaymentType(paymentMethod) : void 0;
130
+ if (paymentType === void 0) {
131
+ throw new core.ValidationError(
132
+ `Invalid payment method: ${JSON.stringify(paymentMethod)}. Expected a recognized payment type such as "$credit_card".`,
133
+ ["metadata.payment_method"]
134
+ );
135
+ }
136
+ return { ...request, metadata: { ...request.metadata, payment_method: paymentType } };
137
+ }
126
138
  var TransactionClient = class extends core.BaseClient {
127
139
  constructor(config) {
128
140
  super(config);
@@ -137,10 +149,11 @@ var TransactionClient = class extends core.BaseClient {
137
149
  */
138
150
  async createTransaction(request, requestOptions) {
139
151
  assertWellFormedCurrency(request.currency);
152
+ const body = withRecognizedPaymentMethod(request);
140
153
  try {
141
154
  return await this.requestWithRetry("/api/v1/transactions", {
142
155
  method: "POST",
143
- body: JSON.stringify(request)
156
+ body: JSON.stringify(body)
144
157
  }, void 0, void 0, requestOptions);
145
158
  } catch (error) {
146
159
  if (error instanceof core.VesantError && error.statusCode === 409) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/pending-withdrawal.ts","../src/tax/client.ts","../src/client.ts","../src/webhook-handler.ts"],"names":["VesantError","BaseClient","ValidationError","InMemoryDedupStore","verifyWebhookSignature"],"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 /**\n * A disabled trigger (e.g. `trigger_manual` off in tenant rules) returns 200 with\n * the customer's unchanged profile and no form sent — check `public_url` presence\n * or compare `form_status`/`tin_status` rather than trusting a 200 alone.\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, ValidationError } 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\n/**\n * Ingestion requires an ISO 4217 style currency — exactly three ASCII letters,\n * the same shape the backend persists and the frontend's Intl.NumberFormat\n * accepts. Reject anything else (empty, a country code, or a social-casino\n * ticker like \"SC\"/\"GC\") up front so a malformed code can never reach the\n * ingestion endpoint and crash a downstream currency formatter. This is a\n * structural guard only — it does not rewrite the code; callers own any\n * business mapping (e.g. Sweeps Coins → USD) before submitting.\n */\nfunction assertWellFormedCurrency(currency: string): void {\n if (!/^[A-Za-z]{3}$/.test(currency ?? '')) {\n throw new ValidationError(\n `Invalid currency code: ${JSON.stringify(currency)}. Expected a 3-letter ISO 4217 code.`,\n ['currency']\n );\n }\n}\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 /**\n * Submit a transaction via the unified ingestion endpoint.\n *\n * POST /api/v1/transactions is gated on ANY of transaction monitoring, tax,\n * or fraud being enabled for the tenant, so fraud-only and tax-only tenants\n * can ingest even with TM switched off.\n */\n async createTransaction(\n request: TransactionCreateDTO,\n requestOptions?: RequestOptions\n ): Promise<TransactionCreateResponse> {\n assertWellFormedCurrency(request.currency);\n try {\n return await this.requestWithRetry<TransactionCreateResponse>('/api/v1/transactions', {\n method: 'POST',\n body: JSON.stringify(request),\n }, undefined, undefined, requestOptions);\n } catch (error) {\n if (error instanceof VesantError && error.statusCode === 409) {\n const transaction = await this.getTransaction(request.tx_id, requestOptions);\n return { transaction, message: 'Transaction already exists' };\n }\n throw error;\n }\n }\n\n async submitWithdrawal(\n request: TransactionCreateDTO,\n requestOptions?: RequestOptions\n ): Promise<PendingWithdrawal> {\n const response = await this.createTransaction(\n { ...request, transaction_type: 'withdrawal' },\n requestOptions\n );\n return new PendingWithdrawal(response.transaction.tx_id, response.transaction);\n }\n\n async getTransaction(transactionId: string, requestOptions?: RequestOptions): Promise<Transaction> {\n return this.requestWithRetry<Transaction>(`/api/v1/tm/transactions/status/${transactionId}`, {\n method: 'GET',\n }, undefined, undefined, requestOptions);\n }\n\n async getSettings(): Promise<TmTenantSettings> {\n return this.requestWithRetry<TmTenantSettings>('/api/v1/tm/settings', {\n method: 'GET',\n });\n }\n}\n","import { verifyWebhookSignature, ValidationError, InMemoryDedupStore, type WebhookDedupStore } from '@vesant-sdk/core';\nimport { PendingWithdrawal } from './pending-withdrawal';\nimport type {\n TransactionCallbackEvent,\n TransactionCallbackHandler,\n TransactionHeldEvent,\n TransactionWebhookEvent,\n} from './types';\n\ntype EventHandler<T> = (event: T) => void | Promise<void>;\n\ntype HandlerMap = {\n 'tm.transaction.callback': Array<EventHandler<TransactionCallbackEvent>>;\n 'tm.transaction.held': Array<EventHandler<TransactionHeldEvent>>;\n};\n\nexport const TRANSACTION_SIGNATURE_HEADER = 'x-webhook-signature';\n\nexport interface TransactionWebhookHandlerConfig {\n secret: string;\n /** Reject duplicate event_type+tx_id pairs within replayWindow. Default: true. */\n replayProtection?: boolean;\n /** How long (ms) to remember seen events for replay detection. Default: 300_000 (5 min). */\n replayWindow?: number;\n /** Pluggable dedup store for replay protection (default: in-memory, process-local). */\n dedupStore?: WebhookDedupStore;\n}\n\nexport class TransactionWebhookHandler {\n private readonly handlers: HandlerMap = {\n 'tm.transaction.callback': [],\n 'tm.transaction.held': [],\n };\n private readonly secret: string;\n private readonly replayProtection: boolean;\n private readonly replayWindow: number;\n private readonly dedupStore: WebhookDedupStore;\n private readonly pendingWithdrawals = new Map<string, PendingWithdrawal>();\n private callbackHandler: TransactionCallbackHandler | null = null;\n\n constructor(config: TransactionWebhookHandlerConfig) {\n this.secret = config.secret;\n this.replayProtection = config.replayProtection ?? true;\n this.replayWindow = config.replayWindow ?? 300_000;\n this.dedupStore = config.dedupStore ?? new InMemoryDedupStore();\n }\n\n on(eventType: 'tm.transaction.callback', handler: EventHandler<TransactionCallbackEvent>): this;\n on(eventType: 'tm.transaction.held', handler: EventHandler<TransactionHeldEvent>): this;\n on(eventType: keyof HandlerMap, handler: EventHandler<TransactionCallbackEvent> | EventHandler<TransactionHeldEvent>): this {\n (this.handlers[eventType] as Array<typeof handler>).push(handler);\n return this;\n }\n\n /** Register a semantic callback handler for the three withholding outcomes. Replaces any previous handler. */\n registerCallbackHandler(handler: TransactionCallbackHandler): this {\n this.callbackHandler = handler;\n return this;\n }\n\n /** Track a PendingWithdrawal so it is auto-settled when its tm.transaction.callback arrives. */\n link(pending: PendingWithdrawal): this {\n this.pendingWithdrawals.set(pending.tx_id, pending);\n return this;\n }\n\n /** Stop tracking a PendingWithdrawal by tx_id. */\n unlink(tx_id: string): this {\n this.pendingWithdrawals.delete(tx_id);\n return this;\n }\n\n /** Verify HMAC-SHA256 signature only. Returns false instead of throwing. */\n async verify(rawBody: string, signature: string): Promise<boolean> {\n return verifyWebhookSignature(rawBody, signature, this.secret);\n }\n\n /** Parse and validate required fields. Does not verify signature — use verifyAndParse in production. */\n parse(body: string): TransactionWebhookEvent {\n const event = JSON.parse(body) as TransactionWebhookEvent;\n if (!event.event_type || !event.tx_id) {\n throw new ValidationError(\n 'Invalid TM webhook event: missing required fields (event_type, tx_id)',\n ['event_type', 'tx_id']\n );\n }\n return event;\n }\n\n /** Verify signature, parse, and check for replays. Throws ValidationError on any failure. */\n async verifyAndParse(body: string, signature: string): Promise<TransactionWebhookEvent> {\n const isValid = await verifyWebhookSignature(body, signature, this.secret);\n if (!isValid) {\n throw new ValidationError('Invalid webhook signature', ['signature']);\n }\n\n const event = this.parse(body);\n\n if (this.replayProtection) {\n const key = `${event.event_type}:${event.tx_id}`;\n if (await this.dedupStore.seen(key)) {\n throw new ValidationError(\n `Duplicate webhook event: ${event.event_type} for ${event.tx_id} has already been processed`,\n ['tx_id']\n );\n }\n await this.dedupStore.mark(key, this.replayWindow);\n }\n\n return event;\n }\n\n /** Verify signature, parse, and dispatch to registered handlers. */\n async handle(body: string, signature: string): Promise<void> {\n const event = await this.verifyAndParse(body, signature);\n\n if (event.event_type === 'tm.transaction.callback') {\n const pending = this.pendingWithdrawals.get(event.tx_id);\n if (pending) {\n pending._settle(event);\n this.pendingWithdrawals.delete(event.tx_id);\n }\n\n if (this.callbackHandler) {\n if (event.status === 'hold' || event.status === 'suspend') {\n await this.callbackHandler.onHeld?.(event);\n } else if (event.withholding_type === 'none') {\n await this.callbackHandler.onReleased?.(event);\n } else {\n await this.callbackHandler.onWithheld?.(event);\n }\n }\n }\n\n const handlers = this.handlers[event.event_type] as Array<EventHandler<typeof event>>;\n for (const handler of handlers) {\n await handler(event);\n }\n }\n}\n"]}
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","ValidationError","normalizePaymentType","InMemoryDedupStore","verifyWebhookSignature"],"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 /**\n * A disabled trigger (e.g. `trigger_manual` off in tenant rules) returns 200 with\n * the customer's unchanged profile and no form sent — check `public_url` presence\n * or compare `form_status`/`tin_status` rather than trusting a 200 alone.\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, ValidationError, normalizePaymentType } 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\n/**\n * Ingestion requires an ISO 4217 style currency — exactly three ASCII letters,\n * the same shape the backend persists and the frontend's Intl.NumberFormat\n * accepts. Reject anything else (empty, a country code, or a social-casino\n * ticker like \"SC\"/\"GC\") up front so a malformed code can never reach the\n * ingestion endpoint and crash a downstream currency formatter. This is a\n * structural guard only — it does not rewrite the code; callers own any\n * business mapping (e.g. Sweeps Coins → USD) before submitting.\n */\nfunction assertWellFormedCurrency(currency: string): void {\n if (!/^[A-Za-z]{3}$/.test(currency ?? '')) {\n throw new ValidationError(\n `Invalid currency code: ${JSON.stringify(currency)}. Expected a 3-letter ISO 4217 code.`,\n ['currency']\n );\n }\n}\n\n/**\n * Fraud rules and payment-method reporting only recognize the standard payment\n * types, so `metadata.payment_method` is rewritten to its recognized form (for\n * example `bank_account` → `$electronic_fund_transfer`) and any other value is\n * rejected before it can reach ingestion.\n */\nfunction withRecognizedPaymentMethod(request: TransactionCreateDTO): TransactionCreateDTO {\n const paymentMethod = request.metadata?.payment_method;\n if (paymentMethod === undefined) return request;\n const paymentType =\n typeof paymentMethod === 'string' ? normalizePaymentType(paymentMethod) : undefined;\n if (paymentType === undefined) {\n throw new ValidationError(\n `Invalid payment method: ${JSON.stringify(paymentMethod)}. Expected a recognized payment type such as \"$credit_card\".`,\n ['metadata.payment_method']\n );\n }\n return { ...request, metadata: { ...request.metadata, payment_method: paymentType } };\n}\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 /**\n * Submit a transaction via the unified ingestion endpoint.\n *\n * POST /api/v1/transactions is gated on ANY of transaction monitoring, tax,\n * or fraud being enabled for the tenant, so fraud-only and tax-only tenants\n * can ingest even with TM switched off.\n */\n async createTransaction(\n request: TransactionCreateDTO,\n requestOptions?: RequestOptions\n ): Promise<TransactionCreateResponse> {\n assertWellFormedCurrency(request.currency);\n const body = withRecognizedPaymentMethod(request);\n try {\n return await this.requestWithRetry<TransactionCreateResponse>('/api/v1/transactions', {\n method: 'POST',\n body: JSON.stringify(body),\n }, undefined, undefined, requestOptions);\n } catch (error) {\n if (error instanceof VesantError && error.statusCode === 409) {\n const transaction = await this.getTransaction(request.tx_id, requestOptions);\n return { transaction, message: 'Transaction already exists' };\n }\n throw error;\n }\n }\n\n async submitWithdrawal(\n request: TransactionCreateDTO,\n requestOptions?: RequestOptions\n ): Promise<PendingWithdrawal> {\n const response = await this.createTransaction(\n { ...request, transaction_type: 'withdrawal' },\n requestOptions\n );\n return new PendingWithdrawal(response.transaction.tx_id, response.transaction);\n }\n\n async getTransaction(transactionId: string, requestOptions?: RequestOptions): Promise<Transaction> {\n return this.requestWithRetry<Transaction>(`/api/v1/tm/transactions/status/${transactionId}`, {\n method: 'GET',\n }, undefined, undefined, requestOptions);\n }\n\n async getSettings(): Promise<TmTenantSettings> {\n return this.requestWithRetry<TmTenantSettings>('/api/v1/tm/settings', {\n method: 'GET',\n });\n }\n}\n","import { verifyWebhookSignature, ValidationError, InMemoryDedupStore, type WebhookDedupStore } from '@vesant-sdk/core';\nimport { PendingWithdrawal } from './pending-withdrawal';\nimport type {\n TransactionCallbackEvent,\n TransactionCallbackHandler,\n TransactionHeldEvent,\n TransactionWebhookEvent,\n} from './types';\n\ntype EventHandler<T> = (event: T) => void | Promise<void>;\n\ntype HandlerMap = {\n 'tm.transaction.callback': Array<EventHandler<TransactionCallbackEvent>>;\n 'tm.transaction.held': Array<EventHandler<TransactionHeldEvent>>;\n};\n\nexport const TRANSACTION_SIGNATURE_HEADER = 'x-webhook-signature';\n\nexport interface TransactionWebhookHandlerConfig {\n secret: string;\n /** Reject duplicate event_type+tx_id pairs within replayWindow. Default: true. */\n replayProtection?: boolean;\n /** How long (ms) to remember seen events for replay detection. Default: 300_000 (5 min). */\n replayWindow?: number;\n /** Pluggable dedup store for replay protection (default: in-memory, process-local). */\n dedupStore?: WebhookDedupStore;\n}\n\nexport class TransactionWebhookHandler {\n private readonly handlers: HandlerMap = {\n 'tm.transaction.callback': [],\n 'tm.transaction.held': [],\n };\n private readonly secret: string;\n private readonly replayProtection: boolean;\n private readonly replayWindow: number;\n private readonly dedupStore: WebhookDedupStore;\n private readonly pendingWithdrawals = new Map<string, PendingWithdrawal>();\n private callbackHandler: TransactionCallbackHandler | null = null;\n\n constructor(config: TransactionWebhookHandlerConfig) {\n this.secret = config.secret;\n this.replayProtection = config.replayProtection ?? true;\n this.replayWindow = config.replayWindow ?? 300_000;\n this.dedupStore = config.dedupStore ?? new InMemoryDedupStore();\n }\n\n on(eventType: 'tm.transaction.callback', handler: EventHandler<TransactionCallbackEvent>): this;\n on(eventType: 'tm.transaction.held', handler: EventHandler<TransactionHeldEvent>): this;\n on(eventType: keyof HandlerMap, handler: EventHandler<TransactionCallbackEvent> | EventHandler<TransactionHeldEvent>): this {\n (this.handlers[eventType] as Array<typeof handler>).push(handler);\n return this;\n }\n\n /** Register a semantic callback handler for the three withholding outcomes. Replaces any previous handler. */\n registerCallbackHandler(handler: TransactionCallbackHandler): this {\n this.callbackHandler = handler;\n return this;\n }\n\n /** Track a PendingWithdrawal so it is auto-settled when its tm.transaction.callback arrives. */\n link(pending: PendingWithdrawal): this {\n this.pendingWithdrawals.set(pending.tx_id, pending);\n return this;\n }\n\n /** Stop tracking a PendingWithdrawal by tx_id. */\n unlink(tx_id: string): this {\n this.pendingWithdrawals.delete(tx_id);\n return this;\n }\n\n /** Verify HMAC-SHA256 signature only. Returns false instead of throwing. */\n async verify(rawBody: string, signature: string): Promise<boolean> {\n return verifyWebhookSignature(rawBody, signature, this.secret);\n }\n\n /** Parse and validate required fields. Does not verify signature — use verifyAndParse in production. */\n parse(body: string): TransactionWebhookEvent {\n const event = JSON.parse(body) as TransactionWebhookEvent;\n if (!event.event_type || !event.tx_id) {\n throw new ValidationError(\n 'Invalid TM webhook event: missing required fields (event_type, tx_id)',\n ['event_type', 'tx_id']\n );\n }\n return event;\n }\n\n /** Verify signature, parse, and check for replays. Throws ValidationError on any failure. */\n async verifyAndParse(body: string, signature: string): Promise<TransactionWebhookEvent> {\n const isValid = await verifyWebhookSignature(body, signature, this.secret);\n if (!isValid) {\n throw new ValidationError('Invalid webhook signature', ['signature']);\n }\n\n const event = this.parse(body);\n\n if (this.replayProtection) {\n const key = `${event.event_type}:${event.tx_id}`;\n if (await this.dedupStore.seen(key)) {\n throw new ValidationError(\n `Duplicate webhook event: ${event.event_type} for ${event.tx_id} has already been processed`,\n ['tx_id']\n );\n }\n await this.dedupStore.mark(key, this.replayWindow);\n }\n\n return event;\n }\n\n /** Verify signature, parse, and dispatch to registered handlers. */\n async handle(body: string, signature: string): Promise<void> {\n const event = await this.verifyAndParse(body, signature);\n\n if (event.event_type === 'tm.transaction.callback') {\n const pending = this.pendingWithdrawals.get(event.tx_id);\n if (pending) {\n pending._settle(event);\n this.pendingWithdrawals.delete(event.tx_id);\n }\n\n if (this.callbackHandler) {\n if (event.status === 'hold' || event.status === 'suspend') {\n await this.callbackHandler.onHeld?.(event);\n } else if (event.withholding_type === 'none') {\n await this.callbackHandler.onReleased?.(event);\n } else {\n await this.callbackHandler.onWithheld?.(event);\n }\n }\n }\n\n const handlers = this.handlers[event.event_type] as Array<EventHandler<typeof event>>;\n for (const handler of handlers) {\n await handler(event);\n }\n }\n}\n"]}
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { VesantError, BaseClient, InMemoryDedupStore, verifyWebhookSignature, ValidationError } from '@vesant-sdk/core';
1
+ import { VesantError, BaseClient, InMemoryDedupStore, verifyWebhookSignature, ValidationError, normalizePaymentType } from '@vesant-sdk/core';
2
2
 
3
3
  // src/client.ts
4
4
  var DuplicateTransactionError = class _DuplicateTransactionError extends VesantError {
@@ -121,6 +121,18 @@ function assertWellFormedCurrency(currency) {
121
121
  );
122
122
  }
123
123
  }
124
+ function withRecognizedPaymentMethod(request) {
125
+ const paymentMethod = request.metadata?.payment_method;
126
+ if (paymentMethod === void 0) return request;
127
+ const paymentType = typeof paymentMethod === "string" ? normalizePaymentType(paymentMethod) : void 0;
128
+ if (paymentType === void 0) {
129
+ throw new ValidationError(
130
+ `Invalid payment method: ${JSON.stringify(paymentMethod)}. Expected a recognized payment type such as "$credit_card".`,
131
+ ["metadata.payment_method"]
132
+ );
133
+ }
134
+ return { ...request, metadata: { ...request.metadata, payment_method: paymentType } };
135
+ }
124
136
  var TransactionClient = class extends BaseClient {
125
137
  constructor(config) {
126
138
  super(config);
@@ -135,10 +147,11 @@ var TransactionClient = class extends BaseClient {
135
147
  */
136
148
  async createTransaction(request, requestOptions) {
137
149
  assertWellFormedCurrency(request.currency);
150
+ const body = withRecognizedPaymentMethod(request);
138
151
  try {
139
152
  return await this.requestWithRetry("/api/v1/transactions", {
140
153
  method: "POST",
141
- body: JSON.stringify(request)
154
+ body: JSON.stringify(body)
142
155
  }, void 0, void 0, requestOptions);
143
156
  } catch (error) {
144
157
  if (error instanceof VesantError && error.statusCode === 409) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/pending-withdrawal.ts","../src/tax/client.ts","../src/client.ts","../src/webhook-handler.ts"],"names":["VesantError","BaseClient","ValidationError"],"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 /**\n * A disabled trigger (e.g. `trigger_manual` off in tenant rules) returns 200 with\n * the customer's unchanged profile and no form sent — check `public_url` presence\n * or compare `form_status`/`tin_status` rather than trusting a 200 alone.\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, ValidationError } 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\n/**\n * Ingestion requires an ISO 4217 style currency — exactly three ASCII letters,\n * the same shape the backend persists and the frontend's Intl.NumberFormat\n * accepts. Reject anything else (empty, a country code, or a social-casino\n * ticker like \"SC\"/\"GC\") up front so a malformed code can never reach the\n * ingestion endpoint and crash a downstream currency formatter. This is a\n * structural guard only — it does not rewrite the code; callers own any\n * business mapping (e.g. Sweeps Coins → USD) before submitting.\n */\nfunction assertWellFormedCurrency(currency: string): void {\n if (!/^[A-Za-z]{3}$/.test(currency ?? '')) {\n throw new ValidationError(\n `Invalid currency code: ${JSON.stringify(currency)}. Expected a 3-letter ISO 4217 code.`,\n ['currency']\n );\n }\n}\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 /**\n * Submit a transaction via the unified ingestion endpoint.\n *\n * POST /api/v1/transactions is gated on ANY of transaction monitoring, tax,\n * or fraud being enabled for the tenant, so fraud-only and tax-only tenants\n * can ingest even with TM switched off.\n */\n async createTransaction(\n request: TransactionCreateDTO,\n requestOptions?: RequestOptions\n ): Promise<TransactionCreateResponse> {\n assertWellFormedCurrency(request.currency);\n try {\n return await this.requestWithRetry<TransactionCreateResponse>('/api/v1/transactions', {\n method: 'POST',\n body: JSON.stringify(request),\n }, undefined, undefined, requestOptions);\n } catch (error) {\n if (error instanceof VesantError && error.statusCode === 409) {\n const transaction = await this.getTransaction(request.tx_id, requestOptions);\n return { transaction, message: 'Transaction already exists' };\n }\n throw error;\n }\n }\n\n async submitWithdrawal(\n request: TransactionCreateDTO,\n requestOptions?: RequestOptions\n ): Promise<PendingWithdrawal> {\n const response = await this.createTransaction(\n { ...request, transaction_type: 'withdrawal' },\n requestOptions\n );\n return new PendingWithdrawal(response.transaction.tx_id, response.transaction);\n }\n\n async getTransaction(transactionId: string, requestOptions?: RequestOptions): Promise<Transaction> {\n return this.requestWithRetry<Transaction>(`/api/v1/tm/transactions/status/${transactionId}`, {\n method: 'GET',\n }, undefined, undefined, requestOptions);\n }\n\n async getSettings(): Promise<TmTenantSettings> {\n return this.requestWithRetry<TmTenantSettings>('/api/v1/tm/settings', {\n method: 'GET',\n });\n }\n}\n","import { verifyWebhookSignature, ValidationError, InMemoryDedupStore, type WebhookDedupStore } from '@vesant-sdk/core';\nimport { PendingWithdrawal } from './pending-withdrawal';\nimport type {\n TransactionCallbackEvent,\n TransactionCallbackHandler,\n TransactionHeldEvent,\n TransactionWebhookEvent,\n} from './types';\n\ntype EventHandler<T> = (event: T) => void | Promise<void>;\n\ntype HandlerMap = {\n 'tm.transaction.callback': Array<EventHandler<TransactionCallbackEvent>>;\n 'tm.transaction.held': Array<EventHandler<TransactionHeldEvent>>;\n};\n\nexport const TRANSACTION_SIGNATURE_HEADER = 'x-webhook-signature';\n\nexport interface TransactionWebhookHandlerConfig {\n secret: string;\n /** Reject duplicate event_type+tx_id pairs within replayWindow. Default: true. */\n replayProtection?: boolean;\n /** How long (ms) to remember seen events for replay detection. Default: 300_000 (5 min). */\n replayWindow?: number;\n /** Pluggable dedup store for replay protection (default: in-memory, process-local). */\n dedupStore?: WebhookDedupStore;\n}\n\nexport class TransactionWebhookHandler {\n private readonly handlers: HandlerMap = {\n 'tm.transaction.callback': [],\n 'tm.transaction.held': [],\n };\n private readonly secret: string;\n private readonly replayProtection: boolean;\n private readonly replayWindow: number;\n private readonly dedupStore: WebhookDedupStore;\n private readonly pendingWithdrawals = new Map<string, PendingWithdrawal>();\n private callbackHandler: TransactionCallbackHandler | null = null;\n\n constructor(config: TransactionWebhookHandlerConfig) {\n this.secret = config.secret;\n this.replayProtection = config.replayProtection ?? true;\n this.replayWindow = config.replayWindow ?? 300_000;\n this.dedupStore = config.dedupStore ?? new InMemoryDedupStore();\n }\n\n on(eventType: 'tm.transaction.callback', handler: EventHandler<TransactionCallbackEvent>): this;\n on(eventType: 'tm.transaction.held', handler: EventHandler<TransactionHeldEvent>): this;\n on(eventType: keyof HandlerMap, handler: EventHandler<TransactionCallbackEvent> | EventHandler<TransactionHeldEvent>): this {\n (this.handlers[eventType] as Array<typeof handler>).push(handler);\n return this;\n }\n\n /** Register a semantic callback handler for the three withholding outcomes. Replaces any previous handler. */\n registerCallbackHandler(handler: TransactionCallbackHandler): this {\n this.callbackHandler = handler;\n return this;\n }\n\n /** Track a PendingWithdrawal so it is auto-settled when its tm.transaction.callback arrives. */\n link(pending: PendingWithdrawal): this {\n this.pendingWithdrawals.set(pending.tx_id, pending);\n return this;\n }\n\n /** Stop tracking a PendingWithdrawal by tx_id. */\n unlink(tx_id: string): this {\n this.pendingWithdrawals.delete(tx_id);\n return this;\n }\n\n /** Verify HMAC-SHA256 signature only. Returns false instead of throwing. */\n async verify(rawBody: string, signature: string): Promise<boolean> {\n return verifyWebhookSignature(rawBody, signature, this.secret);\n }\n\n /** Parse and validate required fields. Does not verify signature — use verifyAndParse in production. */\n parse(body: string): TransactionWebhookEvent {\n const event = JSON.parse(body) as TransactionWebhookEvent;\n if (!event.event_type || !event.tx_id) {\n throw new ValidationError(\n 'Invalid TM webhook event: missing required fields (event_type, tx_id)',\n ['event_type', 'tx_id']\n );\n }\n return event;\n }\n\n /** Verify signature, parse, and check for replays. Throws ValidationError on any failure. */\n async verifyAndParse(body: string, signature: string): Promise<TransactionWebhookEvent> {\n const isValid = await verifyWebhookSignature(body, signature, this.secret);\n if (!isValid) {\n throw new ValidationError('Invalid webhook signature', ['signature']);\n }\n\n const event = this.parse(body);\n\n if (this.replayProtection) {\n const key = `${event.event_type}:${event.tx_id}`;\n if (await this.dedupStore.seen(key)) {\n throw new ValidationError(\n `Duplicate webhook event: ${event.event_type} for ${event.tx_id} has already been processed`,\n ['tx_id']\n );\n }\n await this.dedupStore.mark(key, this.replayWindow);\n }\n\n return event;\n }\n\n /** Verify signature, parse, and dispatch to registered handlers. */\n async handle(body: string, signature: string): Promise<void> {\n const event = await this.verifyAndParse(body, signature);\n\n if (event.event_type === 'tm.transaction.callback') {\n const pending = this.pendingWithdrawals.get(event.tx_id);\n if (pending) {\n pending._settle(event);\n this.pendingWithdrawals.delete(event.tx_id);\n }\n\n if (this.callbackHandler) {\n if (event.status === 'hold' || event.status === 'suspend') {\n await this.callbackHandler.onHeld?.(event);\n } else if (event.withholding_type === 'none') {\n await this.callbackHandler.onReleased?.(event);\n } else {\n await this.callbackHandler.onWithheld?.(event);\n }\n }\n }\n\n const handlers = this.handlers[event.event_type] as Array<EventHandler<typeof event>>;\n for (const handler of handlers) {\n await handler(event);\n }\n }\n}\n"]}
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","ValidationError"],"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 /**\n * A disabled trigger (e.g. `trigger_manual` off in tenant rules) returns 200 with\n * the customer's unchanged profile and no form sent — check `public_url` presence\n * or compare `form_status`/`tin_status` rather than trusting a 200 alone.\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, ValidationError, normalizePaymentType } 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\n/**\n * Ingestion requires an ISO 4217 style currency — exactly three ASCII letters,\n * the same shape the backend persists and the frontend's Intl.NumberFormat\n * accepts. Reject anything else (empty, a country code, or a social-casino\n * ticker like \"SC\"/\"GC\") up front so a malformed code can never reach the\n * ingestion endpoint and crash a downstream currency formatter. This is a\n * structural guard only — it does not rewrite the code; callers own any\n * business mapping (e.g. Sweeps Coins → USD) before submitting.\n */\nfunction assertWellFormedCurrency(currency: string): void {\n if (!/^[A-Za-z]{3}$/.test(currency ?? '')) {\n throw new ValidationError(\n `Invalid currency code: ${JSON.stringify(currency)}. Expected a 3-letter ISO 4217 code.`,\n ['currency']\n );\n }\n}\n\n/**\n * Fraud rules and payment-method reporting only recognize the standard payment\n * types, so `metadata.payment_method` is rewritten to its recognized form (for\n * example `bank_account` → `$electronic_fund_transfer`) and any other value is\n * rejected before it can reach ingestion.\n */\nfunction withRecognizedPaymentMethod(request: TransactionCreateDTO): TransactionCreateDTO {\n const paymentMethod = request.metadata?.payment_method;\n if (paymentMethod === undefined) return request;\n const paymentType =\n typeof paymentMethod === 'string' ? normalizePaymentType(paymentMethod) : undefined;\n if (paymentType === undefined) {\n throw new ValidationError(\n `Invalid payment method: ${JSON.stringify(paymentMethod)}. Expected a recognized payment type such as \"$credit_card\".`,\n ['metadata.payment_method']\n );\n }\n return { ...request, metadata: { ...request.metadata, payment_method: paymentType } };\n}\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 /**\n * Submit a transaction via the unified ingestion endpoint.\n *\n * POST /api/v1/transactions is gated on ANY of transaction monitoring, tax,\n * or fraud being enabled for the tenant, so fraud-only and tax-only tenants\n * can ingest even with TM switched off.\n */\n async createTransaction(\n request: TransactionCreateDTO,\n requestOptions?: RequestOptions\n ): Promise<TransactionCreateResponse> {\n assertWellFormedCurrency(request.currency);\n const body = withRecognizedPaymentMethod(request);\n try {\n return await this.requestWithRetry<TransactionCreateResponse>('/api/v1/transactions', {\n method: 'POST',\n body: JSON.stringify(body),\n }, undefined, undefined, requestOptions);\n } catch (error) {\n if (error instanceof VesantError && error.statusCode === 409) {\n const transaction = await this.getTransaction(request.tx_id, requestOptions);\n return { transaction, message: 'Transaction already exists' };\n }\n throw error;\n }\n }\n\n async submitWithdrawal(\n request: TransactionCreateDTO,\n requestOptions?: RequestOptions\n ): Promise<PendingWithdrawal> {\n const response = await this.createTransaction(\n { ...request, transaction_type: 'withdrawal' },\n requestOptions\n );\n return new PendingWithdrawal(response.transaction.tx_id, response.transaction);\n }\n\n async getTransaction(transactionId: string, requestOptions?: RequestOptions): Promise<Transaction> {\n return this.requestWithRetry<Transaction>(`/api/v1/tm/transactions/status/${transactionId}`, {\n method: 'GET',\n }, undefined, undefined, requestOptions);\n }\n\n async getSettings(): Promise<TmTenantSettings> {\n return this.requestWithRetry<TmTenantSettings>('/api/v1/tm/settings', {\n method: 'GET',\n });\n }\n}\n","import { verifyWebhookSignature, ValidationError, InMemoryDedupStore, type WebhookDedupStore } from '@vesant-sdk/core';\nimport { PendingWithdrawal } from './pending-withdrawal';\nimport type {\n TransactionCallbackEvent,\n TransactionCallbackHandler,\n TransactionHeldEvent,\n TransactionWebhookEvent,\n} from './types';\n\ntype EventHandler<T> = (event: T) => void | Promise<void>;\n\ntype HandlerMap = {\n 'tm.transaction.callback': Array<EventHandler<TransactionCallbackEvent>>;\n 'tm.transaction.held': Array<EventHandler<TransactionHeldEvent>>;\n};\n\nexport const TRANSACTION_SIGNATURE_HEADER = 'x-webhook-signature';\n\nexport interface TransactionWebhookHandlerConfig {\n secret: string;\n /** Reject duplicate event_type+tx_id pairs within replayWindow. Default: true. */\n replayProtection?: boolean;\n /** How long (ms) to remember seen events for replay detection. Default: 300_000 (5 min). */\n replayWindow?: number;\n /** Pluggable dedup store for replay protection (default: in-memory, process-local). */\n dedupStore?: WebhookDedupStore;\n}\n\nexport class TransactionWebhookHandler {\n private readonly handlers: HandlerMap = {\n 'tm.transaction.callback': [],\n 'tm.transaction.held': [],\n };\n private readonly secret: string;\n private readonly replayProtection: boolean;\n private readonly replayWindow: number;\n private readonly dedupStore: WebhookDedupStore;\n private readonly pendingWithdrawals = new Map<string, PendingWithdrawal>();\n private callbackHandler: TransactionCallbackHandler | null = null;\n\n constructor(config: TransactionWebhookHandlerConfig) {\n this.secret = config.secret;\n this.replayProtection = config.replayProtection ?? true;\n this.replayWindow = config.replayWindow ?? 300_000;\n this.dedupStore = config.dedupStore ?? new InMemoryDedupStore();\n }\n\n on(eventType: 'tm.transaction.callback', handler: EventHandler<TransactionCallbackEvent>): this;\n on(eventType: 'tm.transaction.held', handler: EventHandler<TransactionHeldEvent>): this;\n on(eventType: keyof HandlerMap, handler: EventHandler<TransactionCallbackEvent> | EventHandler<TransactionHeldEvent>): this {\n (this.handlers[eventType] as Array<typeof handler>).push(handler);\n return this;\n }\n\n /** Register a semantic callback handler for the three withholding outcomes. Replaces any previous handler. */\n registerCallbackHandler(handler: TransactionCallbackHandler): this {\n this.callbackHandler = handler;\n return this;\n }\n\n /** Track a PendingWithdrawal so it is auto-settled when its tm.transaction.callback arrives. */\n link(pending: PendingWithdrawal): this {\n this.pendingWithdrawals.set(pending.tx_id, pending);\n return this;\n }\n\n /** Stop tracking a PendingWithdrawal by tx_id. */\n unlink(tx_id: string): this {\n this.pendingWithdrawals.delete(tx_id);\n return this;\n }\n\n /** Verify HMAC-SHA256 signature only. Returns false instead of throwing. */\n async verify(rawBody: string, signature: string): Promise<boolean> {\n return verifyWebhookSignature(rawBody, signature, this.secret);\n }\n\n /** Parse and validate required fields. Does not verify signature — use verifyAndParse in production. */\n parse(body: string): TransactionWebhookEvent {\n const event = JSON.parse(body) as TransactionWebhookEvent;\n if (!event.event_type || !event.tx_id) {\n throw new ValidationError(\n 'Invalid TM webhook event: missing required fields (event_type, tx_id)',\n ['event_type', 'tx_id']\n );\n }\n return event;\n }\n\n /** Verify signature, parse, and check for replays. Throws ValidationError on any failure. */\n async verifyAndParse(body: string, signature: string): Promise<TransactionWebhookEvent> {\n const isValid = await verifyWebhookSignature(body, signature, this.secret);\n if (!isValid) {\n throw new ValidationError('Invalid webhook signature', ['signature']);\n }\n\n const event = this.parse(body);\n\n if (this.replayProtection) {\n const key = `${event.event_type}:${event.tx_id}`;\n if (await this.dedupStore.seen(key)) {\n throw new ValidationError(\n `Duplicate webhook event: ${event.event_type} for ${event.tx_id} has already been processed`,\n ['tx_id']\n );\n }\n await this.dedupStore.mark(key, this.replayWindow);\n }\n\n return event;\n }\n\n /** Verify signature, parse, and dispatch to registered handlers. */\n async handle(body: string, signature: string): Promise<void> {\n const event = await this.verifyAndParse(body, signature);\n\n if (event.event_type === 'tm.transaction.callback') {\n const pending = this.pendingWithdrawals.get(event.tx_id);\n if (pending) {\n pending._settle(event);\n this.pendingWithdrawals.delete(event.tx_id);\n }\n\n if (this.callbackHandler) {\n if (event.status === 'hold' || event.status === 'suspend') {\n await this.callbackHandler.onHeld?.(event);\n } else if (event.withholding_type === 'none') {\n await this.callbackHandler.onReleased?.(event);\n } else {\n await this.callbackHandler.onWithheld?.(event);\n }\n }\n }\n\n const handlers = this.handlers[event.event_type] as Array<EventHandler<typeof event>>;\n for (const handler of handlers) {\n await handler(event);\n }\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vesant-sdk/transaction",
3
- "version": "0.1.2-next.8d7fa63",
3
+ "version": "0.1.2-next.9770a24",
4
4
  "description": "Transaction monitoring and unified transaction ingestion client for the Vesant Compliance Platform",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -32,7 +32,7 @@
32
32
  "author": "Vesant Compliance",
33
33
  "license": "MIT",
34
34
  "dependencies": {
35
- "@vesant-sdk/core": "0.1.0"
35
+ "@vesant-sdk/core": "0.1.1-next.9770a24"
36
36
  },
37
37
  "devDependencies": {
38
38
  "msw": "^2.4.0",