@fin-integrity/node 0.1.0
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/LICENSE +21 -0
- package/README.md +162 -0
- package/dist/index.cjs +476 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +269 -0
- package/dist/index.d.ts +269 -0
- package/dist/index.js +443 -0
- package/dist/index.js.map +1 -0
- package/package.json +68 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/environment.ts","../src/errors.ts","../src/idempotency.ts","../src/transport.ts","../src/client.ts","../src/stripe.ts"],"sourcesContent":["import { FinIntegrityClient } from \"./client.js\";\nimport type { FinIntegrityConfig } from \"./types.js\";\n\nexport { FinIntegrityClient } from \"./client.js\";\nexport { instrumentStripe } from \"./stripe.js\";\nexport { FinIntegrityError, ConfigError } from \"./errors.js\";\n// Reaches onError whenever a 2xx carries per-event rejections — callers can't\n// branch on an error they have no way to import. Python exports it; parity.\nexport { RejectedEventsError, type RejectedEvent } from \"./transport.js\";\nexport type {\n FinIntegrityConfig,\n RecordInput,\n PayoutInput,\n SubscriptionInput,\n EventEnvelope,\n Money,\n Side,\n EventType,\n WireEventType,\n Direction,\n Transport,\n} from \"./types.js\";\n\nlet current: FinIntegrityClient | undefined;\n\n/** Create and configure the client. Returns it (also stored as the module singleton). */\nexport function init(config?: FinIntegrityConfig): FinIntegrityClient {\n current = new FinIntegrityClient(config);\n return current;\n}\n\n/** The client from the most recent init() — convenient in scripts. */\nexport function getClient(): FinIntegrityClient {\n if (!current) throw new Error(\"fin-integrity: call init() before getClient()\");\n return current;\n}\n","// Sentry-style environment tags, validated the same way the ingest server does\n// (kept in sync by hand — the SDK has no shared dependency on the backend):\n// - trimmed; empty is treated as \"not provided\"\n// - max 64 chars\n// - no whitespace (incl. newlines) or forward slashes\n// - not the literal \"none\" (case-insensitive)\n// Case is otherwise preserved (environments are case-sensitive).\n\nconst MAX_LEN = 64;\nconst INVALID = /[\\s/]/;\n\n/** Returns the cleaned environment, or undefined if absent/invalid (the caller\n * falls back to its default, and ultimately the server defaults to \"production\"). */\nexport function cleanEnvironment(raw: unknown): string | undefined {\n if (typeof raw !== \"string\") return undefined;\n const v = raw.trim();\n if (!v || v.length > MAX_LEN || INVALID.test(v) || v.toLowerCase() === \"none\") return undefined;\n return v;\n}\n","export class FinIntegrityError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"FinIntegrityError\";\n }\n}\n\n/** Thrown at init() for invalid configuration (fail fast). Runtime capture never throws. */\nexport class ConfigError extends FinIntegrityError {\n constructor(message: string) {\n super(message);\n this.name = \"ConfigError\";\n }\n}\n","import { createHash, randomUUID } from \"node:crypto\";\nimport type { EventEnvelope } from \"./types.js\";\n\ntype KeyBasis = Pick<EventEnvelope, \"source\" | \"side\" | \"external_id\" | \"event_type\"> &\n Partial<Pick<EventEnvelope, \"status\" | \"current_period_end\" | \"arrival_at\" | \"environment\">> & {\n amount?: { minor: string };\n };\n\n/**\n * Deterministic content-hash key so a client crash/retry of the *same* underlying\n * fact collapses to one row.\n *\n * The basis is identity + observed state. Identity alone is not enough: the\n * server dedupes on this key and drops the event before it can update anything,\n * so any field that legitimately changes over an entity's life must be in here\n * or the update is silently lost. That bites the states that matter most — a\n * dispute going needs_response -> lost (only `lost` is money out), a\n * subscription renewing into its next period, a payout going pending -> paid.\n *\n * Retry safety is preserved: same fact in the same state = same key = collapsed.\n * A real change produces a new key, reaches the server, and upserts the row.\n */\nexport function deterministicKey(e: KeyBasis): string {\n const basis = [\n e.source,\n e.side,\n e.external_id,\n e.event_type,\n // Environment is part of identity: the same external_id in staging vs\n // production are distinct facts that must not collapse to one row.\n e.environment ?? \"\",\n // Mutable state. Absent fields collapse to \"\" so unused ones cost nothing.\n e.status ?? \"\",\n e.amount?.minor ?? \"\",\n e.current_period_end ?? \"\",\n e.arrival_at ?? \"\",\n ].join(\":\");\n return \"fi_\" + createHash(\"sha256\").update(basis).digest(\"hex\").slice(0, 40);\n}\n\nexport function uuidKey(): string {\n return \"fi_\" + randomUUID();\n}\n","import { FinIntegrityError } from \"./errors.js\";\nimport type { EventEnvelope, Transport } from \"./types.js\";\n\nexport interface HttpTransportOptions {\n endpoint: string;\n apiKey: string;\n retries: number;\n debug: boolean;\n}\n\n/** Batched HTTP transport. Retries 5xx/429/network with backoff; honors Retry-After. */\nexport class HttpTransport implements Transport {\n constructor(private readonly opts: HttpTransportOptions) {}\n\n async send(batch: EventEnvelope[], meta: { dropped: number }): Promise<void> {\n const url = this.opts.endpoint.replace(/\\/+$/, \"\") + \"/v1/events\";\n const body = JSON.stringify({ sent_at: new Date().toISOString(), dropped: meta.dropped, events: batch });\n\n for (let attempt = 0; ; attempt++) {\n let res: Response;\n try {\n res = await fetch(url, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n authorization: `Bearer ${this.opts.apiKey}`,\n \"idempotency-key\": batch[0]?.idempotency_key ?? \"\",\n },\n body,\n });\n } catch (netErr) {\n if (attempt >= this.opts.retries) throw netErr;\n await sleep(backoff(attempt));\n continue;\n }\n\n if (res.ok) {\n // A 200 means the batch was received, NOT that every event was stored:\n // ingest validates per event and reports rejects in the body. Treating\n // 200 as total success hides dropped money events behind a success log.\n const rejected = await rejectedFrom(res);\n if (rejected.length > 0) {\n throw new RejectedEventsError(rejected, batch.length);\n }\n if (this.opts.debug) console.log(`[fin-integrity] delivered ${batch.length} event(s)`);\n return;\n }\n if (res.status === 429 || res.status >= 500) {\n if (attempt >= this.opts.retries) throw new Error(`fin-integrity ingest ${res.status}`);\n const ra = Number(res.headers.get(\"retry-after\"));\n await sleep(Number.isFinite(ra) && ra > 0 ? ra * 1000 : backoff(attempt));\n continue;\n }\n // 4xx — terminal, do not retry\n throw new Error(`fin-integrity ingest ${res.status}: ${await safeText(res)}`);\n }\n }\n}\n\n/** In-memory transport for dryRun and tests. */\nexport class MemoryTransport implements Transport {\n sent: EventEnvelope[] = [];\n async send(batch: EventEnvelope[]): Promise<void> {\n this.sent.push(...batch);\n }\n}\n\nfunction backoff(n: number): number {\n return Math.min(1000 * 2 ** n, 15000) + Math.random() * 250;\n}\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n/** Per-event rejections inside an HTTP 200. Surfaced via onError, never thrown to the caller. */\nexport class RejectedEventsError extends FinIntegrityError {\n constructor(\n readonly rejected: RejectedEvent[],\n readonly batchSize: number,\n ) {\n const detail = rejected.map((r) => `${r.event_id}: ${r.error}`).join(\"; \");\n super(`fin-integrity: ingest rejected ${rejected.length}/${batchSize} event(s) — ${detail}`);\n this.name = \"RejectedEventsError\";\n }\n}\n\nexport interface RejectedEvent {\n event_id: string;\n error: string;\n}\n\n/** Rejected entries from a 200 body. An unparseable body means nothing to report. */\nasync function rejectedFrom(res: Response): Promise<RejectedEvent[]> {\n try {\n const body = (await res.clone().json()) as {\n results?: Array<{ event_id?: string; status?: string; error?: string }>;\n };\n if (!Array.isArray(body?.results)) return [];\n return body.results\n .filter((r) => r?.status === \"rejected\")\n .map((r) => ({ event_id: r.event_id ?? \"unknown\", error: r.error ?? \"unknown error\" }));\n } catch {\n return [];\n }\n}\n\nasync function safeText(res: Response): Promise<string> {\n try {\n return await res.text();\n } catch {\n return \"\";\n }\n}\n","import { cleanEnvironment } from \"./environment.js\";\nimport { ConfigError, FinIntegrityError } from \"./errors.js\";\nimport { deterministicKey, uuidKey } from \"./idempotency.js\";\nimport { HttpTransport, MemoryTransport } from \"./transport.js\";\nimport type { EventEnvelope, FinIntegrityConfig, PayoutInput, RecordInput, Side, SubscriptionInput, Transport } from \"./types.js\";\n\nconst DEFAULT_ENDPOINT = \"https://ingest.fin-integrity.com\";\n\nexport class FinIntegrityClient {\n private readonly apiKey: string;\n private readonly endpoint: string;\n private readonly idempotencyMode: \"deterministic\" | \"uuid\";\n private readonly maxSize: number;\n private readonly flushMs: number;\n private readonly maxQueueSize: number;\n private readonly sampleRate: number;\n private readonly environment?: string;\n private readonly beforeSend?: FinIntegrityConfig[\"beforeSend\"];\n private readonly debug: boolean;\n private readonly onError: (err: unknown) => void;\n private readonly transport: Transport;\n private readonly memory?: MemoryTransport;\n\n private queue: EventEnvelope[] = [];\n private dropped = 0;\n private timer?: ReturnType<typeof setInterval>;\n\n /** Capture money-movement observed from a payment processor (Stripe, Adyen, bank, …). */\n readonly processor: {\n record: (input: RecordInput) => void;\n recordPayout: (input: PayoutInput) => void;\n recordSubscription: (input: SubscriptionInput) => void;\n };\n /** Capture entries from your own internal ledger / books. */\n readonly ledger: { record: (input: RecordInput) => void };\n\n constructor(config: FinIntegrityConfig = {}) {\n const dryRun = config.dryRun ?? false;\n const apiKey = config.apiKey ?? process.env.FIN_INTEGRITY_KEY;\n if (!apiKey && !dryRun && !config.transport) {\n throw new ConfigError(\n \"fin-integrity: apiKey is required (pass config.apiKey or set FIN_INTEGRITY_KEY). Use { dryRun: true } to test without a key.\",\n );\n }\n this.apiKey = apiKey ?? \"dry-run\";\n this.endpoint = config.endpoint ?? process.env.FIN_INTEGRITY_ENDPOINT ?? DEFAULT_ENDPOINT;\n this.idempotencyMode = config.idempotency ?? \"deterministic\";\n this.maxSize = config.batch?.maxSize ?? 50;\n this.flushMs = config.batch?.flushMs ?? 2000;\n this.maxQueueSize = config.maxQueueSize ?? 1000;\n this.sampleRate = config.sampleRate ?? 1.0;\n this.environment = cleanEnvironment(config.environment ?? process.env.NODE_ENV);\n this.beforeSend = config.beforeSend;\n this.debug = config.debug ?? false;\n this.onError = config.onError ?? ((e) => { if (this.debug) console.warn(\"[fin-integrity]\", e); });\n\n if (config.transport) {\n this.transport = config.transport;\n } else if (dryRun) {\n this.memory = new MemoryTransport();\n this.transport = this.memory;\n } else {\n this.transport = new HttpTransport({\n endpoint: this.endpoint,\n apiKey: this.apiKey,\n retries: config.retries ?? 3,\n debug: this.debug,\n });\n }\n\n this.processor = {\n record: (input) => this.record(\"processor\", input),\n recordPayout: (input) => this.recordPayout(input),\n recordSubscription: (input) => this.recordSubscription(input),\n };\n this.ledger = { record: (input) => this.record(\"ledger\", input) };\n\n this.timer = setInterval(() => void this.flush(), this.flushMs);\n if (typeof this.timer.unref === \"function\") this.timer.unref();\n\n process.once(\"beforeExit\", () => void this.flush());\n process.once(\"SIGTERM\", () => this.onSigterm());\n }\n\n /**\n * Drain on SIGTERM, then hand the process back its normal fate.\n *\n * Node disables its default terminate-on-SIGTERM as soon as ANY listener is\n * registered, so simply attaching one made this library silently decide that\n * SIGTERM no longer kills the host process — a bare script would ignore\n * `docker stop` and hang until SIGKILL, which is also when the queued events\n * we were trying to protect get dropped anyway.\n *\n * So: flush, then exit ourselves — but only if nothing else is listening. If\n * the app registered its own handler it owns the shutdown sequence, and a\n * library must not exit out from under it. `once` has already removed our own\n * listener by the time this runs, so any remaining count is the app's.\n */\n private onSigterm(): void {\n const appHandlesIt = process.listenerCount(\"SIGTERM\") > 0;\n void this.flush().finally(() => {\n if (!appHandlesIt) process.exit(143); // 128 + SIGTERM(15), the conventional code\n });\n }\n\n /** Low-level escape hatch: capture a fully-specified event (both adapters use this). */\n capture(input: RecordInput & { side: Side }): void {\n this.record(input.side, input);\n }\n\n private record(side: Side, input: RecordInput): void {\n try {\n const env: EventEnvelope = {\n schema_version: \"1.0\",\n event_id: uuidKey(),\n idempotency_key: \"\",\n side,\n source: input.source ?? (side === \"ledger\" ? \"ledger.internal\" : \"custom\"),\n event_type: input.type,\n reference: input.reference,\n external_id: input.external_id,\n amount: {\n minor: toMinorString(input.amount.minor),\n currency: input.amount.currency.toLowerCase(),\n ...(input.amount.exponent != null ? { exponent: input.amount.exponent } : {}),\n },\n ...(input.fee != null\n ? { fee: { minor: toMinorString(input.fee.minor), currency: input.fee.currency.toLowerCase() } }\n : {}),\n ...(input.traceId != null ? { trace_id: input.traceId } : {}),\n ...(input.payoutId != null ? { payout_id: input.payoutId } : {}),\n ...(input.subscriptionId != null ? { subscription_id: input.subscriptionId } : {}),\n ...(input.parentExternalId != null ? { parent_external_id: input.parentExternalId } : {}),\n occurred_at: toIso(input.occurred_at),\n captured_at: new Date().toISOString(),\n ...(input.status != null ? { status: input.status } : {}),\n ...(input.direction != null ? { direction: input.direction } : {}),\n ...this.envFields(input.environment),\n ...(input.metadata != null ? { metadata: input.metadata } : {}),\n };\n env.idempotency_key = this.idempotencyMode === \"uuid\" ? uuidKey() : deterministicKey(env);\n this.enqueue(env);\n } catch (err) {\n this.onError(err); // fail-open — never throw into the caller\n }\n }\n\n /**\n * Capture a recurring billing container. Not money movement — this is what a\n * charge is expected to arrive in, which is what lets reconciliation catch a\n * billing period that produced no charge at all.\n *\n * Send it whenever the subscription changes (created, renewed, status change)\n * so `currentPeriodEnd` stays current.\n */\n private recordSubscription(input: SubscriptionInput): void {\n try {\n const env: EventEnvelope = {\n schema_version: \"1.0\",\n event_id: uuidKey(),\n idempotency_key: \"\",\n side: \"processor\",\n source: input.source ?? \"custom\",\n event_type: \"subscription\",\n reference: input.external_id,\n external_id: input.external_id,\n amount: {\n minor: toMinorString(input.amount.minor),\n currency: input.amount.currency.toLowerCase(),\n ...(input.amount.exponent != null ? { exponent: input.amount.exponent } : {}),\n },\n status: input.status,\n ...(input.interval != null ? { interval: input.interval } : {}),\n ...(input.currentPeriodStart != null ? { current_period_start: toIso(input.currentPeriodStart) } : {}),\n ...(input.currentPeriodEnd != null ? { current_period_end: toIso(input.currentPeriodEnd) } : {}),\n ...(input.traceId != null ? { trace_id: input.traceId } : {}),\n occurred_at: toIso(input.occurred_at),\n captured_at: new Date().toISOString(),\n ...this.envFields(input.environment),\n ...(input.metadata != null ? { metadata: input.metadata } : {}),\n };\n env.idempotency_key = this.idempotencyMode === \"uuid\" ? uuidKey() : deterministicKey(env);\n this.enqueue(env);\n } catch (err) {\n this.onError(err); // fail-open\n }\n }\n\n /** Capture a processor payout (processor → bank). Stored separately; links to\n * transactions via their payoutId. */\n private recordPayout(input: PayoutInput): void {\n try {\n const env: EventEnvelope = {\n schema_version: \"1.0\",\n event_id: uuidKey(),\n idempotency_key: \"\",\n side: \"processor\",\n source: input.source ?? \"custom\",\n event_type: \"payout\",\n reference: input.external_id,\n external_id: input.external_id,\n amount: {\n minor: toMinorString(input.amount.minor),\n currency: input.amount.currency.toLowerCase(),\n ...(input.amount.exponent != null ? { exponent: input.amount.exponent } : {}),\n },\n ...(input.traceId != null ? { trace_id: input.traceId } : {}),\n ...(input.arrivalAt != null ? { arrival_at: toIso(input.arrivalAt) } : {}),\n occurred_at: toIso(input.occurred_at),\n captured_at: new Date().toISOString(),\n ...(input.status != null ? { status: input.status } : {}),\n ...this.envFields(input.environment),\n ...(input.metadata != null ? { metadata: input.metadata } : {}),\n };\n env.idempotency_key = this.idempotencyMode === \"uuid\" ? uuidKey() : deterministicKey(env);\n this.enqueue(env);\n } catch (err) {\n this.onError(err); // fail-open\n }\n }\n\n /** Per-event override wins over the client default; an invalid value falls back\n * to the default (and, if that's absent too, the server defaults to production). */\n private envFields(perEvent?: string): { environment?: string } {\n const environment = cleanEnvironment(perEvent) ?? this.environment;\n return environment != null ? { environment } : {};\n }\n\n private enqueue(env: EventEnvelope): void {\n if (this.sampleRate < 1 && Math.random() > this.sampleRate) return;\n let out: EventEnvelope | null | undefined = env;\n if (this.beforeSend) out = this.beforeSend(env);\n if (!out) return;\n if (this.queue.length >= this.maxQueueSize) {\n this.queue.shift(); // drop-oldest\n this.dropped++;\n }\n this.queue.push(out);\n if (this.queue.length >= this.maxSize) void this.flush();\n }\n\n /** Send all queued events now. Safe to await (e.g. in a serverless `finally`). */\n async flush(): Promise<void> {\n if (this.queue.length === 0) return;\n const batch = this.queue.splice(0, this.queue.length);\n const dropped = this.dropped;\n this.dropped = 0;\n try {\n await this.transport.send(batch, { dropped });\n } catch (err) {\n this.onError(err); // fail-open: batch is lost, SDK stays alive\n }\n }\n\n /** Drain and stop the client (call before a long-lived process exits). */\n async shutdown(): Promise<void> {\n if (this.timer) clearInterval(this.timer);\n await this.flush();\n }\n\n /** Envelopes captured so far (dryRun / MemoryTransport only). Great for tests. */\n inspect(): EventEnvelope[] {\n return this.memory ? [...this.memory.sent, ...this.queue] : [...this.queue];\n }\n}\n\nfunction toMinorString(m: number | bigint): string {\n if (typeof m === \"bigint\") return m.toString();\n if (!Number.isInteger(m)) {\n throw new FinIntegrityError(`amount.minor must be an integer in minor units, got ${m}`);\n }\n return String(m);\n}\nfunction toIso(v?: string | Date): string {\n if (!v) return new Date().toISOString();\n return v instanceof Date ? v.toISOString() : v;\n}\n","import type { FinIntegrityClient } from \"./client.js\";\nimport type { EventType } from \"./types.js\";\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\n/**\n * Wrap the Stripe client you pass in so `charges.create`, `paymentIntents.create`,\n * and `refunds.create` are captured automatically as fin-integrity processor events.\n *\n * We wrap the exact instance you hand us — no global monkey-patching — so it's\n * predictable and ESM/bundler-safe. Capture runs off the hot path and never blocks\n * or alters your Stripe call. Returns the same (now instrumented) client.\n */\nexport function instrumentStripe<T>(stripe: T, fi: FinIntegrityClient): T {\n const s = stripe as any;\n wrap(s?.charges, \"create\", fi, \"payment\");\n wrap(s?.paymentIntents, \"create\", fi, \"payment\");\n wrap(s?.refunds, \"create\", fi, \"refund\");\n return stripe;\n}\n\nfunction wrap(resource: any, method: string, fi: FinIntegrityClient, type: EventType): void {\n if (!resource || typeof resource[method] !== \"function\") return;\n const original = resource[method];\n if (original.__fiWrapped) return; // idempotent — never double-wrap\n\n const wrapped = async function (this: any, ...args: any[]) {\n const result = await original.apply(this, args); // never block/alter the real call\n queueMicrotask(() => {\n try {\n captureStripe(fi, type, result);\n } catch {\n /* swallowed — must never break the Stripe call path */\n }\n });\n return result;\n };\n wrapped.__fiWrapped = true;\n resource[method] = wrapped;\n}\n\nfunction captureStripe(fi: FinIntegrityClient, type: EventType, obj: any): void {\n if (!obj || typeof obj !== \"object\" || typeof obj.id !== \"string\") return;\n const amount = type === \"refund\" ? obj.amount : obj.amount_received ?? obj.amount;\n const reference =\n obj.metadata?.reference ??\n obj.metadata?.order_id ??\n obj.metadata?.reconciliation_id ??\n (type === \"refund\" ? obj.charge ?? obj.payment_intent ?? obj.id : obj.id);\n\n fi.capture({\n side: \"processor\",\n type,\n source: \"stripe\",\n reference: String(reference),\n external_id: obj.id,\n amount: { minor: typeof amount === \"number\" ? amount : 0, currency: String(obj.currency ?? \"\") },\n ...(obj.status ? { status: String(obj.status) } : {}),\n ...(typeof obj.created === \"number\" ? { occurred_at: new Date(obj.created * 1000) } : {}),\n metadata: { stripe_object: obj.object },\n });\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
type Side = "processor" | "ledger";
|
|
2
|
+
/** Money movement. A dispute is money leaving against a charge — same shape as a
|
|
3
|
+
* refund, so it reconciles through the same path. */
|
|
4
|
+
type EventType = "payment" | "refund" | "dispute";
|
|
5
|
+
/** Wire-level event types. `payout` and `subscription` are processor-only and
|
|
6
|
+
* land in their own tables. */
|
|
7
|
+
type WireEventType = EventType | "payout" | "subscription";
|
|
8
|
+
type Direction = "credit" | "debit";
|
|
9
|
+
/** Money as an integer count of the currency's minor units + ISO-4217 code. Never a float. */
|
|
10
|
+
interface Money {
|
|
11
|
+
/** Integer minor units (e.g. 4999 = $49.99). Accepts number or bigint; serialized as a string. */
|
|
12
|
+
minor: number | bigint;
|
|
13
|
+
/** ISO-4217 currency code, lowercased (e.g. "usd", "jpy"). */
|
|
14
|
+
currency: string;
|
|
15
|
+
/** Optional minor-unit exponent for zero/three-decimal or custom currencies. */
|
|
16
|
+
exponent?: number;
|
|
17
|
+
}
|
|
18
|
+
/** The wire envelope sent to the ingest API. CloudEvents-shaped. */
|
|
19
|
+
interface EventEnvelope {
|
|
20
|
+
schema_version: "1.0";
|
|
21
|
+
event_id: string;
|
|
22
|
+
idempotency_key: string;
|
|
23
|
+
side: Side;
|
|
24
|
+
source: string;
|
|
25
|
+
event_type: WireEventType;
|
|
26
|
+
/** The cross-side join key both sides agree on. */
|
|
27
|
+
reference: string;
|
|
28
|
+
/** This side's native id (e.g. Stripe pi_…/re_…, a ledger row id). */
|
|
29
|
+
external_id: string;
|
|
30
|
+
amount: {
|
|
31
|
+
minor: string;
|
|
32
|
+
currency: string;
|
|
33
|
+
exponent?: number;
|
|
34
|
+
};
|
|
35
|
+
/** Processor fee (minor units); lets reconciliation match a net-of-fee ledger entry. */
|
|
36
|
+
fee?: {
|
|
37
|
+
minor: string;
|
|
38
|
+
currency: string;
|
|
39
|
+
};
|
|
40
|
+
/** Correlation id threading the whole transaction journey (frontend → db). */
|
|
41
|
+
trace_id?: string;
|
|
42
|
+
/** The payout this transaction settled in (processor side). */
|
|
43
|
+
payout_id?: string;
|
|
44
|
+
/** The subscription a charge belongs to (processor side). */
|
|
45
|
+
subscription_id?: string;
|
|
46
|
+
/** The charge a refund/dispute acts on. */
|
|
47
|
+
parent_external_id?: string;
|
|
48
|
+
/** Payout arrival time (payout events only). */
|
|
49
|
+
arrival_at?: string;
|
|
50
|
+
/** Subscription fields (subscription events only). */
|
|
51
|
+
interval?: string;
|
|
52
|
+
current_period_start?: string;
|
|
53
|
+
current_period_end?: string;
|
|
54
|
+
occurred_at: string;
|
|
55
|
+
captured_at: string;
|
|
56
|
+
status?: string;
|
|
57
|
+
direction?: Direction;
|
|
58
|
+
/** Free-form environment tag (Sentry-style). Reconciliation segments by it, so
|
|
59
|
+
* events tagged `staging` never match a `production` ledger. */
|
|
60
|
+
environment?: string;
|
|
61
|
+
metadata?: Record<string, unknown>;
|
|
62
|
+
}
|
|
63
|
+
/** Ergonomic input for record(); the client builds the envelope from it. */
|
|
64
|
+
interface RecordInput {
|
|
65
|
+
type: EventType;
|
|
66
|
+
/** Origin system. Defaults to "custom" (processor) / "ledger.internal" (ledger). */
|
|
67
|
+
source?: string;
|
|
68
|
+
reference: string;
|
|
69
|
+
external_id: string;
|
|
70
|
+
amount: Money;
|
|
71
|
+
/** Processor fee (minor units) — enables net-of-fee reconciliation. */
|
|
72
|
+
fee?: Money;
|
|
73
|
+
/** Correlation id for end-to-end tracing. Reuse the one from the frontend SDK. */
|
|
74
|
+
traceId?: string;
|
|
75
|
+
/** Link this transaction to the payout it settles in. */
|
|
76
|
+
payoutId?: string;
|
|
77
|
+
/** The subscription this charge belongs to (sub_…). Lets reconciliation spot a
|
|
78
|
+
* billing period that never produced a charge. */
|
|
79
|
+
subscriptionId?: string;
|
|
80
|
+
/** The charge a refund or dispute acts on. */
|
|
81
|
+
parentExternalId?: string;
|
|
82
|
+
occurred_at?: string | Date;
|
|
83
|
+
/** For disputes: needs_response | under_review | won | lost. Only `lost` is
|
|
84
|
+
* settled money-out. */
|
|
85
|
+
status?: string;
|
|
86
|
+
direction?: Direction;
|
|
87
|
+
/** Override the client's default environment for this one event. */
|
|
88
|
+
environment?: string;
|
|
89
|
+
metadata?: Record<string, unknown>;
|
|
90
|
+
}
|
|
91
|
+
/** Input for recordSubscription() — a recurring billing container, not money. */
|
|
92
|
+
interface SubscriptionInput {
|
|
93
|
+
source?: string;
|
|
94
|
+
/** The processor's subscription id (e.g. Stripe sub_…). */
|
|
95
|
+
external_id: string;
|
|
96
|
+
status: "active" | "past_due" | "canceled" | "paused" | "trialing";
|
|
97
|
+
/** Amount billed each period. */
|
|
98
|
+
amount: Money;
|
|
99
|
+
interval?: "day" | "week" | "month" | "year";
|
|
100
|
+
currentPeriodStart?: string | Date;
|
|
101
|
+
/** When the next charge is expected by. Reconciliation flags a live
|
|
102
|
+
* subscription whose period ended with no charge. */
|
|
103
|
+
currentPeriodEnd?: string | Date;
|
|
104
|
+
traceId?: string;
|
|
105
|
+
occurred_at?: string | Date;
|
|
106
|
+
/** Override the client's default environment for this one event. */
|
|
107
|
+
environment?: string;
|
|
108
|
+
metadata?: Record<string, unknown>;
|
|
109
|
+
}
|
|
110
|
+
/** Input for recordPayout() — a processor payout (processor → bank). */
|
|
111
|
+
interface PayoutInput {
|
|
112
|
+
source?: string;
|
|
113
|
+
/** The processor's payout id (e.g. Stripe po_…). Used as the reference + external id. */
|
|
114
|
+
external_id: string;
|
|
115
|
+
amount: Money;
|
|
116
|
+
/** When the funds are expected to (or did) arrive in the bank. */
|
|
117
|
+
arrivalAt?: string | Date;
|
|
118
|
+
traceId?: string;
|
|
119
|
+
occurred_at?: string | Date;
|
|
120
|
+
status?: string;
|
|
121
|
+
/** Override the client's default environment for this one event. */
|
|
122
|
+
environment?: string;
|
|
123
|
+
metadata?: Record<string, unknown>;
|
|
124
|
+
}
|
|
125
|
+
/** Pluggable transport (swap for tests or custom delivery). */
|
|
126
|
+
interface Transport {
|
|
127
|
+
send(batch: EventEnvelope[], meta: {
|
|
128
|
+
dropped: number;
|
|
129
|
+
}): Promise<void>;
|
|
130
|
+
}
|
|
131
|
+
interface FinIntegrityConfig {
|
|
132
|
+
/** Secret key (fi_sk_live_… / fi_sk_test_…). Falls back to env FIN_INTEGRITY_KEY. */
|
|
133
|
+
apiKey?: string;
|
|
134
|
+
/** Ingest base URL. Falls back to env FIN_INTEGRITY_ENDPOINT, then the hosted default. */
|
|
135
|
+
endpoint?: string;
|
|
136
|
+
/** Tag events with an environment. Defaults to NODE_ENV. */
|
|
137
|
+
environment?: string;
|
|
138
|
+
/** How idempotency keys are generated. Default "deterministic" (content hash). */
|
|
139
|
+
idempotency?: "deterministic" | "uuid";
|
|
140
|
+
/** Batching controls. */
|
|
141
|
+
batch?: {
|
|
142
|
+
maxSize?: number;
|
|
143
|
+
flushMs?: number;
|
|
144
|
+
};
|
|
145
|
+
/** Max queued events before drop-oldest kicks in. Default 1000. */
|
|
146
|
+
maxQueueSize?: number;
|
|
147
|
+
/** Network retry attempts on 5xx/429/network errors. Default 3. */
|
|
148
|
+
retries?: number;
|
|
149
|
+
/** Fraction of events to keep, [0,1]. Default 1.0 — never silently drop money events. */
|
|
150
|
+
sampleRate?: number;
|
|
151
|
+
/** Mutate or drop (return null) each envelope before sending — e.g. redact PII. */
|
|
152
|
+
beforeSend?: (e: EventEnvelope) => EventEnvelope | null | undefined;
|
|
153
|
+
/** Log transport activity. Default false. */
|
|
154
|
+
debug?: boolean;
|
|
155
|
+
/** Build + validate envelopes but never hit the network. Inspect via _inspect(). */
|
|
156
|
+
dryRun?: boolean;
|
|
157
|
+
/** Override the transport (e.g. an in-memory transport in tests). */
|
|
158
|
+
transport?: Transport;
|
|
159
|
+
/** Called on any internal/transport error. The SDK never throws into your code. */
|
|
160
|
+
onError?: (err: unknown) => void;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
declare class FinIntegrityClient {
|
|
164
|
+
private readonly apiKey;
|
|
165
|
+
private readonly endpoint;
|
|
166
|
+
private readonly idempotencyMode;
|
|
167
|
+
private readonly maxSize;
|
|
168
|
+
private readonly flushMs;
|
|
169
|
+
private readonly maxQueueSize;
|
|
170
|
+
private readonly sampleRate;
|
|
171
|
+
private readonly environment?;
|
|
172
|
+
private readonly beforeSend?;
|
|
173
|
+
private readonly debug;
|
|
174
|
+
private readonly onError;
|
|
175
|
+
private readonly transport;
|
|
176
|
+
private readonly memory?;
|
|
177
|
+
private queue;
|
|
178
|
+
private dropped;
|
|
179
|
+
private timer?;
|
|
180
|
+
/** Capture money-movement observed from a payment processor (Stripe, Adyen, bank, …). */
|
|
181
|
+
readonly processor: {
|
|
182
|
+
record: (input: RecordInput) => void;
|
|
183
|
+
recordPayout: (input: PayoutInput) => void;
|
|
184
|
+
recordSubscription: (input: SubscriptionInput) => void;
|
|
185
|
+
};
|
|
186
|
+
/** Capture entries from your own internal ledger / books. */
|
|
187
|
+
readonly ledger: {
|
|
188
|
+
record: (input: RecordInput) => void;
|
|
189
|
+
};
|
|
190
|
+
constructor(config?: FinIntegrityConfig);
|
|
191
|
+
/**
|
|
192
|
+
* Drain on SIGTERM, then hand the process back its normal fate.
|
|
193
|
+
*
|
|
194
|
+
* Node disables its default terminate-on-SIGTERM as soon as ANY listener is
|
|
195
|
+
* registered, so simply attaching one made this library silently decide that
|
|
196
|
+
* SIGTERM no longer kills the host process — a bare script would ignore
|
|
197
|
+
* `docker stop` and hang until SIGKILL, which is also when the queued events
|
|
198
|
+
* we were trying to protect get dropped anyway.
|
|
199
|
+
*
|
|
200
|
+
* So: flush, then exit ourselves — but only if nothing else is listening. If
|
|
201
|
+
* the app registered its own handler it owns the shutdown sequence, and a
|
|
202
|
+
* library must not exit out from under it. `once` has already removed our own
|
|
203
|
+
* listener by the time this runs, so any remaining count is the app's.
|
|
204
|
+
*/
|
|
205
|
+
private onSigterm;
|
|
206
|
+
/** Low-level escape hatch: capture a fully-specified event (both adapters use this). */
|
|
207
|
+
capture(input: RecordInput & {
|
|
208
|
+
side: Side;
|
|
209
|
+
}): void;
|
|
210
|
+
private record;
|
|
211
|
+
/**
|
|
212
|
+
* Capture a recurring billing container. Not money movement — this is what a
|
|
213
|
+
* charge is expected to arrive in, which is what lets reconciliation catch a
|
|
214
|
+
* billing period that produced no charge at all.
|
|
215
|
+
*
|
|
216
|
+
* Send it whenever the subscription changes (created, renewed, status change)
|
|
217
|
+
* so `currentPeriodEnd` stays current.
|
|
218
|
+
*/
|
|
219
|
+
private recordSubscription;
|
|
220
|
+
/** Capture a processor payout (processor → bank). Stored separately; links to
|
|
221
|
+
* transactions via their payoutId. */
|
|
222
|
+
private recordPayout;
|
|
223
|
+
/** Per-event override wins over the client default; an invalid value falls back
|
|
224
|
+
* to the default (and, if that's absent too, the server defaults to production). */
|
|
225
|
+
private envFields;
|
|
226
|
+
private enqueue;
|
|
227
|
+
/** Send all queued events now. Safe to await (e.g. in a serverless `finally`). */
|
|
228
|
+
flush(): Promise<void>;
|
|
229
|
+
/** Drain and stop the client (call before a long-lived process exits). */
|
|
230
|
+
shutdown(): Promise<void>;
|
|
231
|
+
/** Envelopes captured so far (dryRun / MemoryTransport only). Great for tests. */
|
|
232
|
+
inspect(): EventEnvelope[];
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Wrap the Stripe client you pass in so `charges.create`, `paymentIntents.create`,
|
|
237
|
+
* and `refunds.create` are captured automatically as fin-integrity processor events.
|
|
238
|
+
*
|
|
239
|
+
* We wrap the exact instance you hand us — no global monkey-patching — so it's
|
|
240
|
+
* predictable and ESM/bundler-safe. Capture runs off the hot path and never blocks
|
|
241
|
+
* or alters your Stripe call. Returns the same (now instrumented) client.
|
|
242
|
+
*/
|
|
243
|
+
declare function instrumentStripe<T>(stripe: T, fi: FinIntegrityClient): T;
|
|
244
|
+
|
|
245
|
+
declare class FinIntegrityError extends Error {
|
|
246
|
+
constructor(message: string);
|
|
247
|
+
}
|
|
248
|
+
/** Thrown at init() for invalid configuration (fail fast). Runtime capture never throws. */
|
|
249
|
+
declare class ConfigError extends FinIntegrityError {
|
|
250
|
+
constructor(message: string);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** Per-event rejections inside an HTTP 200. Surfaced via onError, never thrown to the caller. */
|
|
254
|
+
declare class RejectedEventsError extends FinIntegrityError {
|
|
255
|
+
readonly rejected: RejectedEvent[];
|
|
256
|
+
readonly batchSize: number;
|
|
257
|
+
constructor(rejected: RejectedEvent[], batchSize: number);
|
|
258
|
+
}
|
|
259
|
+
interface RejectedEvent {
|
|
260
|
+
event_id: string;
|
|
261
|
+
error: string;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** Create and configure the client. Returns it (also stored as the module singleton). */
|
|
265
|
+
declare function init(config?: FinIntegrityConfig): FinIntegrityClient;
|
|
266
|
+
/** The client from the most recent init() — convenient in scripts. */
|
|
267
|
+
declare function getClient(): FinIntegrityClient;
|
|
268
|
+
|
|
269
|
+
export { ConfigError, type Direction, type EventEnvelope, type EventType, FinIntegrityClient, type FinIntegrityConfig, FinIntegrityError, type Money, type PayoutInput, type RecordInput, type RejectedEvent, RejectedEventsError, type Side, type SubscriptionInput, type Transport, type WireEventType, getClient, init, instrumentStripe };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
type Side = "processor" | "ledger";
|
|
2
|
+
/** Money movement. A dispute is money leaving against a charge — same shape as a
|
|
3
|
+
* refund, so it reconciles through the same path. */
|
|
4
|
+
type EventType = "payment" | "refund" | "dispute";
|
|
5
|
+
/** Wire-level event types. `payout` and `subscription` are processor-only and
|
|
6
|
+
* land in their own tables. */
|
|
7
|
+
type WireEventType = EventType | "payout" | "subscription";
|
|
8
|
+
type Direction = "credit" | "debit";
|
|
9
|
+
/** Money as an integer count of the currency's minor units + ISO-4217 code. Never a float. */
|
|
10
|
+
interface Money {
|
|
11
|
+
/** Integer minor units (e.g. 4999 = $49.99). Accepts number or bigint; serialized as a string. */
|
|
12
|
+
minor: number | bigint;
|
|
13
|
+
/** ISO-4217 currency code, lowercased (e.g. "usd", "jpy"). */
|
|
14
|
+
currency: string;
|
|
15
|
+
/** Optional minor-unit exponent for zero/three-decimal or custom currencies. */
|
|
16
|
+
exponent?: number;
|
|
17
|
+
}
|
|
18
|
+
/** The wire envelope sent to the ingest API. CloudEvents-shaped. */
|
|
19
|
+
interface EventEnvelope {
|
|
20
|
+
schema_version: "1.0";
|
|
21
|
+
event_id: string;
|
|
22
|
+
idempotency_key: string;
|
|
23
|
+
side: Side;
|
|
24
|
+
source: string;
|
|
25
|
+
event_type: WireEventType;
|
|
26
|
+
/** The cross-side join key both sides agree on. */
|
|
27
|
+
reference: string;
|
|
28
|
+
/** This side's native id (e.g. Stripe pi_…/re_…, a ledger row id). */
|
|
29
|
+
external_id: string;
|
|
30
|
+
amount: {
|
|
31
|
+
minor: string;
|
|
32
|
+
currency: string;
|
|
33
|
+
exponent?: number;
|
|
34
|
+
};
|
|
35
|
+
/** Processor fee (minor units); lets reconciliation match a net-of-fee ledger entry. */
|
|
36
|
+
fee?: {
|
|
37
|
+
minor: string;
|
|
38
|
+
currency: string;
|
|
39
|
+
};
|
|
40
|
+
/** Correlation id threading the whole transaction journey (frontend → db). */
|
|
41
|
+
trace_id?: string;
|
|
42
|
+
/** The payout this transaction settled in (processor side). */
|
|
43
|
+
payout_id?: string;
|
|
44
|
+
/** The subscription a charge belongs to (processor side). */
|
|
45
|
+
subscription_id?: string;
|
|
46
|
+
/** The charge a refund/dispute acts on. */
|
|
47
|
+
parent_external_id?: string;
|
|
48
|
+
/** Payout arrival time (payout events only). */
|
|
49
|
+
arrival_at?: string;
|
|
50
|
+
/** Subscription fields (subscription events only). */
|
|
51
|
+
interval?: string;
|
|
52
|
+
current_period_start?: string;
|
|
53
|
+
current_period_end?: string;
|
|
54
|
+
occurred_at: string;
|
|
55
|
+
captured_at: string;
|
|
56
|
+
status?: string;
|
|
57
|
+
direction?: Direction;
|
|
58
|
+
/** Free-form environment tag (Sentry-style). Reconciliation segments by it, so
|
|
59
|
+
* events tagged `staging` never match a `production` ledger. */
|
|
60
|
+
environment?: string;
|
|
61
|
+
metadata?: Record<string, unknown>;
|
|
62
|
+
}
|
|
63
|
+
/** Ergonomic input for record(); the client builds the envelope from it. */
|
|
64
|
+
interface RecordInput {
|
|
65
|
+
type: EventType;
|
|
66
|
+
/** Origin system. Defaults to "custom" (processor) / "ledger.internal" (ledger). */
|
|
67
|
+
source?: string;
|
|
68
|
+
reference: string;
|
|
69
|
+
external_id: string;
|
|
70
|
+
amount: Money;
|
|
71
|
+
/** Processor fee (minor units) — enables net-of-fee reconciliation. */
|
|
72
|
+
fee?: Money;
|
|
73
|
+
/** Correlation id for end-to-end tracing. Reuse the one from the frontend SDK. */
|
|
74
|
+
traceId?: string;
|
|
75
|
+
/** Link this transaction to the payout it settles in. */
|
|
76
|
+
payoutId?: string;
|
|
77
|
+
/** The subscription this charge belongs to (sub_…). Lets reconciliation spot a
|
|
78
|
+
* billing period that never produced a charge. */
|
|
79
|
+
subscriptionId?: string;
|
|
80
|
+
/** The charge a refund or dispute acts on. */
|
|
81
|
+
parentExternalId?: string;
|
|
82
|
+
occurred_at?: string | Date;
|
|
83
|
+
/** For disputes: needs_response | under_review | won | lost. Only `lost` is
|
|
84
|
+
* settled money-out. */
|
|
85
|
+
status?: string;
|
|
86
|
+
direction?: Direction;
|
|
87
|
+
/** Override the client's default environment for this one event. */
|
|
88
|
+
environment?: string;
|
|
89
|
+
metadata?: Record<string, unknown>;
|
|
90
|
+
}
|
|
91
|
+
/** Input for recordSubscription() — a recurring billing container, not money. */
|
|
92
|
+
interface SubscriptionInput {
|
|
93
|
+
source?: string;
|
|
94
|
+
/** The processor's subscription id (e.g. Stripe sub_…). */
|
|
95
|
+
external_id: string;
|
|
96
|
+
status: "active" | "past_due" | "canceled" | "paused" | "trialing";
|
|
97
|
+
/** Amount billed each period. */
|
|
98
|
+
amount: Money;
|
|
99
|
+
interval?: "day" | "week" | "month" | "year";
|
|
100
|
+
currentPeriodStart?: string | Date;
|
|
101
|
+
/** When the next charge is expected by. Reconciliation flags a live
|
|
102
|
+
* subscription whose period ended with no charge. */
|
|
103
|
+
currentPeriodEnd?: string | Date;
|
|
104
|
+
traceId?: string;
|
|
105
|
+
occurred_at?: string | Date;
|
|
106
|
+
/** Override the client's default environment for this one event. */
|
|
107
|
+
environment?: string;
|
|
108
|
+
metadata?: Record<string, unknown>;
|
|
109
|
+
}
|
|
110
|
+
/** Input for recordPayout() — a processor payout (processor → bank). */
|
|
111
|
+
interface PayoutInput {
|
|
112
|
+
source?: string;
|
|
113
|
+
/** The processor's payout id (e.g. Stripe po_…). Used as the reference + external id. */
|
|
114
|
+
external_id: string;
|
|
115
|
+
amount: Money;
|
|
116
|
+
/** When the funds are expected to (or did) arrive in the bank. */
|
|
117
|
+
arrivalAt?: string | Date;
|
|
118
|
+
traceId?: string;
|
|
119
|
+
occurred_at?: string | Date;
|
|
120
|
+
status?: string;
|
|
121
|
+
/** Override the client's default environment for this one event. */
|
|
122
|
+
environment?: string;
|
|
123
|
+
metadata?: Record<string, unknown>;
|
|
124
|
+
}
|
|
125
|
+
/** Pluggable transport (swap for tests or custom delivery). */
|
|
126
|
+
interface Transport {
|
|
127
|
+
send(batch: EventEnvelope[], meta: {
|
|
128
|
+
dropped: number;
|
|
129
|
+
}): Promise<void>;
|
|
130
|
+
}
|
|
131
|
+
interface FinIntegrityConfig {
|
|
132
|
+
/** Secret key (fi_sk_live_… / fi_sk_test_…). Falls back to env FIN_INTEGRITY_KEY. */
|
|
133
|
+
apiKey?: string;
|
|
134
|
+
/** Ingest base URL. Falls back to env FIN_INTEGRITY_ENDPOINT, then the hosted default. */
|
|
135
|
+
endpoint?: string;
|
|
136
|
+
/** Tag events with an environment. Defaults to NODE_ENV. */
|
|
137
|
+
environment?: string;
|
|
138
|
+
/** How idempotency keys are generated. Default "deterministic" (content hash). */
|
|
139
|
+
idempotency?: "deterministic" | "uuid";
|
|
140
|
+
/** Batching controls. */
|
|
141
|
+
batch?: {
|
|
142
|
+
maxSize?: number;
|
|
143
|
+
flushMs?: number;
|
|
144
|
+
};
|
|
145
|
+
/** Max queued events before drop-oldest kicks in. Default 1000. */
|
|
146
|
+
maxQueueSize?: number;
|
|
147
|
+
/** Network retry attempts on 5xx/429/network errors. Default 3. */
|
|
148
|
+
retries?: number;
|
|
149
|
+
/** Fraction of events to keep, [0,1]. Default 1.0 — never silently drop money events. */
|
|
150
|
+
sampleRate?: number;
|
|
151
|
+
/** Mutate or drop (return null) each envelope before sending — e.g. redact PII. */
|
|
152
|
+
beforeSend?: (e: EventEnvelope) => EventEnvelope | null | undefined;
|
|
153
|
+
/** Log transport activity. Default false. */
|
|
154
|
+
debug?: boolean;
|
|
155
|
+
/** Build + validate envelopes but never hit the network. Inspect via _inspect(). */
|
|
156
|
+
dryRun?: boolean;
|
|
157
|
+
/** Override the transport (e.g. an in-memory transport in tests). */
|
|
158
|
+
transport?: Transport;
|
|
159
|
+
/** Called on any internal/transport error. The SDK never throws into your code. */
|
|
160
|
+
onError?: (err: unknown) => void;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
declare class FinIntegrityClient {
|
|
164
|
+
private readonly apiKey;
|
|
165
|
+
private readonly endpoint;
|
|
166
|
+
private readonly idempotencyMode;
|
|
167
|
+
private readonly maxSize;
|
|
168
|
+
private readonly flushMs;
|
|
169
|
+
private readonly maxQueueSize;
|
|
170
|
+
private readonly sampleRate;
|
|
171
|
+
private readonly environment?;
|
|
172
|
+
private readonly beforeSend?;
|
|
173
|
+
private readonly debug;
|
|
174
|
+
private readonly onError;
|
|
175
|
+
private readonly transport;
|
|
176
|
+
private readonly memory?;
|
|
177
|
+
private queue;
|
|
178
|
+
private dropped;
|
|
179
|
+
private timer?;
|
|
180
|
+
/** Capture money-movement observed from a payment processor (Stripe, Adyen, bank, …). */
|
|
181
|
+
readonly processor: {
|
|
182
|
+
record: (input: RecordInput) => void;
|
|
183
|
+
recordPayout: (input: PayoutInput) => void;
|
|
184
|
+
recordSubscription: (input: SubscriptionInput) => void;
|
|
185
|
+
};
|
|
186
|
+
/** Capture entries from your own internal ledger / books. */
|
|
187
|
+
readonly ledger: {
|
|
188
|
+
record: (input: RecordInput) => void;
|
|
189
|
+
};
|
|
190
|
+
constructor(config?: FinIntegrityConfig);
|
|
191
|
+
/**
|
|
192
|
+
* Drain on SIGTERM, then hand the process back its normal fate.
|
|
193
|
+
*
|
|
194
|
+
* Node disables its default terminate-on-SIGTERM as soon as ANY listener is
|
|
195
|
+
* registered, so simply attaching one made this library silently decide that
|
|
196
|
+
* SIGTERM no longer kills the host process — a bare script would ignore
|
|
197
|
+
* `docker stop` and hang until SIGKILL, which is also when the queued events
|
|
198
|
+
* we were trying to protect get dropped anyway.
|
|
199
|
+
*
|
|
200
|
+
* So: flush, then exit ourselves — but only if nothing else is listening. If
|
|
201
|
+
* the app registered its own handler it owns the shutdown sequence, and a
|
|
202
|
+
* library must not exit out from under it. `once` has already removed our own
|
|
203
|
+
* listener by the time this runs, so any remaining count is the app's.
|
|
204
|
+
*/
|
|
205
|
+
private onSigterm;
|
|
206
|
+
/** Low-level escape hatch: capture a fully-specified event (both adapters use this). */
|
|
207
|
+
capture(input: RecordInput & {
|
|
208
|
+
side: Side;
|
|
209
|
+
}): void;
|
|
210
|
+
private record;
|
|
211
|
+
/**
|
|
212
|
+
* Capture a recurring billing container. Not money movement — this is what a
|
|
213
|
+
* charge is expected to arrive in, which is what lets reconciliation catch a
|
|
214
|
+
* billing period that produced no charge at all.
|
|
215
|
+
*
|
|
216
|
+
* Send it whenever the subscription changes (created, renewed, status change)
|
|
217
|
+
* so `currentPeriodEnd` stays current.
|
|
218
|
+
*/
|
|
219
|
+
private recordSubscription;
|
|
220
|
+
/** Capture a processor payout (processor → bank). Stored separately; links to
|
|
221
|
+
* transactions via their payoutId. */
|
|
222
|
+
private recordPayout;
|
|
223
|
+
/** Per-event override wins over the client default; an invalid value falls back
|
|
224
|
+
* to the default (and, if that's absent too, the server defaults to production). */
|
|
225
|
+
private envFields;
|
|
226
|
+
private enqueue;
|
|
227
|
+
/** Send all queued events now. Safe to await (e.g. in a serverless `finally`). */
|
|
228
|
+
flush(): Promise<void>;
|
|
229
|
+
/** Drain and stop the client (call before a long-lived process exits). */
|
|
230
|
+
shutdown(): Promise<void>;
|
|
231
|
+
/** Envelopes captured so far (dryRun / MemoryTransport only). Great for tests. */
|
|
232
|
+
inspect(): EventEnvelope[];
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Wrap the Stripe client you pass in so `charges.create`, `paymentIntents.create`,
|
|
237
|
+
* and `refunds.create` are captured automatically as fin-integrity processor events.
|
|
238
|
+
*
|
|
239
|
+
* We wrap the exact instance you hand us — no global monkey-patching — so it's
|
|
240
|
+
* predictable and ESM/bundler-safe. Capture runs off the hot path and never blocks
|
|
241
|
+
* or alters your Stripe call. Returns the same (now instrumented) client.
|
|
242
|
+
*/
|
|
243
|
+
declare function instrumentStripe<T>(stripe: T, fi: FinIntegrityClient): T;
|
|
244
|
+
|
|
245
|
+
declare class FinIntegrityError extends Error {
|
|
246
|
+
constructor(message: string);
|
|
247
|
+
}
|
|
248
|
+
/** Thrown at init() for invalid configuration (fail fast). Runtime capture never throws. */
|
|
249
|
+
declare class ConfigError extends FinIntegrityError {
|
|
250
|
+
constructor(message: string);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** Per-event rejections inside an HTTP 200. Surfaced via onError, never thrown to the caller. */
|
|
254
|
+
declare class RejectedEventsError extends FinIntegrityError {
|
|
255
|
+
readonly rejected: RejectedEvent[];
|
|
256
|
+
readonly batchSize: number;
|
|
257
|
+
constructor(rejected: RejectedEvent[], batchSize: number);
|
|
258
|
+
}
|
|
259
|
+
interface RejectedEvent {
|
|
260
|
+
event_id: string;
|
|
261
|
+
error: string;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** Create and configure the client. Returns it (also stored as the module singleton). */
|
|
265
|
+
declare function init(config?: FinIntegrityConfig): FinIntegrityClient;
|
|
266
|
+
/** The client from the most recent init() — convenient in scripts. */
|
|
267
|
+
declare function getClient(): FinIntegrityClient;
|
|
268
|
+
|
|
269
|
+
export { ConfigError, type Direction, type EventEnvelope, type EventType, FinIntegrityClient, type FinIntegrityConfig, FinIntegrityError, type Money, type PayoutInput, type RecordInput, type RejectedEvent, RejectedEventsError, type Side, type SubscriptionInput, type Transport, type WireEventType, getClient, init, instrumentStripe };
|