@classytic/loyalty 0.4.0 → 0.6.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 +106 -0
- package/LICENSE +75 -21
- package/README.md +2 -2
- package/dist/{config-Dtun1duw.d.mts → config-CtkGj3xZ.d.mts} +41 -22
- package/dist/{constants-DkQYkOK1.mjs → constants-DOPBh6kt.mjs} +1 -3
- package/dist/domain/enums/index.d.mts +1 -1
- package/dist/domain/enums/index.mjs +1 -1
- package/dist/events/index.d.mts +2 -2
- package/dist/events/index.mjs +2 -2
- package/dist/{events-DN5_p6T0.mjs → events-BLjg3uLA.mjs} +22 -31
- package/dist/{index-Dngaj21_.d.mts → index-CR2k7jQ8.d.mts} +81 -14
- package/dist/{index-YSsi4e_G.d.mts → index-Cp_sXNpG.d.mts} +35 -4
- package/dist/index-D6GvuvmF.d.mts +692 -0
- package/dist/{index-D_5Ojc2c.d.mts → index-YUFamhDk.d.mts} +7 -17
- package/dist/index.d.mts +60 -29
- package/dist/index.mjs +99 -52
- package/dist/models/index.d.mts +3 -1273
- package/dist/models/index.mjs +2 -2
- package/dist/{models-Ddh62lgO.mjs → models-weWqS5Bc.mjs} +152 -70
- package/dist/notifications/index.d.mts +1 -2
- package/dist/repositories/index.d.mts +1 -1
- package/dist/repositories/index.mjs +1 -1
- package/dist/{repositories-DEwCbLCh.mjs → repositories-B676z4bE.mjs} +183 -67
- package/dist/types/index.d.mts +3 -3
- package/package.json +12 -9
- /package/dist/{index-DMK8a98J.d.mts → index-D_Oe3aDk.d.mts} +0 -0
- /package/dist/{loyalty-events-C38JA3cU.mjs → loyalty-events-DXirolM_.mjs} +0 -0
- /package/dist/{notification.port-CJ9nXj0k.d.mts → notification.port-wY4IKimz.d.mts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,112 @@
|
|
|
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.6.0] — 2026-08-17
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- `SessionNotInTransactionError` — thrown when a host passes `ctx.session` that has no active transaction; a bare session provides no atomicity and would leave the points mutation standing if the outbox save fails
|
|
10
|
+
- `ResolvedTenant` type alias (deprecated shim for `ResolvedTenantConfig` from `@classytic/repo-core/tenant`)
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- `TenantConfig` widened to `RepoTenantConfig | boolean` (was `false | PrimitivesTenantConfig`); hosts rename `field` → `tenantField` and `type` → `fieldType`
|
|
14
|
+
- `in-process-bus` consolidated onto primitives `InProcessEventBus`; event catalog, dispatch, and repository types aligned
|
|
15
|
+
- Schemas and repositories updated: earning-rule, redemption, tier-definition, point-transaction, referral
|
|
16
|
+
- `src/index.ts` exports reorganised for tree-shaking
|
|
17
|
+
|
|
18
|
+
## 1.0.0 - 2026-07-29
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
- **License:** relicensed from MIT to the **Classytic Source-Available License** (Community & Commercial). Evaluation/development use remains free; production use now requires a commercial license from Classytic LLC. See `LICENSE`.
|
|
22
|
+
- Major bump marks the license change; versions published before 1.0.0 remain under their original MIT terms.
|
|
23
|
+
|
|
24
|
+
## 0.7.1 - 2026-07-22 (unpublished)
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
- Internal: `InProcess*Bus` is now a thin subclass of `@classytic/primitives/event-infra`'s shared `InProcessEventBus` (dedup / error-isolation / close semantics unchanged); `MemoryOutboxStore` re-exported from the shared runtime where applicable. No public API, event-name, or wire-envelope change. Requires `@classytic/primitives >=0.13.0`.
|
|
28
|
+
|
|
29
|
+
## [0.7.0] — unpublished (2026-07-22)
|
|
30
|
+
|
|
31
|
+
### Added — engine lifecycle: `destroy()`
|
|
32
|
+
|
|
33
|
+
- **`LoyaltyEngine.destroy()`** — closes the self-created
|
|
34
|
+
`InProcessLoyaltyBus` (skipped when the host injected `eventTransport`).
|
|
35
|
+
- `destroy()` follows the fleet ownsBus convention (manufacturing/purchase
|
|
36
|
+
pattern): only what the engine itself created is closed; a host-injected
|
|
37
|
+
transport is the host's to close. Idempotent — a second call is a no-op.
|
|
38
|
+
Mongoose models/connection are untouched (host-owned).
|
|
39
|
+
- Lifecycle coverage in `tests/integration/engine-lifecycle.test.ts`.
|
|
40
|
+
|
|
41
|
+
## [0.6.0] — 2026-07-20
|
|
42
|
+
|
|
43
|
+
### Changed — money contract: integer MINOR units + explicit rates, no major-unit ambiguity (BREAKING)
|
|
44
|
+
|
|
45
|
+
Every monetary number in loyalty was an ambiguous major-currency float
|
|
46
|
+
(unenforced), and both earning rates carried an implicit "per 1 MAJOR unit"
|
|
47
|
+
basis. Aligned with `@classytic/flow@0.6.0`'s TOTAL-vs-RATE standardization
|
|
48
|
+
(see flow `wiki/conventions.md` §15). Loyalty stays ambient-single-currency —
|
|
49
|
+
NO currency field on any doc or API; the host owns currency.
|
|
50
|
+
|
|
51
|
+
- **All money AMOUNTS are now integers in the host's minor unit** (paisa for
|
|
52
|
+
BDT), enforced by `mongooseMinorUnits()` from the new
|
|
53
|
+
**`@classytic/validation` dependency (`>=0.2.0`)**:
|
|
54
|
+
- `EarningConditions.minOrderAmount`
|
|
55
|
+
- `LoyaltyConfig.redemption.minOrderAmount` (`DEFAULT_MIN_ORDER_AMOUNT`
|
|
56
|
+
stays `0` — zero is unit-invariant)
|
|
57
|
+
- `Redemption.discountAmount`, `Redemption.initialBalance`,
|
|
58
|
+
`Redemption.remainingBalance`
|
|
59
|
+
- `TierQualification.minLifetimeSpend` (declared-only field — tier checks
|
|
60
|
+
still use lifetime POINTS; re-documented + validated, no math change)
|
|
61
|
+
- API inputs: `evaluateOrder({ orderTotal, items[].amount })` and
|
|
62
|
+
`validate`/`reserve({ orderTotal })`. `validateOrderTotal` now requires a
|
|
63
|
+
non-negative **safe integer** ("orderTotal must be a non-negative safe
|
|
64
|
+
integer (minor units)") — floats and majors fail loud.
|
|
65
|
+
- **`EarningReward.amountPerPoint`** is now MINOR UNITS PER POINT (integer
|
|
66
|
+
≥ 1, bespoke rate validator). "1 point per ৳100" = `amountPerPoint: 10000`.
|
|
67
|
+
- **`EarningReward.pointsPerAmount` requires a new sibling `rateBasis`**
|
|
68
|
+
(integer minor units ≥ 1): `points = pointsPerAmount × (orderTotal /
|
|
69
|
+
rateBasis)`, rounded ONCE per the rule's `roundingMode`. The old implicit
|
|
70
|
+
"points per 1 MAJOR unit" basis was the bug being killed. Pairing enforced
|
|
71
|
+
at the schema level (pre-validate) AND by a runtime `ValidationError` guard
|
|
72
|
+
in the calculators (legacy/hand-written docs fail loud).
|
|
73
|
+
- **`conversionRate` (points per 1 MAJOR unit) is REPLACED by
|
|
74
|
+
`pointValueMinor`** (integer minor units ONE point redeems for, ≥ 1) on
|
|
75
|
+
`LoyaltyConfig.program`, `ResolvedConfig`, and the `Redemption` doc snapshot.
|
|
76
|
+
`conversionRate: 10` (10 pts per ৳1) ≡ `pointValueMinor: 10` (1 pt = 10
|
|
77
|
+
paisa). `DEFAULT_CONVERSION_RATE` → `DEFAULT_POINT_VALUE_MINOR` (still `10`
|
|
78
|
+
— same economic default). Boot validation: "pointValueMinor must be a
|
|
79
|
+
positive integer (minor units per point)".
|
|
80
|
+
- **Redemption math is now EXACT integer arithmetic** (no more floor-division
|
|
81
|
+
remainder): `requestedDiscountMinor = pointsToRedeem × pointValueMinor`;
|
|
82
|
+
percent cap `maxDiscountMinor = floor(orderTotal × maxRedeemPercent / 100)`
|
|
83
|
+
unchanged in shape; when capped, `actualPoints = floor(cappedDiscount /
|
|
84
|
+
pointValueMinor)` and the discount is **recomputed as `actualPoints ×
|
|
85
|
+
pointValueMinor`** — the granted discount ALWAYS equals exactly the points
|
|
86
|
+
charged (no orphaned remainder paisa; documented invariant).
|
|
87
|
+
- **Event contract**: `RedemptionPayload.discountAmount` is integer minor
|
|
88
|
+
units; the Zod catalog schema now asserts `.int().nonnegative()` (Zod event
|
|
89
|
+
schemas are not tsc-checked against the TS interfaces — flow's lesson).
|
|
90
|
+
|
|
91
|
+
**Migration**: clean cutover, no dual-read shim. Re-express persisted majors
|
|
92
|
+
as minors (× minor-unit factor), rename `conversionRate` → `pointValueMinor`
|
|
93
|
+
(config + redemption docs), and add `rateBasis` to every rule that sets
|
|
94
|
+
`pointsPerAmount` (old implicit basis "per ৳1" = `rateBasis: 100`).
|
|
95
|
+
|
|
96
|
+
## [0.5.0] — unpublished (2026-07-15)
|
|
97
|
+
|
|
98
|
+
### Changed — mongokit 3.22 adopted (peer floors: mongokit >=3.22.0, repo-core >=0.13.0, primitives >=0.11.0)
|
|
99
|
+
|
|
100
|
+
- **Points ledger fence enforced** — `PointTransaction` rows are the
|
|
101
|
+
balance audit trail; `appendOnlyPlugin` now refuses every mutation
|
|
102
|
+
path (405 `APPEND_ONLY_VIOLATION`). Previously nothing enforced it —
|
|
103
|
+
a stray `update()` could rewrite an earn/burn row and silently
|
|
104
|
+
desync the audit trail from member balances. The expiry sweeper's
|
|
105
|
+
`expiredAt` stamp is the one engine-owned lifecycle write and passes
|
|
106
|
+
the audited `bypassAppendOnly` escape.
|
|
107
|
+
- No FSM boilerplate existed (no assert+claim sites, no
|
|
108
|
+
read-modify-write history) — loyalty was already clean there; this
|
|
109
|
+
release is the fence + kit refresh (clean registry install, no
|
|
110
|
+
legacy flags).
|
|
111
|
+
|
|
6
112
|
## [0.4.0] — addendum (2026-07-05)
|
|
7
113
|
|
|
8
114
|
### Changed (behavioral)
|
package/LICENSE
CHANGED
|
@@ -1,21 +1,75 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Classytic
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
1
|
+
Classytic Source-Available License (Community & Commercial), Version 1.0
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Classytic LLC. All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software is source-available, not open source. Please read this License
|
|
6
|
+
before using it.
|
|
7
|
+
|
|
8
|
+
1. DEFINITIONS
|
|
9
|
+
"Software" means the contents of this package and any accompanying source
|
|
10
|
+
code, object code, or documentation, in any form.
|
|
11
|
+
"Licensor" means Classytic LLC, a limited liability company organized in the
|
|
12
|
+
United States.
|
|
13
|
+
"You" means the individual or legal entity exercising rights under this
|
|
14
|
+
License.
|
|
15
|
+
"Evaluation Use" means use for evaluation, development, testing,
|
|
16
|
+
experimentation, or internal non-commercial purposes only.
|
|
17
|
+
"Production Use" means any use of the Software that is live, commercial,
|
|
18
|
+
revenue-generating, or customer-facing, or any use other than Evaluation Use.
|
|
19
|
+
"Commercial License" means a separate written agreement executed with
|
|
20
|
+
Licensor that grants Production Use rights.
|
|
21
|
+
|
|
22
|
+
2. OWNERSHIP
|
|
23
|
+
The Software is licensed, not sold. Licensor retains all right, title, and
|
|
24
|
+
interest in and to the Software, including all intellectual property rights.
|
|
25
|
+
All rights not expressly granted in this License are reserved by Licensor.
|
|
26
|
+
|
|
27
|
+
3. EVALUATION GRANT
|
|
28
|
+
Subject to Your compliance with this License, Licensor grants You a
|
|
29
|
+
worldwide, royalty-free, non-exclusive, non-transferable, revocable license
|
|
30
|
+
to download, install, run, and modify the Software solely for Evaluation Use.
|
|
31
|
+
|
|
32
|
+
4. PRODUCTION USE REQUIRES A COMMERCIAL LICENSE
|
|
33
|
+
Production Use of the Software is NOT permitted under this License and
|
|
34
|
+
requires a Commercial License from Licensor. A Commercial License may be
|
|
35
|
+
granted at Licensor's discretion, with or without a fee, and may confer
|
|
36
|
+
perpetual, irrevocable, and transferable Production Use rights as set out in
|
|
37
|
+
that agreement. Contact licensing@classytic.com to obtain one.
|
|
38
|
+
|
|
39
|
+
5. RESTRICTIONS
|
|
40
|
+
You may not:
|
|
41
|
+
(a) make any Production Use of the Software without a Commercial License;
|
|
42
|
+
(b) redistribute, resell, rent, sublicense, or otherwise make the Software
|
|
43
|
+
(in whole or in part, original or modified) available to third parties as
|
|
44
|
+
a stand-alone product, framework, library, or hosted/managed service that
|
|
45
|
+
competes with the Software or with Licensor;
|
|
46
|
+
(c) remove, obscure, or alter any copyright, license, or attribution notice.
|
|
47
|
+
|
|
48
|
+
6. FEEDBACK AND CONTRIBUTIONS
|
|
49
|
+
Any feedback, suggestions, or contributions You provide relating to the
|
|
50
|
+
Software may be used by Licensor for any purpose without obligation to You.
|
|
51
|
+
|
|
52
|
+
7. TERMINATION
|
|
53
|
+
This License and the Evaluation Grant terminate automatically if You breach
|
|
54
|
+
any term. Upon termination You must cease all use of the Software and destroy
|
|
55
|
+
all copies in Your possession. Rights under a Commercial License are governed
|
|
56
|
+
by that agreement's own terms and survive termination of this License to the
|
|
57
|
+
extent stated there.
|
|
58
|
+
|
|
59
|
+
8. DISCLAIMER OF WARRANTY
|
|
60
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
61
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
62
|
+
FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT.
|
|
63
|
+
|
|
64
|
+
9. LIMITATION OF LIABILITY
|
|
65
|
+
TO THE MAXIMUM EXTENT PERMITTED BY LAW, IN NO EVENT SHALL LICENSOR BE LIABLE
|
|
66
|
+
FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
67
|
+
TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE
|
|
68
|
+
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
69
|
+
|
|
70
|
+
10. GOVERNING LAW
|
|
71
|
+
This License is governed by the laws of the State of Delaware, United States,
|
|
72
|
+
without regard to its conflict-of-laws principles. (Adjust to Your LLC's state
|
|
73
|
+
of formation.)
|
|
74
|
+
|
|
75
|
+
For commercial licensing, contact: licensing@classytic.com
|
package/README.md
CHANGED
|
@@ -22,8 +22,8 @@ import mongoose from 'mongoose';
|
|
|
22
22
|
const engine = createLoyaltyEngine({
|
|
23
23
|
mongoose: mongoose.connection,
|
|
24
24
|
tenant: false, // single-tenant (or { field: 'orgId' } for multi-tenant)
|
|
25
|
-
program: {
|
|
26
|
-
redemption: { minPoints: 100, maxRedeemPercent: 50 },
|
|
25
|
+
program: { pointValueMinor: 10 }, // 1 point redeems for 10 minor units (e.g. 10 paisa)
|
|
26
|
+
redemption: { minPoints: 100, maxRedeemPercent: 50 }, // money amounts are integer MINOR units
|
|
27
27
|
referral: { referrerRewardPoints: 200, refereeRewardPoints: 100 },
|
|
28
28
|
});
|
|
29
29
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { t as NotificationAdapter } from "./notification.port-
|
|
1
|
+
import { t as NotificationAdapter } from "./notification.port-wY4IKimz.mjs";
|
|
2
|
+
import { ResolvedTenantConfig, TenantConfig } from "@classytic/repo-core/tenant";
|
|
2
3
|
import { EventTransport } from "@classytic/primitives/events";
|
|
3
|
-
import { Connection } from "mongoose";
|
|
4
4
|
import { OutboxStore } from "@classytic/primitives/outbox";
|
|
5
|
-
import { TenantConfig, TenantFieldType } from "@classytic/repo-core/tenant";
|
|
6
|
-
|
|
7
5
|
//#region src/types/config.d.ts
|
|
8
6
|
type LoyaltyModelName = 'Member' | 'PointTransaction' | 'EarningRule' | 'TierDefinition' | 'Redemption' | 'Referral';
|
|
9
|
-
type TenantConfig$1 =
|
|
7
|
+
type TenantConfig$1 = TenantConfig | boolean;
|
|
8
|
+
/** @deprecated Use `ResolvedTenantConfig` from `@classytic/repo-core/tenant`. */
|
|
9
|
+
type ResolvedTenant = ResolvedTenantConfig;
|
|
10
10
|
interface IndexDefinition {
|
|
11
11
|
fields: Record<string, 1 | -1>;
|
|
12
12
|
options?: Record<string, unknown> | undefined;
|
|
@@ -21,8 +21,7 @@ interface RepositoryPlugins {
|
|
|
21
21
|
redemption?: PluginType[] | undefined;
|
|
22
22
|
referral?: PluginType[] | undefined;
|
|
23
23
|
}
|
|
24
|
-
interface
|
|
25
|
-
mongoose: Connection;
|
|
24
|
+
interface LoyaltyShape {
|
|
26
25
|
tenant?: TenantConfig$1 | undefined;
|
|
27
26
|
/**
|
|
28
27
|
* IANA timezone name (e.g. `'Asia/Dhaka'`, `'America/New_York'`) used to
|
|
@@ -37,10 +36,22 @@ interface LoyaltyConfig {
|
|
|
37
36
|
timezone?: string | undefined;
|
|
38
37
|
program?: {
|
|
39
38
|
id?: string | undefined;
|
|
40
|
-
|
|
39
|
+
/**
|
|
40
|
+
* RATE: integer minor units ONE point redeems for (≥ 1) in the host's
|
|
41
|
+
* ambient currency. `pointValueMinor: 10` = 1 point → 10 paisa
|
|
42
|
+
* (৳0.10, i.e. the old `conversionRate: 10` "10 points per ৳1").
|
|
43
|
+
* Replaces `conversionRate` (0.5.x), whose implicit per-1-MAJOR-unit
|
|
44
|
+
* basis was ambiguous.
|
|
45
|
+
*/
|
|
46
|
+
pointValueMinor?: number | undefined;
|
|
41
47
|
} | undefined;
|
|
42
48
|
redemption?: {
|
|
43
49
|
minPoints?: number | undefined;
|
|
50
|
+
/**
|
|
51
|
+
* Minimum order total eligible for redemption — **integer minor
|
|
52
|
+
* units** (paisa for BDT). Compared against `validate()`'s
|
|
53
|
+
* `orderTotal` (also minor units).
|
|
54
|
+
*/
|
|
44
55
|
minOrderAmount?: number | undefined;
|
|
45
56
|
maxRedeemPercent?: number | undefined;
|
|
46
57
|
reservationTtlMinutes?: number | undefined;
|
|
@@ -85,15 +96,18 @@ interface LoyaltyConfig {
|
|
|
85
96
|
/**
|
|
86
97
|
* When true, existing Mongoose models with loyalty's names are deleted
|
|
87
98
|
* from the connection before re-registering. Hot-reload / test fixtures
|
|
88
|
-
* only. Default `false` — collision throws `
|
|
99
|
+
* only. Default `false` — collision throws mongokit's `ModelCollisionError`.
|
|
89
100
|
* Hosts that need two loyalty engines should use two Mongoose connections
|
|
90
101
|
* (`mongoose.createConnection(...)`). See PACKAGE_RULES.md §21.
|
|
91
102
|
*/
|
|
92
103
|
forceRecreate?: boolean | undefined;
|
|
104
|
+
}
|
|
105
|
+
interface LoyaltyRuntime {
|
|
93
106
|
/**
|
|
94
|
-
* Arc-compatible event transport. When omitted, the engine
|
|
95
|
-
* `InProcessLoyaltyBus` — a ~60-line structural
|
|
96
|
-
* `MemoryEventTransport`.
|
|
107
|
+
* Arc-compatible event transport. When omitted, the engine creates (and
|
|
108
|
+
* OWNS, so `close()` closes) an `InProcessLoyaltyBus` — a ~60-line structural
|
|
109
|
+
* match of arc's `MemoryEventTransport`. A host-supplied transport is the
|
|
110
|
+
* host's to close. See PACKAGE_RULES §13.
|
|
97
111
|
*/
|
|
98
112
|
eventTransport?: EventTransport | undefined;
|
|
99
113
|
/**
|
|
@@ -120,19 +134,24 @@ interface LoyaltyConfig {
|
|
|
120
134
|
* "Runtime capabilities".
|
|
121
135
|
*/
|
|
122
136
|
allowNonTransactional?: boolean | undefined;
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
137
|
+
/**
|
|
138
|
+
* Where the engine's boot warnings go — today, the `allowNonTransactional`
|
|
139
|
+
* downgrade notice. Defaults to `console`. A downgrade that carries
|
|
140
|
+
* partial-write risk on stored value must be VISIBLE, and a host that routes
|
|
141
|
+
* logs elsewhere would otherwise never see it.
|
|
142
|
+
*/
|
|
143
|
+
logger?: {
|
|
144
|
+
warn?(message: string, ...args: unknown[]): void;
|
|
145
|
+
error?(message: string, ...args: unknown[]): void;
|
|
146
|
+
} | undefined;
|
|
130
147
|
}
|
|
131
148
|
interface ResolvedConfig {
|
|
132
149
|
programId: string;
|
|
133
|
-
|
|
150
|
+
/** Integer minor units one point redeems for (≥ 1). See `LoyaltyShape.program.pointValueMinor`. */
|
|
151
|
+
pointValueMinor: number;
|
|
134
152
|
redemption: {
|
|
135
153
|
minPoints: number;
|
|
154
|
+
/** Integer minor units. */
|
|
136
155
|
minOrderAmount: number;
|
|
137
156
|
maxRedeemPercent: number;
|
|
138
157
|
reservationTtlMinutes: number;
|
|
@@ -153,7 +172,7 @@ interface ResolvedConfig {
|
|
|
153
172
|
};
|
|
154
173
|
/** Resolved IANA zone for wall-clock rule evaluation; `undefined` = UTC. */
|
|
155
174
|
timezone?: string;
|
|
156
|
-
tenant:
|
|
175
|
+
tenant: ResolvedTenantConfig;
|
|
157
176
|
}
|
|
158
177
|
//#endregion
|
|
159
|
-
export {
|
|
178
|
+
export { ModelName as a, ResolvedConfig as c, LoyaltyShape as i, ResolvedTenant as l, LoyaltyModelName as n, PluginType as o, LoyaltyRuntime as r, RepositoryPlugins as s, IndexDefinition as t, TenantConfig$1 as u };
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
//#region src/constants.ts
|
|
2
|
-
const DEFAULT_TENANT_FIELD = "organizationId";
|
|
3
|
-
const DEFAULT_TENANT_TYPE = "objectId";
|
|
4
2
|
const DEFAULT_PROGRAM_ID = "default";
|
|
5
3
|
const TRANSACTION_TYPES = Object.freeze([
|
|
6
4
|
"earn",
|
|
@@ -47,4 +45,4 @@ const EARNING_ACTIONS = Object.freeze([
|
|
|
47
45
|
"profile_complete"
|
|
48
46
|
]);
|
|
49
47
|
//#endregion
|
|
50
|
-
export {
|
|
48
|
+
export { REFERRAL_STATUSES as a, TRANSACTION_TYPES as c, REDEMPTION_STATUSES as i, EARNING_ACTIONS as n, RULE_STATUSES as o, MEMBER_STATUSES as r, RULE_TYPES as s, DEFAULT_PROGRAM_ID as t };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as RuleStatus, c as EARNING_ACTIONS, d as REFERRAL_STATUSES, f as RULE_STATUSES, i as ReferralStatus, l as MEMBER_STATUSES, m as TRANSACTION_TYPES, n as MemberStatus, o as RuleType, p as RULE_TYPES, r as RedemptionStatus, s as TransactionType, t as EarningAction, u as REDEMPTION_STATUSES } from "../../index-
|
|
1
|
+
import { a as RuleStatus, c as EARNING_ACTIONS, d as REFERRAL_STATUSES, f as RULE_STATUSES, i as ReferralStatus, l as MEMBER_STATUSES, m as TRANSACTION_TYPES, n as MemberStatus, o as RuleType, p as RULE_TYPES, r as RedemptionStatus, s as TransactionType, t as EarningAction, u as REDEMPTION_STATUSES } from "../../index-D_Oe3aDk.mjs";
|
|
2
2
|
export { EARNING_ACTIONS, EarningAction, MEMBER_STATUSES, MemberStatus, REDEMPTION_STATUSES, REFERRAL_STATUSES, RULE_STATUSES, RULE_TYPES, RedemptionStatus, ReferralStatus, RuleStatus, RuleType, TRANSACTION_TYPES, TransactionType };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as REFERRAL_STATUSES, c as TRANSACTION_TYPES, i as REDEMPTION_STATUSES, n as EARNING_ACTIONS, o as RULE_STATUSES, r as MEMBER_STATUSES, s as RULE_TYPES } from "../../constants-DOPBh6kt.mjs";
|
|
2
2
|
export { EARNING_ACTIONS, MEMBER_STATUSES, REDEMPTION_STATUSES, REFERRAL_STATUSES, RULE_STATUSES, RULE_TYPES, TRANSACTION_TYPES };
|
package/dist/events/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as PointsExpired, B as ReferralRewarded, C as MemberDeactivated, D as MultiTierReferralAwarded, E as MemberSuspended, F as RedemptionReserved, G as TierUpgraded, H as TierGraceStarted, I as RedemptionReversed, J as InProcessLoyaltyBusOptions, K as loyaltyEventDefinitions, L as ReferralApproved, M as PointsRedeemed, N as RedemptionConfirmed, O as PointsAdjusted, P as RedemptionReleased, R as ReferralRecorded, S as LoyaltyEventSchema, T as MemberReactivated, U as TierOverrideCleared, V as TierDowngraded, W as TierOverrideExpired, Y as createEvent, _ as TierChangedPayload, a as LoyaltyEventMap, b as LoyaltyEventDefinition, c as LoyaltyEvents, d as PointsAdjustedPayload, f as PointsEarnedPayload, g as ReferralPayload, h as RedemptionReversedPayload, i as CampaignAwardedPayload, j as PointsExpiringSoon, k as PointsEarned, l as MemberLifecyclePayload, m as RedemptionPayload, n as EventHandler, o as LoyaltyEventName, p as PointsExpiredPayload, q as InProcessLoyaltyBus, r as EventTransport, s as LoyaltyEventPayload, t as DomainEvent, u as MultiTierReferralAwardedPayload, v as TierOverrideLifecyclePayload, w as MemberEnrolled, x as LoyaltyEventPayloadOf, y as CampaignAwarded, z as ReferralRejected } from "../index-
|
|
2
|
-
export { CampaignAwarded, CampaignAwardedPayload, DomainEvent, EventHandler, EventTransport, InProcessLoyaltyBus, InProcessLoyaltyBusOptions, LoyaltyEventDefinition, LoyaltyEventMap, LoyaltyEventName, LoyaltyEventPayload, LoyaltyEventPayloadOf, LoyaltyEventSchema, LoyaltyEvents, MemberDeactivated, MemberEnrolled, MemberLifecyclePayload, MemberReactivated, MemberSuspended, MultiTierReferralAwarded, MultiTierReferralAwardedPayload, PointsAdjusted, PointsAdjustedPayload, PointsEarned, PointsEarnedPayload, PointsExpired, PointsExpiredPayload, PointsExpiringSoon, PointsRedeemed, RedemptionConfirmed, RedemptionPayload, RedemptionReleased, RedemptionReserved, RedemptionReversed, RedemptionReversedPayload, ReferralApproved, ReferralPayload, ReferralRecorded, ReferralRejected, ReferralRewarded, TierChangedPayload, TierDowngraded, TierGraceStarted, TierOverrideCleared, TierOverrideExpired, TierOverrideLifecyclePayload, TierUpgraded, createEvent, loyaltyEventDefinitions };
|
|
1
|
+
import { A as PointsExpired, B as ReferralRewarded, C as MemberDeactivated, D as MultiTierReferralAwarded, E as MemberSuspended, F as RedemptionReserved, G as TierUpgraded, H as TierGraceStarted, I as RedemptionReversed, J as InProcessLoyaltyBusOptions, K as loyaltyEventDefinitions, L as ReferralApproved, M as PointsRedeemed, N as RedemptionConfirmed, O as PointsAdjusted, P as RedemptionReleased, R as ReferralRecorded, S as LoyaltyEventSchema, T as MemberReactivated, U as TierOverrideCleared, V as TierDowngraded, W as TierOverrideExpired, Y as createEvent, _ as TierChangedPayload, a as LoyaltyEventMap, b as LoyaltyEventDefinition, c as LoyaltyEvents, d as PointsAdjustedPayload, f as PointsEarnedPayload, g as ReferralPayload, h as RedemptionReversedPayload, i as CampaignAwardedPayload, j as PointsExpiringSoon, k as PointsEarned, l as MemberLifecyclePayload, m as RedemptionPayload, n as EventHandler, o as LoyaltyEventName, p as PointsExpiredPayload, q as InProcessLoyaltyBus, r as EventTransport, s as LoyaltyEventPayload, t as DomainEvent, u as MultiTierReferralAwardedPayload, v as TierOverrideLifecyclePayload, w as MemberEnrolled, x as LoyaltyEventPayloadOf, y as CampaignAwarded, z as ReferralRejected } from "../index-YUFamhDk.mjs";
|
|
2
|
+
export { CampaignAwarded, type CampaignAwardedPayload, type DomainEvent, type EventHandler, type EventTransport, InProcessLoyaltyBus, type InProcessLoyaltyBusOptions, type LoyaltyEventDefinition, type LoyaltyEventMap, type LoyaltyEventName, type LoyaltyEventPayload, type LoyaltyEventPayloadOf, type LoyaltyEventSchema, LoyaltyEvents, MemberDeactivated, MemberEnrolled, type MemberLifecyclePayload, MemberReactivated, MemberSuspended, MultiTierReferralAwarded, type MultiTierReferralAwardedPayload, PointsAdjusted, type PointsAdjustedPayload, PointsEarned, type PointsEarnedPayload, PointsExpired, type PointsExpiredPayload, PointsExpiringSoon, PointsRedeemed, RedemptionConfirmed, type RedemptionPayload, RedemptionReleased, RedemptionReserved, RedemptionReversed, type RedemptionReversedPayload, ReferralApproved, type ReferralPayload, ReferralRecorded, ReferralRejected, ReferralRewarded, type TierChangedPayload, TierDowngraded, TierGraceStarted, TierOverrideCleared, TierOverrideExpired, type TierOverrideLifecyclePayload, TierUpgraded, createEvent, loyaltyEventDefinitions };
|
package/dist/events/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { C as TierOverrideExpired, E as InProcessLoyaltyBus, S as TierOverrideCleared, T as loyaltyEventDefinitions, _ as ReferralRecorded, a as MemberSuspended, b as TierDowngraded, c as PointsEarned, d as PointsRedeemed, f as RedemptionConfirmed, g as ReferralApproved, h as RedemptionReversed, i as MemberReactivated, l as PointsExpired, m as RedemptionReserved, n as MemberDeactivated, o as MultiTierReferralAwarded, p as RedemptionReleased, r as MemberEnrolled, s as PointsAdjusted, t as CampaignAwarded, u as PointsExpiringSoon, v as ReferralRejected, w as TierUpgraded, x as TierGraceStarted, y as ReferralRewarded } from "../events-
|
|
2
|
-
import { n as createEvent, t as LoyaltyEvents } from "../loyalty-events-
|
|
1
|
+
import { C as TierOverrideExpired, E as InProcessLoyaltyBus, S as TierOverrideCleared, T as loyaltyEventDefinitions, _ as ReferralRecorded, a as MemberSuspended, b as TierDowngraded, c as PointsEarned, d as PointsRedeemed, f as RedemptionConfirmed, g as ReferralApproved, h as RedemptionReversed, i as MemberReactivated, l as PointsExpired, m as RedemptionReserved, n as MemberDeactivated, o as MultiTierReferralAwarded, p as RedemptionReleased, r as MemberEnrolled, s as PointsAdjusted, t as CampaignAwarded, u as PointsExpiringSoon, v as ReferralRejected, w as TierUpgraded, x as TierGraceStarted, y as ReferralRewarded } from "../events-BLjg3uLA.mjs";
|
|
2
|
+
import { n as createEvent, t as LoyaltyEvents } from "../loyalty-events-DXirolM_.mjs";
|
|
3
3
|
export { CampaignAwarded, InProcessLoyaltyBus, LoyaltyEvents, MemberDeactivated, MemberEnrolled, MemberReactivated, MemberSuspended, MultiTierReferralAwarded, PointsAdjusted, PointsEarned, PointsExpired, PointsExpiringSoon, PointsRedeemed, RedemptionConfirmed, RedemptionReleased, RedemptionReserved, RedemptionReversed, ReferralApproved, ReferralRecorded, ReferralRejected, ReferralRewarded, TierDowngraded, TierGraceStarted, TierOverrideCleared, TierOverrideExpired, TierUpgraded, createEvent, loyaltyEventDefinitions };
|
|
@@ -1,35 +1,26 @@
|
|
|
1
|
-
import { t as LoyaltyEvents } from "./loyalty-events-
|
|
2
|
-
import {
|
|
1
|
+
import { t as LoyaltyEvents } from "./loyalty-events-DXirolM_.mjs";
|
|
2
|
+
import { InProcessEventBus } from "@classytic/primitives/event-infra";
|
|
3
|
+
import { createEvent } from "@classytic/primitives/events";
|
|
3
4
|
import { z } from "zod";
|
|
4
5
|
//#region src/events/in-process-bus.ts
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
this.handlers.get(pattern)?.add(handler);
|
|
24
|
-
return () => {
|
|
25
|
-
const set = this.handlers.get(pattern);
|
|
26
|
-
if (!set) return;
|
|
27
|
-
set.delete(handler);
|
|
28
|
-
if (set.size === 0) this.handlers.delete(pattern);
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
async close() {
|
|
32
|
-
this.handlers.clear();
|
|
6
|
+
/**
|
|
7
|
+
* In-process fan-out transport — the default when the host injects none.
|
|
8
|
+
*
|
|
9
|
+
* A thin subclass of `@classytic/primitives/event-infra`'s shared
|
|
10
|
+
* `InProcessEventBus` (the single implementation every kernel now defaults
|
|
11
|
+
* to). Prior semantics preserved exactly: glob matching via
|
|
12
|
+
* `matchEventPattern` (exact / `*` / `prefix.*` / `prefix:*`), Set-dedup so a
|
|
13
|
+
* handler matched by multiple patterns fires once, per-handler error
|
|
14
|
+
* isolation (errors logged; default `console`), idempotent `close()`.
|
|
15
|
+
* Structurally identical to arc's `MemoryEventTransport`.
|
|
16
|
+
*/
|
|
17
|
+
var InProcessLoyaltyBus = class extends InProcessEventBus {
|
|
18
|
+
constructor(options = {}) {
|
|
19
|
+
super({
|
|
20
|
+
name: "in-process-loyalty",
|
|
21
|
+
logLabel: "loyalty",
|
|
22
|
+
...options
|
|
23
|
+
});
|
|
33
24
|
}
|
|
34
25
|
};
|
|
35
26
|
//#endregion
|
|
@@ -99,7 +90,7 @@ const redemptionSchema = z.object({
|
|
|
99
90
|
externalId: z.string(),
|
|
100
91
|
redemptionId: z.string(),
|
|
101
92
|
points: z.number(),
|
|
102
|
-
discountAmount: z.number(),
|
|
93
|
+
discountAmount: z.number().int().nonnegative(),
|
|
103
94
|
ownerType: z.string(),
|
|
104
95
|
ownerId: z.string()
|
|
105
96
|
});
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { i as ReferralStatus, r as RedemptionStatus } from "./index-
|
|
2
|
-
import { S as EarningRule, _ as TierEvaluationBatch, d as EarningResult, f as ExpirationResult, g as TierEvaluation, h as RedemptionValidation, o as LoyaltyContext, v as PointTransaction, x as TierDefinition, y as Member } from "./index-
|
|
3
|
-
import { t as NotificationAdapter } from "./notification.port-
|
|
4
|
-
import { c as
|
|
5
|
-
import { LoyaltyModels } from "./
|
|
1
|
+
import { i as ReferralStatus, r as RedemptionStatus } from "./index-D_Oe3aDk.mjs";
|
|
2
|
+
import { S as EarningRule, _ as TierEvaluationBatch, d as EarningResult, f as ExpirationResult, g as TierEvaluation, h as RedemptionValidation, o as LoyaltyContext, v as PointTransaction, x as TierDefinition, y as Member } from "./index-Cp_sXNpG.mjs";
|
|
3
|
+
import { t as NotificationAdapter } from "./notification.port-wY4IKimz.mjs";
|
|
4
|
+
import { c as ResolvedConfig, s as RepositoryPlugins } from "./config-CtkGj3xZ.mjs";
|
|
5
|
+
import { n as LoyaltyModels } from "./index-D6GvuvmF.mjs";
|
|
6
6
|
import { PluginType, Repository } from "@classytic/mongokit";
|
|
7
|
-
import {
|
|
7
|
+
import { ResolvedTenantConfig } from "@classytic/repo-core/tenant";
|
|
8
8
|
import { ClientSession, Model } from "mongoose";
|
|
9
|
+
import { EventTransport } from "@classytic/primitives/events";
|
|
9
10
|
import { OutboxStore } from "@classytic/primitives/outbox";
|
|
10
11
|
import { ApprovalChain } from "@classytic/primitives/approval";
|
|
11
|
-
|
|
12
12
|
//#region src/domain/ports/unit-of-work.port.d.ts
|
|
13
13
|
interface UnitOfWork {
|
|
14
14
|
withTransaction<T>(callback: (session: ClientSession) => Promise<T>): Promise<T>;
|
|
@@ -138,12 +138,20 @@ declare class EarningRuleRepository extends Repository<EarningRule> {
|
|
|
138
138
|
constructor(model: Model<EarningRule>, plugins?: PluginType[]);
|
|
139
139
|
private applyCreateDefaults;
|
|
140
140
|
/**
|
|
141
|
-
* Auto-injects `programId` from `
|
|
141
|
+
* Auto-injects `programId` from `LoyaltyShape.program.id` and the tenant id
|
|
142
142
|
* from repo options when omitted. This keeps custom-program engines aligned
|
|
143
143
|
* with `MemberRepository.enroll(...)`, which uses the same config default.
|
|
144
144
|
*/
|
|
145
145
|
create(data: Parameters<Repository<EarningRule>['create']>[0], options?: Parameters<Repository<EarningRule>['create']>[1]): Promise<EarningRule>;
|
|
146
146
|
createMany(docs: Parameters<Repository<EarningRule>['createMany']>[0], options?: Parameters<Repository<EarningRule>['createMany']>[1]): Promise<EarningRule[]>;
|
|
147
|
+
/**
|
|
148
|
+
* Evaluates every active order/category/tier_bonus rule against an order.
|
|
149
|
+
*
|
|
150
|
+
* `orderTotal` and `items[].amount` are **integers in the host's minor
|
|
151
|
+
* currency unit** (paisa for BDT) — `50000` = ৳500. Rates on the rule
|
|
152
|
+
* (`amountPerPoint`, `pointsPerAmount` + `rateBasis`) are quoted in the
|
|
153
|
+
* same minor units.
|
|
154
|
+
*/
|
|
147
155
|
evaluateOrder(input: {
|
|
148
156
|
memberId: string;
|
|
149
157
|
orderId: string;
|
|
@@ -172,7 +180,19 @@ declare class EarningRuleRepository extends Repository<EarningRule> {
|
|
|
172
180
|
* machine's `TZ` env (PACKAGE_RULES P12).
|
|
173
181
|
*/
|
|
174
182
|
private weekday;
|
|
183
|
+
/**
|
|
184
|
+
* `orderTotal` is integer minor units. Two rate shapes:
|
|
185
|
+
* - `pointsPerAmount` + `rateBasis`: `points = pointsPerAmount × (orderTotal / rateBasis)`
|
|
186
|
+
* - `amountPerPoint` (minor units per point): `points = orderTotal / amountPerPoint`
|
|
187
|
+
* Rounding is applied ONCE at the end, per the rule's `roundingMode`.
|
|
188
|
+
*/
|
|
175
189
|
private calculateOrderPoints;
|
|
190
|
+
/**
|
|
191
|
+
* `pointsPerAmount` has NO implicit basis — a rule that carries it without
|
|
192
|
+
* an explicit integer minor-unit `rateBasis` is malformed (the schema
|
|
193
|
+
* enforces the pairing on write; this guards legacy/hand-written docs).
|
|
194
|
+
*/
|
|
195
|
+
private requireRateBasis;
|
|
176
196
|
private calculateCategoryPoints;
|
|
177
197
|
private applyRounding;
|
|
178
198
|
}
|
|
@@ -184,8 +204,18 @@ interface Redemption {
|
|
|
184
204
|
programId: string;
|
|
185
205
|
pointsReserved: number;
|
|
186
206
|
pointsConfirmed: number;
|
|
207
|
+
/**
|
|
208
|
+
* Discount granted for the redeemed points — **integer minor units** (paisa
|
|
209
|
+
* for BDT). Invariant: always exactly `pointsReserved × pointValueMinor`
|
|
210
|
+
* (no orphaned remainder — see `RedemptionRepository.validate`).
|
|
211
|
+
*/
|
|
187
212
|
discountAmount: number;
|
|
188
|
-
|
|
213
|
+
/**
|
|
214
|
+
* RATE snapshot at reserve time: integer minor units ONE point redeems for
|
|
215
|
+
* (≥ 1). `pointValueMinor: 10` = 1 point → 10 paisa (৳0.10).
|
|
216
|
+
* Replaces the old `conversionRate` (points per 1 MAJOR unit).
|
|
217
|
+
*/
|
|
218
|
+
pointValueMinor: number;
|
|
189
219
|
status: RedemptionStatus;
|
|
190
220
|
ownerType: string;
|
|
191
221
|
ownerId: string;
|
|
@@ -200,11 +230,13 @@ interface Redemption {
|
|
|
200
230
|
transactionId?: string;
|
|
201
231
|
/**
|
|
202
232
|
* Optional voucher-like partial-use tracking populated by hosts when
|
|
203
|
-
* the redemption is backed by a gift voucher / reward card.
|
|
204
|
-
*
|
|
233
|
+
* the redemption is backed by a gift voucher / reward card. Both are
|
|
234
|
+
* **integer minor units**. When present,
|
|
235
|
+
* `reverseConfirmed({ proportional: true })` credits back
|
|
205
236
|
* `(remainingBalance / initialBalance) × pointsConfirmed`.
|
|
206
237
|
*/
|
|
207
238
|
initialBalance?: number;
|
|
239
|
+
/** Integer minor units. See `initialBalance`. */
|
|
208
240
|
remainingBalance?: number;
|
|
209
241
|
/**
|
|
210
242
|
* Optional multi-step approval chain (P7 canonical — same shape as
|
|
@@ -240,14 +272,40 @@ declare class RedemptionRepository extends Repository<Redemption> {
|
|
|
240
272
|
private get _dispatchDeps();
|
|
241
273
|
/**
|
|
242
274
|
* Atomic compare-and-set status transition.
|
|
243
|
-
*
|
|
275
|
+
*
|
|
276
|
+
* `null` means ONE thing and one thing only: the guarded update matched
|
|
277
|
+
* no document, i.e. the status was not `expectedStatus`. `throwOnNotFound:
|
|
278
|
+
* false` already produces that `null`, so there is nothing legitimate left
|
|
279
|
+
* for a `catch` to convert.
|
|
280
|
+
*
|
|
281
|
+
* There used to be a bare `} catch { return null; }` here. It laundered
|
|
282
|
+
* every real failure into a CAS miss, and callers map a CAS miss to
|
|
283
|
+
* `ValidationError("Redemption status must be 'reserved', got '<status>'")`
|
|
284
|
+
* — a NON-retryable 4xx built from the status read BEFORE the write. So a
|
|
285
|
+
* routine Mongo `WriteConflict` (a retryable transient, the normal outcome
|
|
286
|
+
* of two concurrent confirms) surfaced as a client error whose message
|
|
287
|
+
* could literally read "must be 'reserved', got 'reserved'": an operator
|
|
288
|
+
* chasing a contradiction while the actual fix was to retry.
|
|
289
|
+
*
|
|
290
|
+
* Anything that throws in here is a real error and must propagate.
|
|
244
291
|
*/
|
|
245
292
|
atomicStatusTransition(id: string, expectedStatus: string, data: Record<string, unknown>, ctx?: Record<string, unknown>): Promise<Redemption | null>;
|
|
293
|
+
/**
|
|
294
|
+
* Validates a redemption request. `orderTotal` is an **integer in the
|
|
295
|
+
* host's minor currency unit** (paisa for BDT); the returned
|
|
296
|
+
* `discountAmount` is in the same minor units.
|
|
297
|
+
*/
|
|
246
298
|
validate(input: {
|
|
247
299
|
memberId: string;
|
|
248
300
|
pointsToRedeem: number;
|
|
249
301
|
orderTotal: number;
|
|
250
302
|
}, ctx: LoyaltyContext): Promise<RedemptionValidation>;
|
|
303
|
+
/**
|
|
304
|
+
* Reserves points against an order. `orderTotal` is an **integer in the
|
|
305
|
+
* host's minor currency unit** (paisa for BDT). The persisted
|
|
306
|
+
* `discountAmount` is minor units and the reserve-time `pointValueMinor`
|
|
307
|
+
* rate is snapshotted on the doc.
|
|
308
|
+
*/
|
|
251
309
|
reserve(input: {
|
|
252
310
|
memberId: string;
|
|
253
311
|
pointsToRedeem: number;
|
|
@@ -317,7 +375,16 @@ declare class ReferralRepository extends Repository<Referral> {
|
|
|
317
375
|
createMany(docs: Parameters<Repository<Referral>['createMany']>[0], options?: Parameters<Repository<Referral>['createMany']>[1]): Promise<Referral[]>;
|
|
318
376
|
/**
|
|
319
377
|
* Atomic compare-and-set status transition.
|
|
320
|
-
*
|
|
378
|
+
*
|
|
379
|
+
* `null` means ONE thing: the guarded update matched no document, i.e.
|
|
380
|
+
* the status was not `expectedStatus`. `throwOnNotFound: false` already
|
|
381
|
+
* returns that `null`, so the bare `} catch { return null; }` that used
|
|
382
|
+
* to sit here could only ever hide REAL errors — most damagingly a
|
|
383
|
+
* retryable Mongo `WriteConflict`, which callers then re-threw as a
|
|
384
|
+
* non-retryable `ValidationError` describing a status that had not
|
|
385
|
+
* actually gone stale.
|
|
386
|
+
*
|
|
387
|
+
* Anything that throws in here is a real error and must propagate.
|
|
321
388
|
*/
|
|
322
389
|
atomicStatusTransition(id: string, expectedStatus: string, data: Record<string, unknown>, ctx?: Record<string, unknown>): Promise<Referral | null>;
|
|
323
390
|
generateCode(memberId: string, ctx: LoyaltyContext): Promise<string>;
|
|
@@ -381,7 +448,7 @@ interface LoyaltyRepositories {
|
|
|
381
448
|
interface CreateRepositoriesInput {
|
|
382
449
|
models: LoyaltyModels;
|
|
383
450
|
plugins?: RepositoryPlugins | undefined;
|
|
384
|
-
tenant?:
|
|
451
|
+
tenant?: ResolvedTenantConfig | undefined;
|
|
385
452
|
config: ResolvedConfig;
|
|
386
453
|
events: EventTransport;
|
|
387
454
|
outbox?: OutboxStore | undefined;
|