import { Schema, Querier } from '@platform-modules/db';
import { AppendEntryInput, AppendEntryResult } from '@platform-modules/ledger';

declare class RefundExceedsPaidError extends Error {
    readonly context: {
        chargeKey: string;
        amount: number;
    };
    readonly name = "RefundExceedsPaidError";
    readonly code: "REFUND_EXCEEDS_PAID";
    readonly httpStatus: 422;
    constructor(message: string, context: {
        chargeKey: string;
        amount: number;
    });
}
/**
 * `settleCharge` found a durable `pending` charge intent already owning this
 * chargeKey — a concurrent in-flight attempt or a prior crash (floor #6). This
 * call does NOT charge again (a re-charge risks a double-charge: provider dedup
 * windows may be short). Recovery is `reconcileCharge`, driven by the
 * host's `listUnsettledIntents` sweep. 409 = the in-flight conflict.
 */
declare class ChargeIntentPendingError extends Error {
    readonly chargeKey: string;
    readonly name = "ChargeIntentPendingError";
    readonly code: "CHARGE_INTENT_PENDING";
    readonly httpStatus: 409;
    constructor(chargeKey: string);
}
declare class WebhookVerificationError extends Error {
    readonly name = "WebhookVerificationError";
    readonly code: "WEBHOOK_VERIFICATION_FAILED";
    readonly httpStatus: 400;
    constructor(message: string);
}
declare class InvalidAmountError extends Error {
    readonly context: {
        value: number | string;
        field?: string;
    };
    readonly name = "InvalidAmountError";
    readonly code: "INVALID_AMOUNT";
    readonly httpStatus: 422;
    constructor(message: string, context: {
        value: number | string;
        field?: string;
    });
}
declare class RefundFailedError extends Error {
    readonly context: {
        chargeKey: string;
        providerRef: string;
        status: string;
    };
    readonly name = "RefundFailedError";
    readonly code: "REFUND_FAILED";
    readonly httpStatus: 502;
    constructor(context: {
        chargeKey: string;
        providerRef: string;
        status: string;
    });
}

/**
 * `@platform-modules/billing` — thin money-seam (provider axis + webhook ingest + settle/refund funnels).
 */

type ChargeRequest = {
    chargeKey: string;
    amount: number;
    currency: string;
    metadata?: Record<string, string>;
};
type ChargeResult = {
    kind: 'requires_client_action';
    chargeKey: string;
    providerRef: string;
    clientSecret: string;
} | {
    kind: 'settled';
    chargeKey: string;
    providerRef: string;
    amount: number;
    currency: string;
    documentUrls?: string[];
};
type RefundRequest = {
    refundKey: string;
    chargeKey: string;
    refundId: string;
    providerChargeId?: string;
    amount: number;
};
type RefundResult = {
    kind: 'refunded';
    refundKey: string;
    chargeKey: string;
    providerRef: string;
    amount: number;
    currency: string;
} | {
    kind: 'pending';
};
type RefundReconciliationRequest = {
    providerChargeId: string;
    amountMinor: bigint;
    currency: string;
    refundKey: string;
};
type RefundReconciliationResult = {
    kind: 'confirmed';
    providerRefundId: string;
    amountMinor: bigint;
    currency: string;
} | {
    kind: 'pending_or_unknown';
} | {
    kind: 'definite_failure';
    code: string;
};
type ProviderEvent = {
    eventId: string;
    kind: 'settlement';
    chargeKey: string;
    providerRef: string;
    amount: number;
    currency: string;
    documentUrls?: string[];
    subscriptionId?: string;
    period?: string;
    amountMinor?: bigint;
} | {
    eventId: string;
    kind: 'refund';
    refundKey: string;
    chargeKey: string;
    providerChargeId: string;
    providerRef: string;
    amount: number;
    currency: string;
} | {
    eventId: string;
    kind: 'other';
    raw: unknown;
};
interface PaymentProvider {
    readonly provider: string;
    readonly emitsInvoiceOnCharge: boolean;
    charge(req: ChargeRequest): Promise<ChargeResult>;
    refund(req: RefundRequest): Promise<RefundResult>;
    reconcileRefund(req: RefundReconciliationRequest): Promise<RefundReconciliationResult>;
    parseWebhook(raw: string, headers: Headers): Promise<ProviderEvent>;
}
/**
 * Host-implemented webhook event dedup. NOT a have-I-seen-it boolean — a
 * single-statement atomic single-winner claim over the provider event id.
 *
 * SQL recipe (host-owned table, billing-owned contract):
 * ```sql
 * INSERT INTO webhook_events (event_id, claimed_at)
 * VALUES ($1, now())
 * ON CONFLICT (event_id) DO UPDATE SET claimed_at = now()
 *   WHERE webhook_events.processed_at IS NULL
 *     AND (webhook_events.claimed_at IS NULL
 *          OR webhook_events.claimed_at < now() - INTERVAL '5 min')
 * RETURNING event_id;
 * ```
 * Empty `RETURNING` ⇒ `'lost'`. `markProcessed` sets `processed_at` ONLY after
 * all side-effects (module post + host dispatch) succeed.
 *
 * TTL corollary (host contract): the winner's post + dispatch MUST complete well
 * within the claim reclaim TTL — a `dispatch` running longer than the window lets
 * a concurrent redelivery win a FRESH claim and double-fire dispatch; keep dispatch
 * fast (enqueue to jobs/outbox, never do slow work inline), or widen the window
 * past worst-case dispatch latency.
 */
