import type { Wallet, WalletBalance, LedgerEntry, Operation, FxSnapshot, PaymentPage, CheckoutSession, CheckoutQuote, CheckoutPayment, PayoutRecipient, ScheduledPayout, ScheduledPayoutExecution, ScheduledPayoutStatus, ScheduledTransfer, ScheduledTransferExecution, ScheduledTransferStatus } from "./schemas.js"; import type { ID, OperationStatus, EntryStatus } from "./types.js"; export interface PageOptions { limit?: number; cursor?: string; /** * When true, include soft-deleted (archived) wallets in list results. * Defaults to false — archived wallets are excluded by default. */ includeArchived?: boolean; } export interface Page { items: T[]; nextCursor?: string; } /** * Injected by the consumer. All persistence logic lives here. * Implementations MUST enforce atomic balance-check + entry-write for debit operations. * RECOMMENDED strategy (used by Stripe, Wise, Monzo): * 1. Per-wallet serialized lock (DB row lock or pessimistic SELECT FOR UPDATE). * 2. Inside lock: read latest completed entry's runningBalance, check sufficiency for debit. * 3. Compute new entry's runningBalance and write atomically in same transaction. * This ensures O(1) balance reads with strong consistency and prevents lost updates. */ export interface StorageAdapter { createWallet(wallet: Wallet): Promise; getWallet(tenantId: ID, walletId: ID): Promise; listWalletsByOwner(tenantId: ID, ownerId: ID, ownerType: string, page?: PageOptions): Promise>; updateWallet(tenantId: ID, walletId: ID, patch: Partial): Promise; /** * Soft-delete a wallet by setting deletedAt. Returns the archived wallet. * MUST reject with WalletArchivalError if the wallet has any ledger entries. * MUST reject with WalletArchivedError if already archived. */ archiveWallet(tenantId: ID, walletId: ID): Promise; /** * Create multiple entries as part of a single atomic operation. * For debit entries, the adapter MUST check that the wallet has sufficient * available balance (from previous entry's runningBalance) before writing. * If the check fails, throw InsufficientFundsError. * * Each entry's runningBalance MUST be computed at write time: * - Sum all entries' impact (credits - debits) for this wallet. * - Add to the previous tail entry's runningBalance. * - Store this value immutably on the new entry. */ createEntries(tenantId: ID, entries: LedgerEntry[]): Promise; getEntry(tenantId: ID, entryId: ID): Promise; listEntriesByWallet(tenantId: ID, walletId: ID, page?: PageOptions, filter?: { /** Include only entries with effectiveAt <= this timestamp. Used for point-in-time balance queries (§14). */ beforeEffectiveAt?: Date; /** Include only entries after this sequence number (exclusive). */ afterSequence?: number; }): Promise>; updateEntryStatus(tenantId: ID, entryId: ID, status: EntryStatus): Promise; createOperation(operation: Operation): Promise; getOperation(tenantId: ID, operationId: ID): Promise; getOperationByIdempotencyKey(tenantId: ID, idempotencyKey: string): Promise; getOperationByExternalId(tenantId: ID, externalId: string, provider: string): Promise; updateOperationStatus(tenantId: ID, operationId: ID, status: OperationStatus, patch?: Partial>): Promise; listOperationsByWallet(tenantId: ID, walletId: ID, page?: PageOptions): Promise>; /** * Cross-wallet operation listing for a tenant. * Used by pay.operations.list() (§26). Requires authorizer check at the * domain level — the adapter does NOT enforce authorization. */ listOperations(tenantId: ID, page?: PageOptions, filter?: { walletId?: string; /** * Return operations where the given wallet appears on **either** side * (i.e. `walletId === anyWalletId` OR `counterpartyWalletId === anyWalletId`). * Use this to build a complete transaction history for a wallet, including * incoming transfers and conversion credits where the wallet is the destination. * Mutually exclusive with `walletId` — if both are set, `anyWalletId` takes precedence. */ anyWalletId?: string; /** * Return operations where **any** of the given wallets appear on either side. * Equivalent to `anyWalletId` but for multiple wallets simultaneously. * Use when building an aggregated transaction history across all of a user's wallets. * Takes precedence over both `walletId` and `anyWalletId`. */ anyWalletIds?: string[]; /** Filter by one or more operation types. */ types?: string[]; /** Filter by one or more operation statuses. */ statuses?: string[]; /** Filter by payment provider name. */ provider?: string; /** Lower bound on operation effectiveAt (inclusive). */ fromEffectiveAt?: Date; /** Upper bound on operation effectiveAt (inclusive). */ toEffectiveAt?: Date; /** Exact idempotency key match. */ idempotencyKey?: string; /** Filter by payout recipient ID (D30). */ recipientId?: string; /** * Return only operations whose `pendingExpiresAt` is before this date. * Used by `expireOverdue()` to find expired pending operations. */ pendingExpiredBefore?: Date; }): Promise>; /** * Merge arbitrary key/value pairs into an operation's metadata object. * Used to persist system-generated markers (e.g. requiresManualResolution) * without changing the operation's status. */ mergeOperationMetadata(tenantId: ID, operationId: ID, patch: Record): Promise; /** * Persist a new PayoutRecipient record. * Storage MUST enforce a unique constraint on (tenantId, provider, providerRecipientCode). * Duplicate insertion MUST throw a storage-level conflict error. */ createPayoutRecipient(recipient: PayoutRecipient): Promise; getPayoutRecipient(tenantId: ID, recipientId: ID): Promise; updatePayoutRecipient(tenantId: ID, recipientId: ID, patch: Partial): Promise; listPayoutRecipients(tenantId: ID, page?: PageOptions, filter?: { provider?: string; currency?: string; ownerId?: string; ownerType?: string; /** When true, include archived recipients. Defaults to false. */ includeArchived?: boolean; }): Promise>; /** * Soft-delete a PayoutRecipient by setting archivedAt. Returns the archived record. * Archived recipients cannot be used for new payouts. */ archivePayoutRecipient(tenantId: ID, recipientId: ID): Promise; /** * Return the current balance snapshot for a wallet. * OPTIMIZED implementation: read the latest `completed` entry for this wallet, * extract its `runningBalance`, and construct the WalletBalance object from it. * This is O(1) with strong consistency — no eventual consistency window. */ getBalance(tenantId: ID, walletId: ID): Promise; /** * Return the balance snapshot for a wallet as of a specific point in time (§14). * Replays completed ledger entries with effectiveAt <= `at` to derive the balance. * Adapters MUST sort entries by (effectiveAt ASC, sequence ASC) before replaying. */ getBalanceAt(tenantId: ID, walletId: ID, at: Date): Promise; createFxSnapshot(snapshot: FxSnapshot): Promise; getFxSnapshot(tenantId: ID, snapshotId: ID): Promise; createPaymentPage(page: PaymentPage): Promise; getPaymentPage(tenantId: ID, pageId: ID): Promise; updatePaymentPage(tenantId: ID, pageId: ID, patch: Partial): Promise; listPaymentPages(tenantId: ID, page?: PageOptions): Promise>; createCheckoutSession(session: CheckoutSession): Promise; getCheckoutSession(tenantId: ID, sessionId: ID): Promise; /** Lookup a session by the pre-computed payerRefHash for a given page. */ findCheckoutSessionByPayerRef(tenantId: ID, pageId: ID, payerRefHash: string): Promise; updateCheckoutSession(tenantId: ID, sessionId: ID, patch: Partial): Promise; listCheckoutSessionsByPage(tenantId: ID, pageId: ID, page?: PageOptions): Promise>; createCheckoutQuote(quote: CheckoutQuote): Promise; getCheckoutQuote(tenantId: ID, quoteId: ID): Promise; updateCheckoutQuote(tenantId: ID, quoteId: ID, patch: Partial): Promise; createCheckoutPayment(payment: CheckoutPayment): Promise; getCheckoutPayment(tenantId: ID, paymentId: ID): Promise; /** Lookup a CheckoutPayment by providerRef for webhook idempotency. */ findCheckoutPaymentByProviderRef(tenantId: ID, providerRef: string): Promise; updateCheckoutPayment(tenantId: ID, paymentId: ID, patch: Partial): Promise; listCheckoutPaymentsBySession(tenantId: ID, sessionId: ID, page?: PageOptions): Promise>; createScheduledPayout(schedule: ScheduledPayout): Promise; getScheduledPayout(tenantId: ID, scheduleId: ID): Promise; updateScheduledPayout(tenantId: ID, scheduleId: ID, patch: Partial): Promise; listScheduledPayouts(tenantId: ID, page?: PageOptions, filter?: { walletId?: string; recipientId?: string; status?: ScheduledPayoutStatus; }): Promise>; /** * Return all active schedules whose nextFireAt <= now. * Used by executeDueSchedules() to find what needs to fire. */ listDueScheduledPayouts(tenantId: ID, now: Date): Promise; /** * Atomic claim: transition status "active" → "firing" for a specific schedule * only if it is still active and nextFireAt <= now. * Returns the claimed record or null if another instance claimed it first. */ claimScheduledPayout(tenantId: ID, scheduleId: ID, now: Date): Promise; createScheduledPayoutExecution(exec: ScheduledPayoutExecution): Promise; listScheduledPayoutExecutions(tenantId: ID, scheduleId: ID, page?: PageOptions): Promise>; createScheduledTransfer(schedule: ScheduledTransfer): Promise; getScheduledTransfer(tenantId: ID, scheduleId: ID): Promise; updateScheduledTransfer(tenantId: ID, scheduleId: ID, patch: Partial): Promise; listScheduledTransfers(tenantId: ID, page?: PageOptions, filter?: { fromWalletId?: string; status?: ScheduledTransferStatus; }): Promise>; /** * Return all active scheduled transfers whose nextFireAt <= now. */ listDueScheduledTransfers(tenantId: ID, now: Date): Promise; /** * Atomic claim: transition status "active" → "firing" for a specific * scheduled transfer only if it is still active and nextFireAt <= now. * Returns the claimed record or null if another instance claimed it first. */ claimScheduledTransfer(tenantId: ID, scheduleId: ID, now: Date): Promise; createScheduledTransferExecution(exec: ScheduledTransferExecution): Promise; listScheduledTransferExecutions(tenantId: ID, scheduleId: ID, page?: PageOptions): Promise>; } /** * Internal extension of StorageAdapter that includes getReserved and * createTransitWallet — internal-only operations that must not be exposed * on the public StorageAdapter surface. */ export interface StorageAdapterInternal extends StorageAdapter { /** * Returns the sum of all pending debit entry amounts for the wallet. * Called exclusively inside createEntries under the wallet-level lock. */ getReserved(tenantId: ID, walletId: ID): Promise; /** * Create a transit wallet owned by a checkout session. * Uses upsert semantics: if a transit wallet for (tenantId, sessionId, currency) * already exists, the existing wallet is returned without error. * IC-4: Unique constraint on (tenantId, ownerId, ownerType='checkout_session', currency). */ createTransitWallet(wallet: Wallet): Promise; } export interface CheckoutInitInput { tenantId: ID; operationId: ID; amountMinor: number; currency: string; callbackUrl: string; customerEmail?: string; customerName?: string; customerPhone?: string; /** * Platform-assigned identifier for the individual payer (e.g. the investor's auth user ID). * Used as `customer.user_id` in provider payloads. Falls back to `tenantId` when omitted. */ customerUserId?: string; metadata?: Record; } export interface CheckoutInitResult { checkoutUrl: string; providerReference: string; expiresAt?: Date; raw?: unknown; } export interface CustomerInput { email?: string; name?: string; phone?: string; /** Platform-assigned identifier for this individual customer (e.g. auth user ID). Maps to `CheckoutInitInput.customerUserId`. */ userId?: string; metadata?: Record; } export interface CheckoutQuoteResponse { quoteId: string; payerCurrency: string; payerAmountMinor: number; settlementAmountMinor: number; rate: string; fxProvider: string; expiresAt: Date; } /** * Result of a synchronous wallet payment via sessions.pay({ quoteId, walletId }). * Returned instead of CheckoutInitResult when the payer supplies a walletId. */ export interface CheckoutWalletPayResult { paymentId: string; /** The transfer or conversion operation id that settled the funds. */ operationId: string; status: "completed"; /** session.receivedAmountMinor after this payment settled. */ receivedAmountMinor: number; fulfillmentStatus?: "under" | "exact" | "over"; } export interface CheckoutSessionState { sessionId: string; pageId: string; payerRef?: Record; status: "active" | "closing" | "closed" | "expired" | "settled"; expiresAt: Date; settlementOperationIds?: string[]; receivedAmountMinor: number; receivedCurrency: string; remainingAmountMinor?: number; fulfillmentStatus?: "under" | "exact" | "over"; activeQuote?: CheckoutQuoteResponse; payments: Array<{ paymentId: string; providerRef?: string; payerCurrency: string; payerAmountMinor: number; settlementAmountMinor: number; status: "pending" | "completed" | "failed"; flaggedForReview?: boolean; receivedAt: Date; }>; closedAt?: Date; requiresManualResolution?: boolean; metadata?: Record; } /** * Registration-time configuration for a payment provider adapter. * The webhookSecret is stored internally by the adapter at construction time * and MUST NOT be passed at call time (FIX-14). */ export interface PaymentProviderAdapterConfig { /** * Webhook signing secret for this provider. * Used internally by the adapter's verifyWebhook() implementation. * Never passes through the call-time WebhookVerifyInput. */ webhookSecret: string; /** * Optional logger instance. When provided, the adapter emits structured * log events for every outbound provider API call and inbound webhook event. * Use the same `PayLogger` instance passed to `PayClientConfig.logger` so * all events flow through a single logger. */ logger?: PayLogger; } export interface WebhookVerifyInput { rawBody: string | Buffer; headers: Record; } export interface WebhookEvent { externalId: string; status: "completed" | "failed" | "pending"; amountMinor: number; currency: string; /** * Provider's canonical charge identifier — suitable for calling * `verifyTransaction()`. Adapters should populate this when the provider * uses a separate charge ID distinct from the operation reference. */ chargeId?: string; /** * Payment processing fee charged by the provider, in minor units. * Populated when the provider discloses the fee in the webhook payload. */ fees?: number; /** * Cumulative total received across all payments for this charge, in minor units. * Set by adapters that support multi-payment sessions (e.g. 100Pay). * * When this value is present and `status` is `"completed"`, the webhook domain * automatically passes it as `amountOverride` to `operations.settle()`. This * ensures the wallet is credited with the actual total received rather than the * quoted amount — critical for overpaid charges where the provider settles * every payment individually to the developer's wallet. * * For `status: "pending"` (underpaid) events this value is stored in * `operation.metadata._pay_totalPaid` for use by `expireOverdue()`. */ totalPaidMinor?: number; /** * True when the provider reports a partial payment. * * @deprecated With multi-payment session support, underpaid events now use * `status: "pending"` and `totalPaidMinor` instead. This flag is preserved * for backwards compatibility with adapters that set it, but 100Pay no longer * emits it for underpaid events. */ partial?: boolean; metadata?: Record; raw: unknown; } /** * Normalised result returned by `verifyTransaction()`. Adapters map their * provider-specific response to this common shape. */ export interface VerifyTransactionResult { status: "completed" | "pending" | "failed"; amountMinor: number; currency: string; /** The operation reference / external ID for this transaction. */ externalId: string; /** * Payment processing fee charged by the provider, in minor units. * Populated when the provider discloses the fee in the verify response. */ fees?: number; raw: unknown; } export type CheckoutMode = "hosted" | "embedded"; export interface CreateRecipientInput { tenantId: ID; currency: string; /** Provider-specific account details (account number, bank code, etc.). */ details: Record; metadata?: Record; } export interface CreateRecipientResult { /** Provider-assigned recipient code used in subsequent initPayout calls. */ providerRecipientCode: string; /** Account holder name as resolved by the provider. */ resolvedName?: string; raw?: unknown; } export interface ResolveAccountInput { tenantId: ID; currency: string; /** Provider-specific account details (account number, bank code, etc.). */ details: Record; } export interface ResolveAccountResult { /** Account holder name as resolved/confirmed by the provider. */ resolvedName: string; /** Whether the account was verified successfully. */ verified: boolean; raw?: unknown; } export interface InitPayoutInput { tenantId: ID; operationId: ID; /** Provider-assigned recipient code from createRecipient. */ providerRecipientCode: string; /** * Full `details` object from the PayoutRecipient record. * Adapters use this to access provider-specific fields (bankCode, payId, * address, payoutType, symbol, walletType, etc.) without re-fetching storage. */ recipientDetails?: Record; amountMinor: number; currency: string; /** Human-readable transfer description passed to the bank/provider. */ narration?: string; /** Caller-supplied payment reference for idempotency on the provider side. */ paymentReference?: string; /** * Provider callback/webhook URL for async settlement notification. * Used by providers that require the callback URL on the transfer request * (e.g. Flutterwave) rather than relying solely on dashboard configuration. */ callbackUrl?: string; metadata?: Record; } export interface InitPayoutResult { /** Provider-assigned transfer reference (stored as operation.externalId). */ providerReference: string; /** * Optional: if the provider settles synchronously (rare), set to "completed". * Core will auto-settle the operation if present. */ status?: "pending" | "completed" | "failed"; raw?: unknown; } /** * Normalised payout webhook event returned by verifyPayoutWebhook(). */ export interface PayoutWebhookEvent { /** Matches operation.externalId (the providerReference from initPayout). */ providerReference: string; /** * The payment reference originally sent to the provider during initPayout. * For most providers this equals the operationId. When present, processWebhook * will use this to look up the operation directly (race-free), bypassing the * externalId index which may not yet be written at webhook arrival time. */ paymentReference?: string; status: "completed" | "failed" | "pending"; /** Provider-reported fees in minor units. */ fees?: number; feesCurrency?: string; /** Human-readable failure reason (for failed payouts). */ failureReason?: string; raw: unknown; } /** * Normalised result returned by verifyPayout() (poll-verify). */ export interface VerifyPayoutResult { /** Matches operation.externalId. */ providerReference: string; status: "completed" | "failed" | "pending"; fees?: number; feesCurrency?: string; failureReason?: string; raw: unknown; } /** * Provider adapters live in separate packages (`@untools/pay-paystack`, etc.) * and are registered at `createPayClient()` time. */ export interface PaymentProviderAdapter { readonly name: string; /** * Optional capability check for checkout modes. When omitted, consumers * should assume only hosted mode is supported. */ supportsCheckoutMode?(mode: CheckoutMode): boolean; initCheckout(input: CheckoutInitInput): Promise; verifyWebhook(input: WebhookVerifyInput): Promise; /** * Poll-verify a single transaction by its provider reference. Optional — * webhooks are the primary settlement signal. When present, the reference * format is provider-specific (e.g. chargeId for 100Pay, reference string * for Paystack, tx_ref for Flutterwave). */ verifyTransaction?(reference: string): Promise; /** * Fetch a provider webhook event by its event document ID. Optional — * used by providers that support server-side proactive event fetching (e.g. 100Pay). * * The returned {@link WebhookEvent} has the same shape as one produced by * {@link verifyWebhook} and can be passed directly to * `webhooks.handleWithEvent({ event })` to skip signature re-verification. * * For 100Pay, this calls `POST /api/v1/pay/crypto/payment/:eventId` using the * secret key — the event id is the MongoDB `_id` of the webhook event document, * emitted by `@100pay-hq/checkout` via `onPayment(eventId)`. */ fetchEventById?(eventId: string): Promise; /** * Declare payout support. When absent or returns false, `pay.payouts.send()` * throws PayoutProviderNotSupportedError for this provider. */ supportsPayout?(): boolean; /** * Declare recipient registration support. When absent or returns false, * `pay.payouts.createRecipient()` throws PayoutProviderNotSupportedError. */ supportsRecipientRegistration?(): boolean; /** * Register a recipient with the provider. Returns the provider-assigned * recipient code and optional resolved name. */ createRecipient?(input: CreateRecipientInput): Promise; /** * Verify a bank account / mobile-money number without registering a recipient. * Used by `pay.payouts.resolveAccount()`. Optional — throws * OperationNotSupportedError if absent. */ resolveAccount?(input: ResolveAccountInput): Promise; /** * Initiate a payout transfer to a pre-registered recipient. * Required if supportsPayout() returns true. */ initPayout?(input: InitPayoutInput): Promise; /** * Verify a payout webhook event from the provider. * Required if supportsPayout() returns true. * Returns a normalised PayoutWebhookEvent. */ verifyPayoutWebhook?(input: WebhookVerifyInput): Promise; /** * Poll-verify a single payout by its provider reference. Optional. * Used by `pay.payouts.verifyPayout()` to fetch latest payout status. */ verifyPayout?(reference: string): Promise; } export interface FetchRateInput { fromCurrency: string; toCurrency: string; } export interface FetchRateResult { rate: string; provider: string; fetchedAt: Date; expiresAt?: Date; } export interface RateProvider { fetchRate(input: FetchRateInput): Promise; } export interface FeePolicyInput { operationType: string; amountMinor: number; currency: string; walletId: ID; tenantId: ID; metadata?: Record; } export interface FeePolicyResult { feeAmountMinor: number; feeCurrency: string; feeWalletId?: ID; } export interface FeePolicy { compute(input: FeePolicyInput): Promise; } export interface AuthorizerInput { tenantId: ID; /** The action being authorized (e.g. 'deposit', 'withdrawal', 'wallet.create'). */ action: string; walletId: ID; /** Destination wallet for crossUserTransfer and transferWithConversion operations. */ counterpartyWalletId?: ID; amountMinor: number; currency: string; metadata?: Record; } export interface Authorizer { authorize(input: AuthorizerInput): Promise; } export interface PayLogger { info(msg: string, ctx?: Record): void; warn(msg: string, ctx?: Record): void; error(msg: string, ctx?: Record): void; debug(msg: string, ctx?: Record): void; } export interface PayHooks { onOperationCreated?(operation: Operation): Promise | void; onOperationStatusChanged?(operation: Operation, prevStatus: string): Promise | void; onEntryCreated?(entry: LedgerEntry): Promise | void; /** Fired after a PayoutRecipient is successfully registered. */ onPayoutRecipientCreated?(recipient: PayoutRecipient): Promise | void; /** Fired after a payout operation is successfully dispatched to the provider. */ onPayoutDispatched?(operation: Operation, providerReference: string): Promise | void; } export interface RecipientIdentifierInput { type: "owner" | "email" | "username" | "paytag"; /** Value is required for email/username/paytag; ownerId+ownerType for type=owner */ value?: string; ownerId?: string; ownerType?: string; } export interface ResolvedRecipient { ownerId: string; ownerType: string; canonicalIdentifier?: { type: "email" | "username" | "paytag"; value: string; }; } /** * Result of a single identifier resolution in a resolveMany batch. */ export interface ResolvedIdentifier { /** The original identifier input that was resolved. */ input: RecipientIdentifierInput; ownerId: string; ownerType: string; canonicalIdentifier?: { type: "email" | "username" | "paytag"; value: string; }; } /** * Optional pluggable interface for resolving recipient owners from * identity-based input (email, username, paytag). * * Uses a single resolveMany() call to avoid N+1 resolution — all identifiers * are resolved in one round-trip (FIX-05). The resolver must throw * RecipientNotFoundError or RecipientAmbiguousError for any identifier that * cannot be uniquely resolved. * * If not configured, callers must supply toWalletId or a pre-resolved owner tuple. */ export interface RecipientResolver { resolveMany(input: { tenantId: string; identifiers: RecipientIdentifierInput[]; }): Promise; } /** * Controls how far in the past or future an entry's effectiveAt may be set * relative to the current wall-clock time (§15, FIX-04). * * Violations throw InvalidEffectiveAtError. If an authorizer is provided, * out-of-window dates may be permitted when the authorizer approves the * 'backdated.effectiveAt' action — allowing operator-level overrides without * relaxing the default policy for all callers. */ export interface EffectiveAtPolicy { /** Maximum milliseconds a date may be in the past. Default: 7 days. */ maxBackdateMs?: number; /** Maximum milliseconds a date may be in the future. Default: 0 (no future dates). */ maxFutureDateMs?: number; /** * Optional authorizer for out-of-window dates. * When provided, dates outside the policy window are sent to the authorizer * with action 'backdated.effectiveAt'. If the authorizer approves, the entry * is created. If it rejects, InvalidEffectiveAtError is thrown. */ authorizer?: Authorizer; } /** Identifies the owner of a wallet. Used in TransferWithConversionPlan and composite hook contexts. */ export interface OwnerTuple { ownerId: string; ownerType: string; } export interface TransferWithConversionPlan { logicalTransferId: string; sender: OwnerTuple; recipient: OwnerTuple; leg1: { fromWalletId: string; toWalletId: string; amountMinor: number; fromCurrency: string; toCurrency: string; fxSnapshotId?: string; }; leg2: { fromWalletId: string; toWalletId: string; amountMinor: number; currency: string; }; atomicityMode: "strict" | "best-effort"; } export interface TransferWithConversionHooks { onTransferWithConversionStarted?(context: { logicalTransferId: string; tenantId: string; plan: TransferWithConversionPlan; }): Promise | void; onTransferWithConversionSettled?(context: { logicalTransferId: string; tenantId: string; conversionOperationId: string; transferOperationId: string; /** Sum of fees charged across both legs (§46.20). */ totalFeeAmountMinor: number; }): Promise | void; onTransferWithConversionFailed?(context: { logicalTransferId: string; tenantId: string; error: unknown; reversalOutcome?: "reversed" | "reversal_failed" | "not_applicable"; }): Promise | void; } /** * A single rule in a FinalityPolicy, configuring the finality mode and optional * reversal window for one operation type (§36). */ export interface FinalityPolicyRule { type: "deposit" | "withdrawal" | "transfer" | "conversion" | "payment" | "fee" | "reversal" | "adjustment"; /** 'hard' means the operation can never be reversed once completed. */ mode: "soft" | "hard"; /** * Milliseconds after completedAt during which reversal is permitted. * Only meaningful when mode is 'soft'. Omit for unlimited window. */ reversalWindowMs?: number; } /** * Global finality and reversal-window policy injected at createPayClient() time (§36). * * Default rule set (applies when no override is configured): * deposit soft 86_400_000 ms (24h) * transfer soft 86_400_000 ms (24h) * payment soft 86_400_000 ms (24h) * conversion soft 300_000 ms (5m) * withdrawal hard * fee hard * reversal hard * adjustment hard */ export interface FinalityPolicy { rules: FinalityPolicyRule[]; } //# sourceMappingURL=interfaces.d.ts.map