import type { StorageAdapter, PayLogger, Authorizer, FeePolicy, RateProvider, RecipientIdentifierInput, RecipientResolver, TransferWithConversionHooks, PageOptions, Page } from "../interfaces.js"; import type { ID, RoundingMode } from "../types.js"; import type { Operation, ScheduledTransfer, ScheduledTransferExecution, ScheduledTransferStatus } from "../schemas.js"; import type { OperationsDomain } from "./operations.js"; export interface CrossUserTransferInput { /** Source wallet owned by the sender. */ fromWalletId: ID; amountMinor: number; currency: string; /** * Fast path: directly supply the destination wallet id. * When present, recipient identity resolution is skipped. */ toWalletId?: ID; /** * Identity-based resolution path. * Required when toWalletId is not supplied. */ recipient?: { identifiers: RecipientIdentifierInput[]; /** Defaults to transfer currency. */ walletCurrency?: string; }; idempotencyKey?: string; finalityMode?: "soft" | "hard"; metadata?: Record; /** Per-request policy overrides. */ policy?: { autoCreateDestinationWallet?: boolean; }; } export interface TransferWithConversionInput { /** Source wallet in fromCurrency owned by the sender. */ fromWalletId: ID; amountMinor: number; fromCurrency: string; toCurrency: string; /** * Fast path: sender's pre-existing bridge wallet in toCurrency. * When absent, core resolves or creates it per bridgeWalletPolicy. */ bridgeWalletId?: ID; /** * Fast path: directly supply the destination wallet id. * When present, recipient identity resolution is skipped. */ toWalletId?: ID; /** * Identity-based resolution path. */ recipient?: { identifiers: RecipientIdentifierInput[]; }; /** Pre-fetched FX snapshot id. When absent, core fetches a live rate. */ fxSnapshotId?: ID; idempotencyKey?: string; finalityMode?: "soft" | "hard"; metadata?: Record; /** Per-request policy overrides. */ policy?: { autoCreateDestinationWallet?: boolean; autoCreateBridgeWallet?: boolean; atomicityMode?: "strict" | "best-effort"; }; } /** Result of a {@link TransfersDomain.withConversion} operation. */ export interface TransferWithConversionResult { /** Shared identifier linking both legs of this logical transfer. */ logicalTransferId: string; /** The `conversion` operation (leg 1 — debit source, credit bridge wallet). */ conversionOperation: Operation; /** The `transfer` operation (leg 2 — debit bridge wallet, credit destination). */ transferOperation: Operation; /** * Set to `'best-effort'` when the caller supplied `finalityMode: 'hard'` * and the resolved atomicityMode was demoted. Inspect this field to confirm * the mode that was actually applied. */ effectiveAtomicityMode?: "best-effort"; } export interface CreateScheduledTransferInput { fromWalletId: ID; /** Fast-path destination wallet. Takes precedence over `recipient` when both are present. */ toWalletId?: ID; /** * Identity-based destination resolution. Resolved at each fire via the * configured recipientResolver. Used when toWalletId is absent. */ recipient?: { identifiers: RecipientIdentifierInput[]; walletCurrency?: string; }; amountMinor: number; fromCurrency: string; /** When provided and different from fromCurrency, fires withConversion. */ toCurrency?: string; narration?: string; /** One-time: fire once at this timestamp. Mutually exclusive with cronExpression. */ scheduledAt?: Date; /** Recurring: 5-field cron expression. Mutually exclusive with scheduledAt. */ cronExpression?: string; /** Auto-cancel when now >= endsAt on a fire tick. */ endsAt?: Date; onFailure?: "pause" | "continue"; /** * Pre-computed nextFireAt. Required for recurring schedules when cronParser * is not configured. Ignored for one-time (scheduledAt) schedules. */ nextFireAt?: Date; metadata?: Record; } export interface UpdateScheduledTransferInput { narration?: string; cronExpression?: string; endsAt?: Date; onFailure?: "pause" | "continue"; nextFireAt?: Date; status?: ScheduledTransferStatus; } /** Result of executeDueTransferSchedules(). */ export interface ExecuteDueTransferSchedulesResult { fired: number; succeeded: number; failed: number; failures: Array<{ scheduleId: string; error: string; }>; } /** High-level cross-user transfer operations (same-currency and cross-currency). */ export interface TransfersDomain { /** * Transfer funds between two users in the same currency. * * Supports a direct wallet-id fast path (`toWalletId`) or automatic recipient * identity resolution via the configured {@link RecipientResolver}. * * @param input - Transfer parameters including source wallet and recipient. * @returns The completed `transfer` operation. * @throws {WalletNotFoundError} if the source wallet does not exist. * @throws {WalletArchivedError} if either wallet is archived. * @throws {RecipientNotFoundError} if identity resolution finds no matching owner. * @throws {RecipientAmbiguousError} if an identifier matches multiple owners. * @throws {DestinationWalletUnavailableError} if the recipient has no wallet in the transfer currency and auto-create is disabled. * @throws {SelfTransferNotAllowedError} if source and destination wallet are the same. * @throws {CrossTenantTransferNotAllowedError} if the destination wallet belongs to a different tenant. * @throws {InsufficientFundsError} if the source wallet has insufficient available balance. * @throws {IdempotencyConflictError} if the key was already used with different parameters. * * @example * ```ts * const op = await pay.transfers.crossUser({ * fromWalletId: senderWallet.id, * amountMinor: 50000, * currency: "NGN", * recipient: { * identifiers: [{ type: "email", value: "alice@example.com" }], * }, * }); * ``` */ crossUser(input: CrossUserTransferInput): Promise; /** * Transfer funds across currencies in a two-leg atomic operation. * * **Leg 1 (conversion):** Debit sender's source wallet → credit sender's bridge wallet * in the target currency, using a locked FX rate. * * **Leg 2 (transfer):** Debit sender's bridge wallet → credit recipient's destination wallet. * * If leg 2 fails, leg 1 is automatically reversed. If leg 1 reversal also fails, the * operation enters `manual_resolution` status — use {@link OperationsDomain.resolveManualResolution} to recover. * * @param input - Conversion-transfer parameters. * @returns Result object containing both leg operations and the effective atomicity mode. * @throws {WalletNotFoundError} if any involved wallet does not exist. * @throws {FxRateUnavailableError} if no rate provider is configured or the snapshot is expired. * @throws {RecipientNotFoundError} if identity resolution fails. * @throws {DestinationWalletUnavailableError} if the recipient has no wallet in `toCurrency`. * @throws {InsufficientFundsError} if the source wallet has insufficient balance. * @throws {ManualResolutionRequiredError} if a prior attempt is stuck in manual-resolution state. * @throws {TransferWithConversionPartialFailureError} if leg 2 fails and leg 1 reversal also fails. * @throws {IdempotencyConflictError} if the key was already used with different parameters. * * @example * ```ts * const result = await pay.transfers.withConversion({ * fromWalletId: ngnWallet.id, * amountMinor: 100000, * fromCurrency: "NGN", * toCurrency: "USD", * recipient: { * identifiers: [{ type: "email", value: "bob@example.com" }], * }, * }); * ``` */ withConversion(input: TransferWithConversionInput): Promise; createTransferSchedule(input: CreateScheduledTransferInput): Promise; getTransferSchedule(scheduleId: ID): Promise; listTransferSchedules(page?: PageOptions, filter?: { fromWalletId?: string; status?: ScheduledTransferStatus; }): Promise>; updateTransferSchedule(scheduleId: ID, patch: UpdateScheduledTransferInput): Promise; pauseTransferSchedule(scheduleId: ID): Promise; resumeTransferSchedule(scheduleId: ID): Promise; cancelTransferSchedule(scheduleId: ID): Promise; executeDueTransferSchedules(): Promise; listTransferScheduleExecutions(scheduleId: ID, page?: PageOptions): Promise>; } export interface TransfersDomainOptions { tenantId: ID; storage: StorageAdapter; operations: OperationsDomain; rateProvider?: RateProvider; logger?: PayLogger; authorizer?: Authorizer; feePolicy?: FeePolicy; hooks?: TransferWithConversionHooks; recipientResolver?: RecipientResolver; policy?: { autoCreateDestinationWallet?: boolean; autoCreateBridgeWallet?: boolean; atomicityMode?: "strict" | "best-effort"; }; roundingMode?: RoundingMode; /** Optional cron parser for recurring transfer schedules. */ cronParser?: (expression: string, after: Date) => Date; } export declare function createTransfersDomain(opts: TransfersDomainOptions): TransfersDomain; //# sourceMappingURL=transfers.d.ts.map