interface DedupStore {
    claim(eventId: string): Promise<'won' | 'lost'>;
    markProcessed(eventId: string): Promise<void>;
}
/**
 * Host-implemented durable charge-intent record that closes the sync
 * charge→ledger window (spec floor #6 rewrite). Without it `settleCharge` ran
 * `provider.charge()` then the ledger post — a crash in between left the card
 * charged with NO durable record (the MONEY-001 silent charge-without-record,
 * money.md M1). The ledger entry cannot serve as the pending marker: floor #4
 * makes a ledger entry MEAN received money, and it lands only AFTER the charge.
 *
 * The fix mirrors `DedupStore` — the host owns the TABLE, billing owns the
 * CONTRACT + SQL recipe + TTL. A durable `pending` intent is written BEFORE the
 * provider call, so a crash leaves a RECOVERABLE intent, never a silent charge.
 *
 * SQL recipe (host-owned table, billing-owned contract):
 * ```sql
 * -- claimIntent: single-statement atomic single-winner over charge_key.
 * INSERT INTO charge_intents (charge_key, amount, currency, status, claimed_at)
 * VALUES ($1, $2, $3, 'pending', now())
 * ON CONFLICT (charge_key) DO NOTHING
 * RETURNING status;
 * --   non-empty RETURNING ('pending') ⇒ 'won' (this caller inserted it).
 * --   empty RETURNING ⇒ a row already exists → SELECT its status:
 * --     status='settled' ⇒ return 'settled' (M2 resolution short-circuit, no-op).
 * --     status='pending' ⇒ return 'pending' (a concurrent in-flight intent OR a
 * --                        prior crash) — do NOT charge again; hand to reconcile.
 * ```
 *
 * RECOVERY DISCRIMINATOR vs `DedupStore`: a duplicate WEBHOOK self-heals because
 * the provider RE-DELIVERS it. A crashed inline CHARGE has nothing re-driving it,
 * so it needs an ACTIVE sweep that ENUMERATES unresolved intents —
 * `listUnsettledIntents`, the method `DedupStore` deliberately lacks. The host
 * crons it and calls `reconcileCharge` on each stale `pending` intent.
 */
