@classytic/primitives 0.4.0 → 0.5.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/CHANGELOG.md +90 -0
- package/dist/bank-transaction.d.mts +167 -0
- package/dist/bank-transaction.mjs +1 -0
- package/dist/currency-rgUAn5KM.d.mts +110 -0
- package/dist/currency.d.mts +1 -109
- package/dist/money.d.mts +1 -1
- package/dist/payment-gateway.d.mts +168 -0
- package/dist/payment-gateway.mjs +1 -0
- package/package.json +9 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,96 @@
|
|
|
3
3
|
Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
4
4
|
adhering to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
5
5
|
|
|
6
|
+
## [0.5.0] - 2026-05-05
|
|
7
|
+
|
|
8
|
+
### Added — `/bank-transaction` subpath
|
|
9
|
+
|
|
10
|
+
Canonical bank-feed shapes shared across `@classytic/fin-io` (parsers
|
|
11
|
+
produce these), `@classytic/revenue` (`transactionRepository.import()`
|
|
12
|
+
consumes them), and `@classytic/ledger` (`bankStatementMapper`
|
|
13
|
+
consumes them). Single source of truth for the cross-package
|
|
14
|
+
vocabulary; eliminates the structural mirrors fin-io and revenue
|
|
15
|
+
were carrying.
|
|
16
|
+
|
|
17
|
+
Exports: `BankTransaction`, `BankCounterparty`, `BankAccount`,
|
|
18
|
+
`BankStatement`, `BankStatementSource`, `BankImportReport`,
|
|
19
|
+
`BankImportRowError`. All anchored on `Money` from `/money` (integer
|
|
20
|
+
minor units, `number`).
|
|
21
|
+
|
|
22
|
+
### Added — `/payment-gateway` subpath
|
|
23
|
+
|
|
24
|
+
Canonical payment-gateway data shapes. **Lets any provider package
|
|
25
|
+
(Stripe, Razorpay, SSLCommerz, PayPal, bKash, Nagad, manual, …)
|
|
26
|
+
implement against primitives' types without depending on revenue's
|
|
27
|
+
heavyweight runtime.** Provider authors peer-dep on
|
|
28
|
+
`@classytic/primitives` only.
|
|
29
|
+
|
|
30
|
+
Exports: `CreateIntentParams`, `PaymentIntent`, `PaymentResult`,
|
|
31
|
+
`RefundResult`, `WebhookEvent`, `ProviderCapabilities`. All `amount`
|
|
32
|
+
fields are `Money` from `/money` (no flat `amount: number, currency:
|
|
33
|
+
string` pairs — currency travels inside Money).
|
|
34
|
+
|
|
35
|
+
The `PaymentProvider` abstract class — the *contract* every provider
|
|
36
|
+
must satisfy — stays in `@classytic/revenue`. Revenue is the data
|
|
37
|
+
engine that consumes providers; provider packages live elsewhere.
|
|
38
|
+
|
|
39
|
+
### Architectural intent
|
|
40
|
+
|
|
41
|
+
Revenue 3.0 + primitives 0.5 + fin-io 0.3 establish a clean
|
|
42
|
+
three-tier seam:
|
|
43
|
+
|
|
44
|
+
1. **primitives** — pure data shapes (no runtime, no deps). Owns
|
|
45
|
+
`Money`, `BankTransaction`, `PaymentIntent`, every cross-
|
|
46
|
+
package vocabulary type.
|
|
47
|
+
2. **revenue** — the engine. Owns the abstract contracts
|
|
48
|
+
(`PaymentProvider`, `BankFeedProvider`), state machines, repos.
|
|
49
|
+
Consumes primitives shapes.
|
|
50
|
+
3. **provider packages** (`revenue-stripe`, `revenue-razorpay`,
|
|
51
|
+
`@classytic/fin-io/adapters/revenue`, …) — implementations.
|
|
52
|
+
Peer-dep on primitives. Optional peer-dep on revenue for the
|
|
53
|
+
abstract base class.
|
|
54
|
+
|
|
55
|
+
This eliminates the cycle where every provider package transitively
|
|
56
|
+
pulled mongoose + mongokit just to know what a `PaymentIntent`
|
|
57
|
+
looked like. Tree-shaking, build times, and dep-graph honesty all
|
|
58
|
+
improve.
|
|
59
|
+
|
|
60
|
+
### Migration
|
|
61
|
+
|
|
62
|
+
Consumers MUST import from primitives subpaths. No re-exports from
|
|
63
|
+
revenue or fin-io (PACKAGE_RULES P2):
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
// Before
|
|
67
|
+
import type { PaymentIntent, CreateIntentParams } from '@classytic/revenue';
|
|
68
|
+
import type { CanonicalTransaction, Money } from '@classytic/fin-io';
|
|
69
|
+
|
|
70
|
+
// After
|
|
71
|
+
import type {
|
|
72
|
+
PaymentIntent,
|
|
73
|
+
CreateIntentParams,
|
|
74
|
+
} from '@classytic/primitives/payment-gateway';
|
|
75
|
+
import type { BankTransaction } from '@classytic/primitives/bank-transaction';
|
|
76
|
+
import type { Money } from '@classytic/primitives/money';
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Provider helper classes (`PaymentIntent`, `PaymentResult`,
|
|
80
|
+
`RefundResult`, `WebhookEvent`) are deleted from revenue — they were
|
|
81
|
+
trivial wrappers around plain objects. Provider implementations now
|
|
82
|
+
return plain object literals matching the interface.
|
|
83
|
+
|
|
84
|
+
`CreateIntentParams.amount` is now `Money` (`{ amount: number,
|
|
85
|
+
currency: string }`), not the flat `amount + currency` pair. Revenue's
|
|
86
|
+
repository wraps internally:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
const intent = await provider.createIntent({
|
|
90
|
+
amount: { amount: params.amount, currency },
|
|
91
|
+
metadata: params.metadata,
|
|
92
|
+
...params.paymentData,
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
6
96
|
## [0.3.1] - 2026-05-02
|
|
7
97
|
|
|
8
98
|
### Added — state-machine ↔ mongokit/sqlitekit integration
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { Money } from "./money.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/bank-transaction.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* The other side of a bank transaction. All fields optional — different
|
|
6
|
+
* formats populate different subsets:
|
|
7
|
+
* OFX usually carries `name` only.
|
|
8
|
+
* CAMT carries IBAN + BIC + name (SEPA structured data).
|
|
9
|
+
* Plaid carries `merchant_name` mapped to `name`.
|
|
10
|
+
* CSV varies; usually `name` + maybe `accountNumber`.
|
|
11
|
+
*/
|
|
12
|
+
interface BankCounterparty {
|
|
13
|
+
/** Free-text name as the bank emits it. */
|
|
14
|
+
name?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Vendor-stable identifier — bank's own customer ID, structured
|
|
17
|
+
* remittance reference (ISO 11649), etc. Distinct from the
|
|
18
|
+
* transaction-level `reference`.
|
|
19
|
+
*/
|
|
20
|
+
identifier?: string;
|
|
21
|
+
/** International Bank Account Number (SEPA / EU). */
|
|
22
|
+
iban?: string;
|
|
23
|
+
/** Domestic account number. */
|
|
24
|
+
accountNumber?: string;
|
|
25
|
+
/** Bank / SWIFT BIC. */
|
|
26
|
+
bic?: string;
|
|
27
|
+
/** US ABA routing / Canadian transit. */
|
|
28
|
+
routingNumber?: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Identifies the account a statement / sync batch is for. Different
|
|
32
|
+
* formats populate different subsets — consumers should treat every
|
|
33
|
+
* field except `currency` as best-effort.
|
|
34
|
+
*/
|
|
35
|
+
interface BankAccount {
|
|
36
|
+
iban?: string;
|
|
37
|
+
accountNumber?: string;
|
|
38
|
+
/** US ABA / SWIFT BIC / domestic bank code. Generic — format-specific. */
|
|
39
|
+
bankCode?: string;
|
|
40
|
+
/** Human-readable bank name. */
|
|
41
|
+
bankName?: string;
|
|
42
|
+
/** Account holder name as the bank emits it. */
|
|
43
|
+
ownerName?: string;
|
|
44
|
+
/** Type as the bank classifies it: `'CHECKING'`, `'SAVINGS'`, `'CREDITLINE'`. */
|
|
45
|
+
accountType?: string;
|
|
46
|
+
/**
|
|
47
|
+
* ISO 4217 of the account itself. REQUIRED — synthesized from format
|
|
48
|
+
* defaults if absent (parsers MUST fill this in).
|
|
49
|
+
*/
|
|
50
|
+
currency: string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* One row from a bank statement / accounting feed sync.
|
|
54
|
+
*
|
|
55
|
+
* `externalId` is REQUIRED — without it idempotent re-import is broken.
|
|
56
|
+
* Synthesize one from `(date | amount | description hash)` if the
|
|
57
|
+
* upstream format lacks a vendor-stable id.
|
|
58
|
+
*/
|
|
59
|
+
interface BankTransaction {
|
|
60
|
+
/**
|
|
61
|
+
* Vendor-stable unique identifier. REQUIRED.
|
|
62
|
+
*
|
|
63
|
+
* OFX: `<FITID>` from the source file.
|
|
64
|
+
* CAMT: `<NtryRef>` or `<AcctSvcrRef>`.
|
|
65
|
+
* QBO: `Id` from the QBO API.
|
|
66
|
+
* Plaid: `transaction_id`.
|
|
67
|
+
* CSV: synthesized as `sha256(date|amount|description)` truncated
|
|
68
|
+
* to 16 hex chars; parser should emit a warning.
|
|
69
|
+
*
|
|
70
|
+
* Used by consumers to enforce idempotent re-import via
|
|
71
|
+
* `(orgId, bankAccountId, externalId)` partial-unique indexes.
|
|
72
|
+
* Distinct from any host-chosen request-scoped idempotency key.
|
|
73
|
+
*/
|
|
74
|
+
externalId: string;
|
|
75
|
+
/** When the bank booked the entry. */
|
|
76
|
+
postedDate: Date;
|
|
77
|
+
/** When funds become available (cleared). May equal `postedDate`. */
|
|
78
|
+
valueDate?: Date;
|
|
79
|
+
/**
|
|
80
|
+
* Signed amount in integer minor units. Positive = inflow to the
|
|
81
|
+
* account holder, negative = outflow. Currency carried inside `Money`.
|
|
82
|
+
*/
|
|
83
|
+
amount: Money;
|
|
84
|
+
/** Free-text description as the bank emitted it. */
|
|
85
|
+
description: string;
|
|
86
|
+
/** Counterparty (payer / payee) when the format provides one. */
|
|
87
|
+
counterparty?: BankCounterparty;
|
|
88
|
+
/** Bank-provided reference number, check number, end-to-end id. */
|
|
89
|
+
reference?: string;
|
|
90
|
+
/**
|
|
91
|
+
* Bank-provided category if any. Rare in OFX / CAMT, common in
|
|
92
|
+
* Plaid and consumer-finance CSV (Mint / YNAB).
|
|
93
|
+
*/
|
|
94
|
+
category?: string;
|
|
95
|
+
/** Running balance on the account after this entry, if available. */
|
|
96
|
+
balanceAfter?: Money;
|
|
97
|
+
/** Format-specific entry-type code (`'DEBIT'`, `'CREDIT'`, `'CHECK'`, …). */
|
|
98
|
+
type?: string;
|
|
99
|
+
/**
|
|
100
|
+
* Format-specific raw record. Pass-through for audit / debugging.
|
|
101
|
+
* Consumers MUST NOT rely on this — schema may change between fin-io
|
|
102
|
+
* minor versions. Discard before persistence if size is a concern.
|
|
103
|
+
*/
|
|
104
|
+
raw?: unknown;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Aggregate envelope produced by file-format parsers (OFX, CAMT.053,
|
|
108
|
+
* MT940, CSV, IIF). Sync providers (Plaid CDC, QBO CDC) emit
|
|
109
|
+
* `BankTransaction[]` directly without the statement wrapper because
|
|
110
|
+
* sync is continuous, not statement-scoped.
|
|
111
|
+
*/
|
|
112
|
+
interface BankStatement {
|
|
113
|
+
account: BankAccount;
|
|
114
|
+
/** Statement period the parser observed. */
|
|
115
|
+
period: {
|
|
116
|
+
from: Date;
|
|
117
|
+
to: Date;
|
|
118
|
+
};
|
|
119
|
+
/** Opening balance at `period.from`. */
|
|
120
|
+
openingBalance: Money;
|
|
121
|
+
/** Closing balance at `period.to`. */
|
|
122
|
+
closingBalance: Money;
|
|
123
|
+
/**
|
|
124
|
+
* Available balance at statement end (may differ from
|
|
125
|
+
* `closingBalance` for pending-but-not-cleared items).
|
|
126
|
+
*/
|
|
127
|
+
availableBalance?: Money;
|
|
128
|
+
transactions: BankTransaction[];
|
|
129
|
+
/** Provenance metadata — which format produced this statement. */
|
|
130
|
+
source: BankStatementSource;
|
|
131
|
+
}
|
|
132
|
+
interface BankStatementSource {
|
|
133
|
+
/**
|
|
134
|
+
* Format the statement was parsed from. The set is closed —
|
|
135
|
+
* consumers can switch on it exhaustively. Add new variants here
|
|
136
|
+
* (and bump the package minor version) when adding a new parser.
|
|
137
|
+
*/
|
|
138
|
+
format: 'ofx' | 'qfx' | 'camt053' | 'mt940' | 'csv' | 'iif' | 'xero-csv' | 'plaid' | 'qbo';
|
|
139
|
+
/** Format version — `'OFX 2.1.1'`, `'CAMT.053.001.08'`, etc. */
|
|
140
|
+
version?: string;
|
|
141
|
+
/** When the file was generated by the bank, if present. */
|
|
142
|
+
generatedAt?: Date;
|
|
143
|
+
/** Bank / vendor software fingerprint (e.g. OFX FI ORG). */
|
|
144
|
+
softwareId?: string;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Per-row failure during a bulk import. Consumers (revenue's `import()`)
|
|
148
|
+
* collect these without aborting the batch.
|
|
149
|
+
*/
|
|
150
|
+
interface BankImportRowError {
|
|
151
|
+
externalId: string;
|
|
152
|
+
reason: string;
|
|
153
|
+
row?: BankTransaction;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Outcome of a bulk import. Same shape ledger's `wireImport` reports
|
|
157
|
+
* for symmetry.
|
|
158
|
+
*/
|
|
159
|
+
interface BankImportReport {
|
|
160
|
+
inserted: number;
|
|
161
|
+
updated: number;
|
|
162
|
+
skipped: number;
|
|
163
|
+
errors: BankImportRowError[];
|
|
164
|
+
durationMs: number;
|
|
165
|
+
}
|
|
166
|
+
//#endregion
|
|
167
|
+
export { BankAccount, BankCounterparty, BankImportReport, BankImportRowError, BankStatement, BankStatementSource, BankTransaction };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { t as Brand } from "./brand-DApaegoR.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/currency.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* ISO 4217 currency code (3 uppercase letters).
|
|
6
|
+
*
|
|
7
|
+
* Branded `string` — any 3-letter string is structurally valid, but the brand
|
|
8
|
+
* prevents accidental mixing with raw strings. Use {@link toCurrencyCode} to
|
|
9
|
+
* construct one safely.
|
|
10
|
+
*/
|
|
11
|
+
type CurrencyCode = Brand<string, 'CurrencyCode'>;
|
|
12
|
+
/**
|
|
13
|
+
* Common ISO 4217 codes. Non-exhaustive — add more at the consumer level if
|
|
14
|
+
* needed. Listed here because shared-types ships with these by default.
|
|
15
|
+
*/
|
|
16
|
+
declare const CURRENCIES: {
|
|
17
|
+
readonly USD: "USD";
|
|
18
|
+
readonly EUR: "EUR";
|
|
19
|
+
readonly GBP: "GBP";
|
|
20
|
+
readonly JPY: "JPY";
|
|
21
|
+
readonly CNY: "CNY";
|
|
22
|
+
readonly INR: "INR";
|
|
23
|
+
readonly BDT: "BDT";
|
|
24
|
+
readonly AED: "AED";
|
|
25
|
+
readonly SAR: "SAR";
|
|
26
|
+
readonly PKR: "PKR";
|
|
27
|
+
readonly NPR: "NPR";
|
|
28
|
+
readonly LKR: "LKR";
|
|
29
|
+
readonly MYR: "MYR";
|
|
30
|
+
readonly SGD: "SGD";
|
|
31
|
+
readonly AUD: "AUD";
|
|
32
|
+
readonly CAD: "CAD";
|
|
33
|
+
readonly CHF: "CHF";
|
|
34
|
+
readonly HKD: "HKD";
|
|
35
|
+
readonly THB: "THB";
|
|
36
|
+
readonly IDR: "IDR";
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Number of minor units per major unit for common currencies.
|
|
40
|
+
*
|
|
41
|
+
* ISO 4217 defines most currencies with 2 decimal places (cents). A few are
|
|
42
|
+
* zero-decimal (JPY, KRW) or three-decimal (BHD, KWD, JOD). When a currency
|
|
43
|
+
* isn't in this table, {@link minorUnitFactor} returns 100 as the safe default.
|
|
44
|
+
*/
|
|
45
|
+
declare const MINOR_UNIT_FACTOR: Readonly<Record<string, number>>;
|
|
46
|
+
/**
|
|
47
|
+
* Narrow a raw string to a {@link CurrencyCode}. Returns `null` if the string
|
|
48
|
+
* is not a valid ISO 4217 format (3 uppercase letters). This does not verify
|
|
49
|
+
* the code is registered with ISO — use your own whitelist for that.
|
|
50
|
+
*/
|
|
51
|
+
declare function toCurrencyCode(value: string): CurrencyCode | null;
|
|
52
|
+
/** Type predicate for `unknown` input. */
|
|
53
|
+
declare function isCurrencyCode(value: unknown): value is CurrencyCode;
|
|
54
|
+
/**
|
|
55
|
+
* Minor units per major unit (e.g. 100 for USD, 1 for JPY, 1000 for KWD).
|
|
56
|
+
* Falls back to 100 when the currency is unknown — matches the ISO 4217 default.
|
|
57
|
+
*/
|
|
58
|
+
declare function minorUnitFactor(currency: string): number;
|
|
59
|
+
/**
|
|
60
|
+
* Frozen FX conversion record — the cost of record for cross-currency
|
|
61
|
+
* transactions. Stored alongside a base-currency amount at the seam where
|
|
62
|
+
* currency crossed (procurement receipt, landed-cost entry, foreign refund).
|
|
63
|
+
*
|
|
64
|
+
* Invariant: once written, never mutated. Revaluation is a new snapshot on
|
|
65
|
+
* a new document — rewriting an existing snapshot corrupts audit history.
|
|
66
|
+
*
|
|
67
|
+
* `rate` is expressed as: 1 sourceCurrency = rate * baseCurrency
|
|
68
|
+
* (e.g. USD→BDT at 110 means rate = 110, so sourceAmount 10 USD → baseAmount 1100 BDT).
|
|
69
|
+
* This matches how banks quote — the source is the "from" currency.
|
|
70
|
+
*/
|
|
71
|
+
interface FxSnapshot {
|
|
72
|
+
/** Currency the transaction was invoiced / quoted in. ISO 4217. */
|
|
73
|
+
sourceCurrency: string;
|
|
74
|
+
/** Organisation's functional currency the base amount is expressed in. */
|
|
75
|
+
baseCurrency: string;
|
|
76
|
+
/** 1 unit of sourceCurrency equals this many units of baseCurrency. */
|
|
77
|
+
rate: number;
|
|
78
|
+
/** When the rate was captured — NOT when the document was last touched. */
|
|
79
|
+
snapshotAt: Date;
|
|
80
|
+
/**
|
|
81
|
+
* Optional provenance tag — e.g. 'manual', 'bangladesh-bank',
|
|
82
|
+
* 'openexchangerates.org', 'supplier-invoice'. Free-form; hosts use it for
|
|
83
|
+
* audit filtering and re-pricing policy.
|
|
84
|
+
*/
|
|
85
|
+
source?: string;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Apply an {@link FxSnapshot} to a source-currency amount. Returns the
|
|
89
|
+
* equivalent base-currency amount. No rounding — callers that persist into
|
|
90
|
+
* an integer minor-unit column must round explicitly with a deterministic
|
|
91
|
+
* rule (`Math.round`, banker's rounding, floor for duty-conservative
|
|
92
|
+
* postings, etc.).
|
|
93
|
+
*/
|
|
94
|
+
declare function convertWithSnapshot(sourceAmount: number, fx: FxSnapshot): number;
|
|
95
|
+
/**
|
|
96
|
+
* Reverse an {@link FxSnapshot} — reconstruct the source-currency amount
|
|
97
|
+
* from a stored base amount. Useful for reporting "cost in original
|
|
98
|
+
* currency". Not round-trip safe under rounding; prefer storing the
|
|
99
|
+
* original source amount alongside the base amount when audit fidelity
|
|
100
|
+
* matters.
|
|
101
|
+
*/
|
|
102
|
+
declare function reverseWithSnapshot(baseAmount: number, fx: FxSnapshot): number;
|
|
103
|
+
/**
|
|
104
|
+
* Structural validation — checks shape only. Does NOT verify the rate is
|
|
105
|
+
* economically sensible (8.5 USD→BDT would pass this check). Host-level
|
|
106
|
+
* guards / anomaly detection handle that.
|
|
107
|
+
*/
|
|
108
|
+
declare function isFxSnapshot(value: unknown): value is FxSnapshot;
|
|
109
|
+
//#endregion
|
|
110
|
+
export { convertWithSnapshot as a, minorUnitFactor as c, MINOR_UNIT_FACTOR as i, reverseWithSnapshot as l, CurrencyCode as n, isCurrencyCode as o, FxSnapshot as r, isFxSnapshot as s, CURRENCIES as t, toCurrencyCode as u };
|
package/dist/currency.d.mts
CHANGED
|
@@ -1,110 +1,2 @@
|
|
|
1
|
-
import { t as
|
|
2
|
-
|
|
3
|
-
//#region src/currency.d.ts
|
|
4
|
-
/**
|
|
5
|
-
* ISO 4217 currency code (3 uppercase letters).
|
|
6
|
-
*
|
|
7
|
-
* Branded `string` — any 3-letter string is structurally valid, but the brand
|
|
8
|
-
* prevents accidental mixing with raw strings. Use {@link toCurrencyCode} to
|
|
9
|
-
* construct one safely.
|
|
10
|
-
*/
|
|
11
|
-
type CurrencyCode = Brand<string, 'CurrencyCode'>;
|
|
12
|
-
/**
|
|
13
|
-
* Common ISO 4217 codes. Non-exhaustive — add more at the consumer level if
|
|
14
|
-
* needed. Listed here because shared-types ships with these by default.
|
|
15
|
-
*/
|
|
16
|
-
declare const CURRENCIES: {
|
|
17
|
-
readonly USD: "USD";
|
|
18
|
-
readonly EUR: "EUR";
|
|
19
|
-
readonly GBP: "GBP";
|
|
20
|
-
readonly JPY: "JPY";
|
|
21
|
-
readonly CNY: "CNY";
|
|
22
|
-
readonly INR: "INR";
|
|
23
|
-
readonly BDT: "BDT";
|
|
24
|
-
readonly AED: "AED";
|
|
25
|
-
readonly SAR: "SAR";
|
|
26
|
-
readonly PKR: "PKR";
|
|
27
|
-
readonly NPR: "NPR";
|
|
28
|
-
readonly LKR: "LKR";
|
|
29
|
-
readonly MYR: "MYR";
|
|
30
|
-
readonly SGD: "SGD";
|
|
31
|
-
readonly AUD: "AUD";
|
|
32
|
-
readonly CAD: "CAD";
|
|
33
|
-
readonly CHF: "CHF";
|
|
34
|
-
readonly HKD: "HKD";
|
|
35
|
-
readonly THB: "THB";
|
|
36
|
-
readonly IDR: "IDR";
|
|
37
|
-
};
|
|
38
|
-
/**
|
|
39
|
-
* Number of minor units per major unit for common currencies.
|
|
40
|
-
*
|
|
41
|
-
* ISO 4217 defines most currencies with 2 decimal places (cents). A few are
|
|
42
|
-
* zero-decimal (JPY, KRW) or three-decimal (BHD, KWD, JOD). When a currency
|
|
43
|
-
* isn't in this table, {@link minorUnitFactor} returns 100 as the safe default.
|
|
44
|
-
*/
|
|
45
|
-
declare const MINOR_UNIT_FACTOR: Readonly<Record<string, number>>;
|
|
46
|
-
/**
|
|
47
|
-
* Narrow a raw string to a {@link CurrencyCode}. Returns `null` if the string
|
|
48
|
-
* is not a valid ISO 4217 format (3 uppercase letters). This does not verify
|
|
49
|
-
* the code is registered with ISO — use your own whitelist for that.
|
|
50
|
-
*/
|
|
51
|
-
declare function toCurrencyCode(value: string): CurrencyCode | null;
|
|
52
|
-
/** Type predicate for `unknown` input. */
|
|
53
|
-
declare function isCurrencyCode(value: unknown): value is CurrencyCode;
|
|
54
|
-
/**
|
|
55
|
-
* Minor units per major unit (e.g. 100 for USD, 1 for JPY, 1000 for KWD).
|
|
56
|
-
* Falls back to 100 when the currency is unknown — matches the ISO 4217 default.
|
|
57
|
-
*/
|
|
58
|
-
declare function minorUnitFactor(currency: string): number;
|
|
59
|
-
/**
|
|
60
|
-
* Frozen FX conversion record — the cost of record for cross-currency
|
|
61
|
-
* transactions. Stored alongside a base-currency amount at the seam where
|
|
62
|
-
* currency crossed (procurement receipt, landed-cost entry, foreign refund).
|
|
63
|
-
*
|
|
64
|
-
* Invariant: once written, never mutated. Revaluation is a new snapshot on
|
|
65
|
-
* a new document — rewriting an existing snapshot corrupts audit history.
|
|
66
|
-
*
|
|
67
|
-
* `rate` is expressed as: 1 sourceCurrency = rate * baseCurrency
|
|
68
|
-
* (e.g. USD→BDT at 110 means rate = 110, so sourceAmount 10 USD → baseAmount 1100 BDT).
|
|
69
|
-
* This matches how banks quote — the source is the "from" currency.
|
|
70
|
-
*/
|
|
71
|
-
interface FxSnapshot {
|
|
72
|
-
/** Currency the transaction was invoiced / quoted in. ISO 4217. */
|
|
73
|
-
sourceCurrency: string;
|
|
74
|
-
/** Organisation's functional currency the base amount is expressed in. */
|
|
75
|
-
baseCurrency: string;
|
|
76
|
-
/** 1 unit of sourceCurrency equals this many units of baseCurrency. */
|
|
77
|
-
rate: number;
|
|
78
|
-
/** When the rate was captured — NOT when the document was last touched. */
|
|
79
|
-
snapshotAt: Date;
|
|
80
|
-
/**
|
|
81
|
-
* Optional provenance tag — e.g. 'manual', 'bangladesh-bank',
|
|
82
|
-
* 'openexchangerates.org', 'supplier-invoice'. Free-form; hosts use it for
|
|
83
|
-
* audit filtering and re-pricing policy.
|
|
84
|
-
*/
|
|
85
|
-
source?: string;
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Apply an {@link FxSnapshot} to a source-currency amount. Returns the
|
|
89
|
-
* equivalent base-currency amount. No rounding — callers that persist into
|
|
90
|
-
* an integer minor-unit column must round explicitly with a deterministic
|
|
91
|
-
* rule (`Math.round`, banker's rounding, floor for duty-conservative
|
|
92
|
-
* postings, etc.).
|
|
93
|
-
*/
|
|
94
|
-
declare function convertWithSnapshot(sourceAmount: number, fx: FxSnapshot): number;
|
|
95
|
-
/**
|
|
96
|
-
* Reverse an {@link FxSnapshot} — reconstruct the source-currency amount
|
|
97
|
-
* from a stored base amount. Useful for reporting "cost in original
|
|
98
|
-
* currency". Not round-trip safe under rounding; prefer storing the
|
|
99
|
-
* original source amount alongside the base amount when audit fidelity
|
|
100
|
-
* matters.
|
|
101
|
-
*/
|
|
102
|
-
declare function reverseWithSnapshot(baseAmount: number, fx: FxSnapshot): number;
|
|
103
|
-
/**
|
|
104
|
-
* Structural validation — checks shape only. Does NOT verify the rate is
|
|
105
|
-
* economically sensible (8.5 USD→BDT would pass this check). Host-level
|
|
106
|
-
* guards / anomaly detection handle that.
|
|
107
|
-
*/
|
|
108
|
-
declare function isFxSnapshot(value: unknown): value is FxSnapshot;
|
|
109
|
-
//#endregion
|
|
1
|
+
import { a as convertWithSnapshot, c as minorUnitFactor, i as MINOR_UNIT_FACTOR, l as reverseWithSnapshot, n as CurrencyCode, o as isCurrencyCode, r as FxSnapshot, s as isFxSnapshot, t as CURRENCIES, u as toCurrencyCode } from "./currency-rgUAn5KM.mjs";
|
|
110
2
|
export { CURRENCIES, CurrencyCode, FxSnapshot, MINOR_UNIT_FACTOR, convertWithSnapshot, isCurrencyCode, isFxSnapshot, minorUnitFactor, reverseWithSnapshot, toCurrencyCode };
|
package/dist/money.d.mts
CHANGED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { Money } from "./money.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/payment-gateway.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Static capability declaration — providers expose this so the engine
|
|
6
|
+
* can branch on what's supported (e.g. skip the refund button for
|
|
7
|
+
* cash providers, route 3DS-required cards to verification flows).
|
|
8
|
+
*/
|
|
9
|
+
interface ProviderCapabilities {
|
|
10
|
+
/** Provider exposes a webhook endpoint the host can register. */
|
|
11
|
+
supportsWebhooks: boolean;
|
|
12
|
+
/** Provider can reverse a captured charge. */
|
|
13
|
+
supportsRefunds: boolean;
|
|
14
|
+
/** Provider can refund less than the captured amount. */
|
|
15
|
+
supportsPartialRefunds: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Verification requires a human (admin marks "received") rather than
|
|
18
|
+
* an API call. True for cash, manual bank transfer, mobile-money
|
|
19
|
+
* outside an API window.
|
|
20
|
+
*/
|
|
21
|
+
requiresManualVerification: boolean;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Arguments to `provider.createIntent(...)`. Engine constructs this
|
|
25
|
+
* from the host's checkout payload and the resolved currency. The
|
|
26
|
+
* `metadata` blob is opaque pass-through — providers MAY forward it to
|
|
27
|
+
* the upstream gateway (Stripe attaches it to the PaymentIntent), but
|
|
28
|
+
* MUST NOT mutate or interpret it.
|
|
29
|
+
*/
|
|
30
|
+
interface CreateIntentParams {
|
|
31
|
+
/** Amount + currency. Integer minor units; provider converts to its native shape. */
|
|
32
|
+
amount: Money;
|
|
33
|
+
/** Host-side customer id, if any. Forwarded to the gateway. */
|
|
34
|
+
customerId?: string;
|
|
35
|
+
/** Where the customer returns after off-site payment (Stripe Checkout, PayPal). */
|
|
36
|
+
returnUrl?: string;
|
|
37
|
+
/** Pass-through metadata. Visible on the gateway dashboard / webhook events. */
|
|
38
|
+
metadata?: Record<string, unknown>;
|
|
39
|
+
/**
|
|
40
|
+
* Provider-specific knobs. Each gateway documents its own keys —
|
|
41
|
+
* Stripe has `payment_method_types`, SSLCommerz has `tran_id`, bKash
|
|
42
|
+
* has `intent`, etc. Engine doesn't peek inside.
|
|
43
|
+
*/
|
|
44
|
+
[key: string]: unknown;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* What `provider.createIntent(...)` returns. The engine persists
|
|
48
|
+
* `id` / `paymentIntentId` / `sessionId` on the Transaction's
|
|
49
|
+
* `gateway` block so subsequent `verify` / webhook lookups can find
|
|
50
|
+
* the row.
|
|
51
|
+
*/
|
|
52
|
+
interface PaymentIntent {
|
|
53
|
+
/** Unique gateway-scoped id for this intent (`pi_xxx`, `ch_xxx`, etc.). */
|
|
54
|
+
id: string;
|
|
55
|
+
/** Lowercase gateway name (`'stripe'`, `'razorpay'`, `'sslcommerz'`, `'bkash'`, `'manual'`). */
|
|
56
|
+
provider: string;
|
|
57
|
+
/**
|
|
58
|
+
* Lifecycle status as the gateway reports it. Free-form string —
|
|
59
|
+
* engine maps known values to its own state machine. Common values:
|
|
60
|
+
* `'pending'`, `'processing'`, `'requires_action'`, `'succeeded'`,
|
|
61
|
+
* `'failed'`, `'cancelled'`.
|
|
62
|
+
*/
|
|
63
|
+
status: string;
|
|
64
|
+
/** Amount + currency that the intent locks in. */
|
|
65
|
+
amount: Money;
|
|
66
|
+
/**
|
|
67
|
+
* Session token for hosted checkout flows (Stripe Checkout `session_id`,
|
|
68
|
+
* SSLCommerz `sessionkey`, bKash `paymentID`). When the gateway
|
|
69
|
+
* uses a redirect-style flow this is the lookup key.
|
|
70
|
+
*/
|
|
71
|
+
sessionId?: string | null;
|
|
72
|
+
/**
|
|
73
|
+
* Stripe-style `pi_xxx` payment-intent id. May equal `id` for
|
|
74
|
+
* gateways that don't distinguish session vs intent.
|
|
75
|
+
*/
|
|
76
|
+
paymentIntentId?: string | null;
|
|
77
|
+
/** Frontend hand-off — Stripe Elements / Razorpay Checkout consume this. */
|
|
78
|
+
clientSecret?: string;
|
|
79
|
+
/** Off-site redirect URL (PayPal `approve` link, SSLCommerz GatewayPageURL). */
|
|
80
|
+
paymentUrl?: string;
|
|
81
|
+
/** Manual-flow instructions ("transfer to A/c 1234, reference X"). */
|
|
82
|
+
instructions?: string;
|
|
83
|
+
/** Pass-through metadata echoed from the create call. */
|
|
84
|
+
metadata?: Record<string, unknown>;
|
|
85
|
+
/** Raw provider response — kept for audit. Consumers MUST NOT rely on shape. */
|
|
86
|
+
raw?: unknown;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* What `provider.verifyPayment(intentId)` returns. The engine maps
|
|
90
|
+
* this to its `TRANSACTION_STATUS` state machine.
|
|
91
|
+
*/
|
|
92
|
+
interface PaymentResult {
|
|
93
|
+
/** Gateway-scoped id (intent id, charge id, tran id — whatever the gateway uses). */
|
|
94
|
+
id: string;
|
|
95
|
+
/** Lowercase gateway name. */
|
|
96
|
+
provider: string;
|
|
97
|
+
/**
|
|
98
|
+
* Closed status set — narrower than `PaymentIntent.status` because
|
|
99
|
+
* verification has settled the lifecycle.
|
|
100
|
+
*
|
|
101
|
+
* - `'succeeded'` — payment captured, funds owed to merchant.
|
|
102
|
+
* - `'failed'` — terminal failure. Engine moves Transaction to FAILED.
|
|
103
|
+
* - `'processing'` — async processing (ACH, e-wallet pending bank).
|
|
104
|
+
* - `'requires_action'` — 3DS, OTP, or out-of-band approval pending.
|
|
105
|
+
*/
|
|
106
|
+
status: 'succeeded' | 'failed' | 'processing' | 'requires_action';
|
|
107
|
+
/** Captured amount as the gateway reports it (may differ from intent on partial capture). */
|
|
108
|
+
amount?: Money;
|
|
109
|
+
/** When the gateway booked the capture. */
|
|
110
|
+
paidAt?: Date;
|
|
111
|
+
/** Pass-through metadata (gateway customer id, fee breakdown, etc.). */
|
|
112
|
+
metadata?: Record<string, unknown>;
|
|
113
|
+
/** Raw response — audit only. */
|
|
114
|
+
raw?: unknown;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* What `provider.refund(...)` returns. Successful refunds become a
|
|
118
|
+
* new `'refund'` Transaction (flow: `'outflow'`) on the engine side.
|
|
119
|
+
*/
|
|
120
|
+
interface RefundResult {
|
|
121
|
+
/** Gateway-scoped refund id (Stripe `re_xxx`, etc.). */
|
|
122
|
+
id: string;
|
|
123
|
+
/** Lowercase gateway name. */
|
|
124
|
+
provider: string;
|
|
125
|
+
status: 'succeeded' | 'failed' | 'processing';
|
|
126
|
+
/** Amount reversed, as the gateway reports it. May be partial. */
|
|
127
|
+
amount?: Money;
|
|
128
|
+
/** When the gateway booked the refund. */
|
|
129
|
+
refundedAt?: Date;
|
|
130
|
+
/** Reason string forwarded from the engine call (or filled by the gateway). */
|
|
131
|
+
reason?: string;
|
|
132
|
+
metadata?: Record<string, unknown>;
|
|
133
|
+
raw?: unknown;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Parsed webhook event from a gateway's HTTP delivery. Engine stamps
|
|
137
|
+
* `id` on the Transaction's `webhook.eventId` for atomic dedup —
|
|
138
|
+
* replays of the same `id` are no-ops.
|
|
139
|
+
*/
|
|
140
|
+
interface WebhookEvent {
|
|
141
|
+
/**
|
|
142
|
+
* Gateway-scoped event id (Stripe `evt_xxx`, Razorpay
|
|
143
|
+
* `webhook_event_id`). Used as the dedup key — repeating delivery
|
|
144
|
+
* attempts MUST share the same id.
|
|
145
|
+
*/
|
|
146
|
+
id: string;
|
|
147
|
+
/** Lowercase gateway name. */
|
|
148
|
+
provider: string;
|
|
149
|
+
/** Event type (`'payment.succeeded'`, `'charge.refunded'`, etc.). */
|
|
150
|
+
type: string;
|
|
151
|
+
/**
|
|
152
|
+
* Routing fields the engine uses to find the matching Transaction:
|
|
153
|
+
* - `sessionId` — for redirect-style flows.
|
|
154
|
+
* - `paymentIntentId` — for Stripe-style flows.
|
|
155
|
+
* Plus arbitrary additional gateway data.
|
|
156
|
+
*/
|
|
157
|
+
data: {
|
|
158
|
+
sessionId?: string;
|
|
159
|
+
paymentIntentId?: string;
|
|
160
|
+
[key: string]: unknown;
|
|
161
|
+
};
|
|
162
|
+
/** When the gateway emitted the event. */
|
|
163
|
+
createdAt?: Date;
|
|
164
|
+
/** Raw provider payload — audit only. */
|
|
165
|
+
raw?: unknown;
|
|
166
|
+
}
|
|
167
|
+
//#endregion
|
|
168
|
+
export { CreateIntentParams, PaymentIntent, PaymentResult, ProviderCapabilities, RefundResult, WebhookEvent };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@classytic/primitives",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Framework-agnostic TypeScript primitives shared across Classytic packages — Money, events, pagination, tenant config, and other building blocks.",
|
|
5
5
|
"author": "Classytic",
|
|
6
6
|
"license": "MIT",
|
|
@@ -89,6 +89,14 @@
|
|
|
89
89
|
"types": "./dist/state-machine.d.mts",
|
|
90
90
|
"default": "./dist/state-machine.mjs"
|
|
91
91
|
},
|
|
92
|
+
"./bank-transaction": {
|
|
93
|
+
"types": "./dist/bank-transaction.d.mts",
|
|
94
|
+
"default": "./dist/bank-transaction.mjs"
|
|
95
|
+
},
|
|
96
|
+
"./payment-gateway": {
|
|
97
|
+
"types": "./dist/payment-gateway.d.mts",
|
|
98
|
+
"default": "./dist/payment-gateway.mjs"
|
|
99
|
+
},
|
|
92
100
|
"./package.json": "./package.json"
|
|
93
101
|
},
|
|
94
102
|
"files": [
|