interface IntentStore {
    /**
     * Phase-1 claim. `'won'` ⇒ proceed to charge. `'settled'` ⇒ a resolution
     * already exists ⇒ caller short-circuits (idempotent re-entry). `'pending'`
     * ⇒ a concurrent/crashed in-flight intent ⇒ do NOT charge again.
     */
    claimIntent(chargeKey: string, intent: {
        amount: number;
        currency: string;
    }): Promise<'won' | 'pending' | 'settled'>;
    /** Persist the provider id the instant `charge()` returns → M4 local settle from stored ref. */
    recordProviderRef(chargeKey: string, providerRef: string): Promise<void>;
    /** Flip to settled AFTER `confirmSettlement` posts → `claimIntent` returns `'settled'` on replay. */
    markIntentSettled(chargeKey: string): Promise<void>;
    /** The ACTIVE-sweep enumerator (the discriminator vs `DedupStore`). */
    listUnsettledIntents(olderThanMs: number): Promise<Array<{
        chargeKey: string;
        providerRef?: string;
        amount: number;
        currency: string;
    }>>;
}
type LedgerSeam = {
    appendEntry: <S extends Schema = Schema>(tx: Querier<S>, input: AppendEntryInput) => Promise<AppendEntryResult>;
};
type LedgerDbBag<S extends Schema = Schema> = {
    ledger: LedgerSeam;
    db: Querier<S>;
};
/**
 * Deterministic idempotency key — stable parts only; never Date.now/random/UUID.
 * Each part is percent-encoded before joining so a host-supplied part containing
 * the `:` separator cannot collide two distinct part-vectors onto one key
 * (e.g. `['refund','ord','1:r']` vs `['refund','ord:1','r']`) — that collision
 * would silently no-op the second `appendEntry` (`inserted:false`) and overstate
 * the ledger. Encoding keeps `join(':')` injective over arbitrary parts.
 */
declare function idempotencyKey(parts: readonly string[]): string;
/** Integer minor-units → bigint (M7). Unit normalization is the adapter's job. */
declare function toMinorUnits(amount: number): bigint;
type SettlementInput = {
    amount: number;
    currency: string;
    providerRef?: string;
    documentUrls?: string[];
};
declare function confirmSettlement<S extends Schema>(chargeKey: string, settlement: SettlementInput, { ledger, db }: LedgerDbBag<S>): Promise<void>;
type RefundConfirmInput = {
    chargeKey: string;
    amount: number;
    currency: string;
    providerRef?: string;
};
declare function confirmRefund<S extends Schema>(refundKey: string, refund: RefundConfirmInput, { ledger, db }: LedgerDbBag<S>): Promise<void>;
type SettleChargeDeps<S extends Schema = Schema> = LedgerDbBag<S> & {
    provider: PaymentProvider;
    /** Required (floor #6): the durable charge-intent claim that closes the charge→ledger window. */
    intentStore: IntentStore;
};
/**
 * M1/M2/M4 three-phase charge: a durable intent is claimed BEFORE the provider
 * call, so a crash between charge and ledger-post leaves a RECOVERABLE `pending`
 * intent rather than a silent charge-without-record (spec floor #6).
 *
 * Order: claimIntent → provider.charge → recordProviderRef → confirmSettlement
 * → markIntentSettled.
 *
 * A `'settled'` claim is the idempotent replay short-circuit — the charge was
 * already settled, so this returns without re-calling the provider (never a
 * double-charge). A `'pending'` claim means a concurrent in-flight attempt OR a
 * prior crash already owns this chargeKey; this call does NOT charge again —
 * recovery is `reconcileCharge`, driven by the host's `listUnsettledIntents`
 * sweep. The provider is called ONLY on a fresh `'won'` claim.
 */
declare function settleCharge<S extends Schema>(req: ChargeRequest, { provider, ledger, db, intentStore }: SettleChargeDeps<S>): Promise<ChargeResult>;
/**
 * The host's read of a charge's prior outcome, by chargeKey. Mirrors the
 * `resolvePaymentIntentId` creds-resolver: billing needs a provider fact it does
 * not hold (did the charge actually go through, and for how much), so the host —
 * which owns the order record AND the provider read — supplies it. Returns `null`
 * when the outcome is not yet knowable (leave the intent `pending` for a later
 * sweep). `reconcileCharge` NEVER re-calls `provider.charge` (M4: settle from the
 * stored/resolved ref, zero provider charge calls — re-charge is unsafe past the
 * provider's dedup window).
 */
type ResolveChargeOutcome = (chargeKey: string) => Promise<{
    settled: boolean;
    providerRef: string;
    amount: number;
    currency: string;
} | null>;
type ReconcileChargeDeps<S extends Schema = Schema> = LedgerDbBag<S> & {
    intentStore: IntentStore;
    resolveChargeOutcome: ResolveChargeOutcome;
};
/**
 * Recovery for ONE stale `pending` charge intent (the host crons
 * `listUnsettledIntents` and calls this per entry). Reads the prior outcome via
 * the host resolver; if the charge settled, posts the idempotent
 * `confirmSettlement` and flips the intent — never re-charging. An unsettled /
 * unknown (`null`) outcome leaves the intent `pending` for the next sweep.
 * Returns the action taken (for host observability).
 */
declare function reconcileCharge<S extends Schema>(chargeKey: string, { intentStore, resolveChargeOutcome, ledger, db }: ReconcileChargeDeps<S>): Promise<'settled' | 'unresolved'>;
type RefundChargeDeps<S extends Schema = Schema> = LedgerDbBag<S> & {
    provider: PaymentProvider;
};
declare function refundCharge<S extends Schema>(req: RefundRequest, { provider, ledger, db }: RefundChargeDeps<S>): Promise<RefundResult>;
/**
 * Host-implemented side-effects for a won, non-duplicate webhook event
 * (fulfilment / status / notify). HOST PRECONDITION — `dispatch` MUST be
 * IDEMPOTENT keyed by `event.eventId`: a `markProcessed` failure leaves the
 * claim's `processed_at` NULL, so a post-TTL redelivery re-wins the claim and
 * re-fires `dispatch`. The "keep dispatch fast within the TTL window"
 * (`DedupStore`) contract covers only the CONCURRENT-redelivery double-fire; the
 * post-TTL re-fire is covered ONLY by eventId-keyed dispatch idempotency (enqueue
 * to jobs/outbox keyed on `event.eventId`, never do non-idempotent work inline).
 */
type WebhookDispatch = (event: ProviderEvent) => Promise<void>;
type SettlementPreflight = (event: Extract<ProviderEvent, {
    kind: 'settlement';
}>) => Promise<'accept' | 'reject'>;
type IngestWebhookDeps<S extends Schema = Schema> = LedgerDbBag<S> & {
    provider: PaymentProvider;
    dedupStore: DedupStore;
    dispatch: WebhookDispatch;
    settlementPreflight?: SettlementPreflight;
};
declare function ingestWebhook<S extends Schema>(req: Request, { provider, dedupStore, dispatch, ledger, db, settlementPreflight }: IngestWebhookDeps<S>): Promise<Response>;
type BillingProviderName = 'stripe';
type ProviderFactory = (creds: unknown) => PaymentProvider;
type ProviderFactories = Partial<Record<BillingProviderName, ProviderFactory>>;
declare function setProviderFactories(factories: ProviderFactories): void;
declare function getProvider(name: string, creds: unknown): PaymentProvider;

export { type BillingProviderName, ChargeIntentPendingError, type ChargeRequest, type ChargeResult, type DedupStore, type IngestWebhookDeps, type IntentStore, InvalidAmountError, type LedgerDbBag, type LedgerSeam, type PaymentProvider, type ProviderEvent, type ProviderFactories, type ProviderFactory, type ReconcileChargeDeps, type RefundChargeDeps, type RefundConfirmInput, RefundExceedsPaidError, RefundFailedError, type RefundReconciliationRequest, type RefundReconciliationResult, type RefundRequest, type RefundResult, type ResolveChargeOutcome, type SettleChargeDeps, type SettlementInput, type SettlementPreflight, type WebhookDispatch, WebhookVerificationError, confirmRefund, confirmSettlement, getProvider, idempotencyKey, ingestWebhook, reconcileCharge, refundCharge, setProviderFactories, settleCharge, toMinorUnits };
