@adriangalilea/utils 0.18.0 → 0.20.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/README.md +3 -1
- package/dist/bot/access-control.d.ts.map +1 -1
- package/dist/bot/access-control.js +17 -1
- package/dist/bot/access-control.js.map +1 -1
- package/dist/bot/allow-list.d.ts +38 -0
- package/dist/bot/allow-list.d.ts.map +1 -0
- package/dist/bot/allow-list.js +41 -0
- package/dist/bot/allow-list.js.map +1 -0
- package/dist/bot/callbacks.d.ts +89 -0
- package/dist/bot/callbacks.d.ts.map +1 -0
- package/dist/bot/callbacks.js +128 -0
- package/dist/bot/callbacks.js.map +1 -0
- package/dist/bot/ctx.d.ts +219 -0
- package/dist/bot/ctx.d.ts.map +1 -0
- package/dist/bot/ctx.js +52 -0
- package/dist/bot/ctx.js.map +1 -0
- package/dist/bot/index.d.ts +4 -0
- package/dist/bot/index.d.ts.map +1 -1
- package/dist/bot/index.js +4 -0
- package/dist/bot/index.js.map +1 -1
- package/dist/bot/kit.d.ts +1 -1
- package/dist/bot/kit.d.ts.map +1 -1
- package/dist/bot/kit.js +4 -2
- package/dist/bot/kit.js.map +1 -1
- package/dist/bot/menu.d.ts +23 -1
- package/dist/bot/menu.d.ts.map +1 -1
- package/dist/bot/menu.js +30 -1
- package/dist/bot/menu.js.map +1 -1
- package/dist/bot/payments/callbacks.d.ts +28 -0
- package/dist/bot/payments/callbacks.d.ts.map +1 -0
- package/dist/bot/payments/callbacks.js +102 -0
- package/dist/bot/payments/callbacks.js.map +1 -0
- package/dist/bot/payments/commands.d.ts +26 -0
- package/dist/bot/payments/commands.d.ts.map +1 -0
- package/dist/bot/payments/commands.js +41 -0
- package/dist/bot/payments/commands.js.map +1 -0
- package/dist/bot/payments/config.d.ts +30 -0
- package/dist/bot/payments/config.d.ts.map +1 -0
- package/dist/bot/payments/config.js +166 -0
- package/dist/bot/payments/config.js.map +1 -0
- package/dist/bot/payments/derive.d.ts +75 -0
- package/dist/bot/payments/derive.d.ts.map +1 -0
- package/dist/bot/payments/derive.js +151 -0
- package/dist/bot/payments/derive.js.map +1 -0
- package/dist/bot/payments/handlers.d.ts +113 -0
- package/dist/bot/payments/handlers.d.ts.map +1 -0
- package/dist/bot/payments/handlers.js +284 -0
- package/dist/bot/payments/handlers.js.map +1 -0
- package/dist/bot/payments/index.d.ts +17 -0
- package/dist/bot/payments/index.d.ts.map +1 -0
- package/dist/bot/payments/index.js +14 -0
- package/dist/bot/payments/index.js.map +1 -0
- package/dist/bot/payments/invoice.d.ts +101 -0
- package/dist/bot/payments/invoice.d.ts.map +1 -0
- package/dist/bot/payments/invoice.js +213 -0
- package/dist/bot/payments/invoice.js.map +1 -0
- package/dist/bot/payments/menu-item.d.ts +41 -0
- package/dist/bot/payments/menu-item.d.ts.map +1 -0
- package/dist/bot/payments/menu-item.js +259 -0
- package/dist/bot/payments/menu-item.js.map +1 -0
- package/dist/bot/payments/payload.d.ts +42 -0
- package/dist/bot/payments/payload.d.ts.map +1 -0
- package/dist/bot/payments/payload.js +69 -0
- package/dist/bot/payments/payload.js.map +1 -0
- package/dist/bot/payments/payouts.d.ts +88 -0
- package/dist/bot/payments/payouts.d.ts.map +1 -0
- package/dist/bot/payments/payouts.js +133 -0
- package/dist/bot/payments/payouts.js.map +1 -0
- package/dist/bot/payments/plugin.d.ts +271 -0
- package/dist/bot/payments/plugin.d.ts.map +1 -0
- package/dist/bot/payments/plugin.js +141 -0
- package/dist/bot/payments/plugin.js.map +1 -0
- package/dist/bot/payments/refund.d.ts +124 -0
- package/dist/bot/payments/refund.d.ts.map +1 -0
- package/dist/bot/payments/refund.js +308 -0
- package/dist/bot/payments/refund.js.map +1 -0
- package/dist/bot/payments/schemas.d.ts +58 -0
- package/dist/bot/payments/schemas.d.ts.map +1 -0
- package/dist/bot/payments/schemas.js +55 -0
- package/dist/bot/payments/schemas.js.map +1 -0
- package/dist/bot/payments/state.d.ts +90 -0
- package/dist/bot/payments/state.d.ts.map +1 -0
- package/dist/bot/payments/state.js +149 -0
- package/dist/bot/payments/state.js.map +1 -0
- package/dist/bot/payments/stores.d.ts +44 -0
- package/dist/bot/payments/stores.d.ts.map +1 -0
- package/dist/bot/payments/stores.js +39 -0
- package/dist/bot/payments/stores.js.map +1 -0
- package/dist/bot/payments/types.d.ts +299 -0
- package/dist/bot/payments/types.d.ts.map +1 -0
- package/dist/bot/payments/types.js +39 -0
- package/dist/bot/payments/types.js.map +1 -0
- package/dist/bot/payments/waiver.d.ts +81 -0
- package/dist/bot/payments/waiver.d.ts.map +1 -0
- package/dist/bot/payments/waiver.js +145 -0
- package/dist/bot/payments/waiver.js.map +1 -0
- package/dist/bot/storage.d.ts +154 -0
- package/dist/bot/storage.d.ts.map +1 -0
- package/dist/bot/storage.js +222 -0
- package/dist/bot/storage.js.map +1 -0
- package/package.json +28 -3
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime schemas for the persisted ledger records. Wired into
|
|
3
|
+
* `stores.ts` as the optional validator on `botRecord<T>` — every read
|
|
4
|
+
* from storage gets `.parse()`'d, so a corrupted Redis record throws
|
|
5
|
+
* a clear `SourcedError({ source: 'storage', operation: 'validate' })`
|
|
6
|
+
* instead of NaN-ing downstream.
|
|
7
|
+
*
|
|
8
|
+
* Why zod? It's the canonical TS validator with the right API surface
|
|
9
|
+
* (`{ parse(data): T }`). The library's `RecordValidator<T>` type is
|
|
10
|
+
* structural, so swapping to valibot / ArkType / a hand-rolled guard
|
|
11
|
+
* just works — but zod is what we ship the default with.
|
|
12
|
+
*
|
|
13
|
+
* Schemas mirror the TS types in `types.ts` 1:1. Keep them in sync; the
|
|
14
|
+
* type system enforces that via the `z.infer<>` round-trip — if a
|
|
15
|
+
* schema drifts from the type, `satisfies ZodType<X>` on each export
|
|
16
|
+
* blows up. We use `satisfies` (not `as`) so the TS error points at the
|
|
17
|
+
* mismatch, not the assertion.
|
|
18
|
+
*/
|
|
19
|
+
import { z } from "zod";
|
|
20
|
+
/**
|
|
21
|
+
* `ChargeRecord` schema. Mutable fields stay loose-typed (zod doesn't
|
|
22
|
+
* enforce readonly the way TS does) but every field is structurally
|
|
23
|
+
* present.
|
|
24
|
+
*/
|
|
25
|
+
export declare const chargeRecordSchema: z.ZodObject<{
|
|
26
|
+
chargeId: z.ZodString;
|
|
27
|
+
userId: z.ZodNumber;
|
|
28
|
+
productKey: z.ZodString;
|
|
29
|
+
xtr: z.ZodNumber;
|
|
30
|
+
receivedAt: z.ZodNumber;
|
|
31
|
+
payload: z.ZodString;
|
|
32
|
+
waiverSnapshot: z.ZodObject<{
|
|
33
|
+
at: z.ZodNumber;
|
|
34
|
+
version: z.ZodString;
|
|
35
|
+
locale: z.ZodString;
|
|
36
|
+
}, z.core.$strip>;
|
|
37
|
+
subscriptionExpiresAt: z.ZodOptional<z.ZodNumber>;
|
|
38
|
+
creditsGranted: z.ZodNumber;
|
|
39
|
+
vipRung: z.ZodOptional<z.ZodNumber>;
|
|
40
|
+
perkKey: z.ZodOptional<z.ZodString>;
|
|
41
|
+
paysupportState: z.ZodEnum<{
|
|
42
|
+
none: "none";
|
|
43
|
+
opened: "opened";
|
|
44
|
+
refunded: "refunded";
|
|
45
|
+
}>;
|
|
46
|
+
refundedAt: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
47
|
+
payoutBatchId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
48
|
+
}, z.core.$strip>;
|
|
49
|
+
export declare const payoutRecordSchema: z.ZodObject<{
|
|
50
|
+
batchId: z.ZodString;
|
|
51
|
+
fromMs: z.ZodNumber;
|
|
52
|
+
toMs: z.ZodNumber;
|
|
53
|
+
tonAmount: z.ZodNumber;
|
|
54
|
+
eurAtReceipt: z.ZodNumber;
|
|
55
|
+
recordedAt: z.ZodNumber;
|
|
56
|
+
facturaNumber: z.ZodOptional<z.ZodString>;
|
|
57
|
+
}, z.core.$strip>;
|
|
58
|
+
//# sourceMappingURL=schemas.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../../../src/bot/payments/schemas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAUxB;;;;GAIG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;iBAeK,CAAC;AAErC,eAAO,MAAM,kBAAkB;;;;;;;;iBAQK,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime schemas for the persisted ledger records. Wired into
|
|
3
|
+
* `stores.ts` as the optional validator on `botRecord<T>` — every read
|
|
4
|
+
* from storage gets `.parse()`'d, so a corrupted Redis record throws
|
|
5
|
+
* a clear `SourcedError({ source: 'storage', operation: 'validate' })`
|
|
6
|
+
* instead of NaN-ing downstream.
|
|
7
|
+
*
|
|
8
|
+
* Why zod? It's the canonical TS validator with the right API surface
|
|
9
|
+
* (`{ parse(data): T }`). The library's `RecordValidator<T>` type is
|
|
10
|
+
* structural, so swapping to valibot / ArkType / a hand-rolled guard
|
|
11
|
+
* just works — but zod is what we ship the default with.
|
|
12
|
+
*
|
|
13
|
+
* Schemas mirror the TS types in `types.ts` 1:1. Keep them in sync; the
|
|
14
|
+
* type system enforces that via the `z.infer<>` round-trip — if a
|
|
15
|
+
* schema drifts from the type, `satisfies ZodType<X>` on each export
|
|
16
|
+
* blows up. We use `satisfies` (not `as`) so the TS error points at the
|
|
17
|
+
* mismatch, not the assertion.
|
|
18
|
+
*/
|
|
19
|
+
import { z } from "zod";
|
|
20
|
+
const waiverRecordSchema = z.object({
|
|
21
|
+
at: z.number(),
|
|
22
|
+
version: z.string(),
|
|
23
|
+
locale: z.string(),
|
|
24
|
+
});
|
|
25
|
+
/**
|
|
26
|
+
* `ChargeRecord` schema. Mutable fields stay loose-typed (zod doesn't
|
|
27
|
+
* enforce readonly the way TS does) but every field is structurally
|
|
28
|
+
* present.
|
|
29
|
+
*/
|
|
30
|
+
export const chargeRecordSchema = z.object({
|
|
31
|
+
chargeId: z.string(),
|
|
32
|
+
userId: z.number(),
|
|
33
|
+
productKey: z.string(),
|
|
34
|
+
xtr: z.number(),
|
|
35
|
+
receivedAt: z.number(),
|
|
36
|
+
payload: z.string(),
|
|
37
|
+
waiverSnapshot: waiverRecordSchema,
|
|
38
|
+
subscriptionExpiresAt: z.number().optional(),
|
|
39
|
+
creditsGranted: z.number(),
|
|
40
|
+
vipRung: z.number().optional(),
|
|
41
|
+
perkKey: z.string().optional(),
|
|
42
|
+
paysupportState: z.enum(["none", "opened", "refunded"]),
|
|
43
|
+
refundedAt: z.number().nullable().optional(),
|
|
44
|
+
payoutBatchId: z.string().nullable().optional(),
|
|
45
|
+
});
|
|
46
|
+
export const payoutRecordSchema = z.object({
|
|
47
|
+
batchId: z.string(),
|
|
48
|
+
fromMs: z.number(),
|
|
49
|
+
toMs: z.number(),
|
|
50
|
+
tonAmount: z.number(),
|
|
51
|
+
eurAtReceipt: z.number(),
|
|
52
|
+
recordedAt: z.number(),
|
|
53
|
+
facturaNumber: z.string().optional(),
|
|
54
|
+
});
|
|
55
|
+
//# sourceMappingURL=schemas.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schemas.js","sourceRoot":"","sources":["../../../src/bot/payments/schemas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACnC,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;IACd,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;CAClB,CAAmC,CAAC;AAErC;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC1C,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IACpB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;IAClB,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;IACtB,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE;IACf,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;IACtB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,cAAc,EAAE,kBAAkB;IAClC,qBAAqB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC5C,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE;IAC1B,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC9B,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC9B,eAAe,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC;IACvD,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IAC5C,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;CAC/C,CAAmC,CAAC;AAErC,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC1C,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;IAClB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;IACrB,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE;IACxB,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;IACtB,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACpC,CAAmC,CAAC"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure state computation over the charge log.
|
|
3
|
+
*
|
|
4
|
+
* This file is `state.ts` (not `derive.ts`) because gramio's `derive`
|
|
5
|
+
* concept is reserved for ctx decoration (see `derive.ts` alongside,
|
|
6
|
+
* which builds `ctx.payments`). The reducers here are bot-id and ctx
|
|
7
|
+
* agnostic — they take `ChargeRecord[]` and a session-shaped object
|
|
8
|
+
* and mutate the session in place.
|
|
9
|
+
*
|
|
10
|
+
* The charge log (`pay:charge:{chargeId}`) is the single source of truth
|
|
11
|
+
* (CLAUDE.md §"Storage layout"). Session state is a cache for O(1)
|
|
12
|
+
* runtime checks. This module owns the function that rebuilds the cache
|
|
13
|
+
* from the log.
|
|
14
|
+
*
|
|
15
|
+
* Two surfaces:
|
|
16
|
+
*
|
|
17
|
+
* `deriveState(charges)` — pure, reduces a chronological list of
|
|
18
|
+
* NON-refunded charges into the cache
|
|
19
|
+
* shape. Used by reconcile and refund
|
|
20
|
+
* (after marking the refunded charge).
|
|
21
|
+
*
|
|
22
|
+
* `applyCharge(session, …)` — incremental, mutates one session record
|
|
23
|
+
* for a single new charge. Used by
|
|
24
|
+
* `handlers.ts` on `successful_payment`.
|
|
25
|
+
*
|
|
26
|
+
* `revertCreditsForCharge(session, charge)` — targeted decrement
|
|
27
|
+
* used by refund (vip/perks are full
|
|
28
|
+
* re-derive territory; credits is not,
|
|
29
|
+
* because consumption isn't in the log).
|
|
30
|
+
*
|
|
31
|
+
* Credits balance is **not fully derivable** from the log alone — the
|
|
32
|
+
* log records grants, not consumption. `deriveState` therefore returns
|
|
33
|
+
* `totalCreditsGranted` (sum) and callers decide whether to use it as
|
|
34
|
+
* the balance (reconcile of fresh state) or to ignore (incremental).
|
|
35
|
+
*/
|
|
36
|
+
import type { ChargeRecord, DerivedPaymentsState, PaymentsSession } from "./types.js";
|
|
37
|
+
/**
|
|
38
|
+
* Result of `deriveState`. `totalCreditsGranted` is informational —
|
|
39
|
+
* subtract consumption (which isn't logged) for the real balance.
|
|
40
|
+
*/
|
|
41
|
+
export type DeriveResult = DerivedPaymentsState & {
|
|
42
|
+
readonly totalCreditsGranted: number;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Pure reducer over a chronological list of charges. Caller is
|
|
46
|
+
* responsible for filtering out refunded ones if they want
|
|
47
|
+
* "current state after refunds" semantics.
|
|
48
|
+
*/
|
|
49
|
+
export declare const deriveState: (charges: ReadonlyArray<ChargeRecord>) => DeriveResult;
|
|
50
|
+
/**
|
|
51
|
+
* Apply a single new charge to a session in place. Idempotent across
|
|
52
|
+
* `chargeId` because the caller (handlers.ts) guards via the
|
|
53
|
+
* `pay:idempotency:{chargeId}` sentinel before this fires; we don't
|
|
54
|
+
* re-check here.
|
|
55
|
+
*
|
|
56
|
+
* - vip: replaces `session.pay.vip` (renewals update expiresAt;
|
|
57
|
+
* tier upgrades replace rung)
|
|
58
|
+
* - credits: increments `session.pay.credits` by the granted amount
|
|
59
|
+
* (covers both pack purchases AND vip renewal grants)
|
|
60
|
+
* - perks: set-if-absent on `session.pay.perks[key]`
|
|
61
|
+
*/
|
|
62
|
+
export declare const applyCharge: (session: {
|
|
63
|
+
pay?: PaymentsSession;
|
|
64
|
+
}, charge: ChargeRecord) => void;
|
|
65
|
+
/**
|
|
66
|
+
* Subtract a refunded charge's `creditsGranted` from the session
|
|
67
|
+
* balance, clamping at zero. The "clamp at zero" semantics are
|
|
68
|
+
* deliberate: a user may have already consumed credits when the refund
|
|
69
|
+
* arrives, and going negative would let them buy back into a debt — we
|
|
70
|
+
* refuse that and accept the asymmetry (the refunded Stars are already
|
|
71
|
+
* leaving the bot's balance via `refundStarPayment` either way).
|
|
72
|
+
*/
|
|
73
|
+
export declare const revertCreditsForCharge: (session: {
|
|
74
|
+
pay?: PaymentsSession;
|
|
75
|
+
}, charge: ChargeRecord) => void;
|
|
76
|
+
/**
|
|
77
|
+
* Rebuild vip + perks (the fully-derivable parts of session.pay.*)
|
|
78
|
+
* from a fresh chronological view of the charge log, **after** the
|
|
79
|
+
* caller has marked any newly-refunded charges. Credits balance is
|
|
80
|
+
* NOT touched — caller handles that explicitly via
|
|
81
|
+
* `revertCreditsForCharge` for the targeted decrement semantics.
|
|
82
|
+
*
|
|
83
|
+
* Used by:
|
|
84
|
+
* - refund.ts after `refundStarPayment` succeeds
|
|
85
|
+
* - reconcile (admin command) for full state recovery
|
|
86
|
+
*/
|
|
87
|
+
export declare const rebuildVipAndPerks: (session: {
|
|
88
|
+
pay?: PaymentsSession;
|
|
89
|
+
}, charges: ReadonlyArray<ChargeRecord>) => void;
|
|
90
|
+
//# sourceMappingURL=state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../src/bot/payments/state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,KAAK,EACX,YAAY,EACZ,oBAAoB,EACpB,eAAe,EAGf,MAAM,YAAY,CAAC;AAEpB;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,oBAAoB,GAAG;IACjD,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;CACrC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,WAAW,GACvB,SAAS,aAAa,CAAC,YAAY,CAAC,KAClC,YAwCF,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,WAAW,GACvB,SAAS;IAAE,GAAG,CAAC,EAAE,eAAe,CAAA;CAAE,EAClC,QAAQ,YAAY,KAClB,IA0BF,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,GAClC,SAAS;IAAE,GAAG,CAAC,EAAE,eAAe,CAAA;CAAE,EAClC,QAAQ,YAAY,KAClB,IAMF,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kBAAkB,GAC9B,SAAS;IAAE,GAAG,CAAC,EAAE,eAAe,CAAA;CAAE,EAClC,SAAS,aAAa,CAAC,YAAY,CAAC,KAClC,IAMF,CAAC"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure state computation over the charge log.
|
|
3
|
+
*
|
|
4
|
+
* This file is `state.ts` (not `derive.ts`) because gramio's `derive`
|
|
5
|
+
* concept is reserved for ctx decoration (see `derive.ts` alongside,
|
|
6
|
+
* which builds `ctx.payments`). The reducers here are bot-id and ctx
|
|
7
|
+
* agnostic — they take `ChargeRecord[]` and a session-shaped object
|
|
8
|
+
* and mutate the session in place.
|
|
9
|
+
*
|
|
10
|
+
* The charge log (`pay:charge:{chargeId}`) is the single source of truth
|
|
11
|
+
* (CLAUDE.md §"Storage layout"). Session state is a cache for O(1)
|
|
12
|
+
* runtime checks. This module owns the function that rebuilds the cache
|
|
13
|
+
* from the log.
|
|
14
|
+
*
|
|
15
|
+
* Two surfaces:
|
|
16
|
+
*
|
|
17
|
+
* `deriveState(charges)` — pure, reduces a chronological list of
|
|
18
|
+
* NON-refunded charges into the cache
|
|
19
|
+
* shape. Used by reconcile and refund
|
|
20
|
+
* (after marking the refunded charge).
|
|
21
|
+
*
|
|
22
|
+
* `applyCharge(session, …)` — incremental, mutates one session record
|
|
23
|
+
* for a single new charge. Used by
|
|
24
|
+
* `handlers.ts` on `successful_payment`.
|
|
25
|
+
*
|
|
26
|
+
* `revertCreditsForCharge(session, charge)` — targeted decrement
|
|
27
|
+
* used by refund (vip/perks are full
|
|
28
|
+
* re-derive territory; credits is not,
|
|
29
|
+
* because consumption isn't in the log).
|
|
30
|
+
*
|
|
31
|
+
* Credits balance is **not fully derivable** from the log alone — the
|
|
32
|
+
* log records grants, not consumption. `deriveState` therefore returns
|
|
33
|
+
* `totalCreditsGranted` (sum) and callers decide whether to use it as
|
|
34
|
+
* the balance (reconcile of fresh state) or to ignore (incremental).
|
|
35
|
+
*/
|
|
36
|
+
/**
|
|
37
|
+
* Pure reducer over a chronological list of charges. Caller is
|
|
38
|
+
* responsible for filtering out refunded ones if they want
|
|
39
|
+
* "current state after refunds" semantics.
|
|
40
|
+
*/
|
|
41
|
+
export const deriveState = (charges) => {
|
|
42
|
+
let vip;
|
|
43
|
+
const perks = {};
|
|
44
|
+
let totalCreditsGranted = 0;
|
|
45
|
+
// Charges are oldest-first by convention. Vip latest-wins by
|
|
46
|
+
// receivedAt; perks first-wins (an idempotent unlock — buying twice
|
|
47
|
+
// shouldn't change the unlock date).
|
|
48
|
+
for (const c of charges) {
|
|
49
|
+
if (c.paysupportState === "refunded")
|
|
50
|
+
continue;
|
|
51
|
+
totalCreditsGranted += c.creditsGranted;
|
|
52
|
+
if (c.vipRung !== undefined && c.subscriptionExpiresAt !== undefined) {
|
|
53
|
+
// Replace the active subscription only when this charge is newer
|
|
54
|
+
// or this is the first vip charge encountered.
|
|
55
|
+
if (!vip || c.receivedAt >= (vip.expiresAt ?? 0) || c.receivedAt > 0) {
|
|
56
|
+
vip = {
|
|
57
|
+
rung: c.vipRung,
|
|
58
|
+
chargeId: c.chargeId,
|
|
59
|
+
expiresAt: c.subscriptionExpiresAt,
|
|
60
|
+
canceled: false,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (c.perkKey && !perks[c.perkKey]) {
|
|
65
|
+
perks[c.perkKey] = {
|
|
66
|
+
chargeId: c.chargeId,
|
|
67
|
+
at: c.receivedAt,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
credits: totalCreditsGranted,
|
|
73
|
+
vip,
|
|
74
|
+
perks,
|
|
75
|
+
totalCreditsGranted,
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Apply a single new charge to a session in place. Idempotent across
|
|
80
|
+
* `chargeId` because the caller (handlers.ts) guards via the
|
|
81
|
+
* `pay:idempotency:{chargeId}` sentinel before this fires; we don't
|
|
82
|
+
* re-check here.
|
|
83
|
+
*
|
|
84
|
+
* - vip: replaces `session.pay.vip` (renewals update expiresAt;
|
|
85
|
+
* tier upgrades replace rung)
|
|
86
|
+
* - credits: increments `session.pay.credits` by the granted amount
|
|
87
|
+
* (covers both pack purchases AND vip renewal grants)
|
|
88
|
+
* - perks: set-if-absent on `session.pay.perks[key]`
|
|
89
|
+
*/
|
|
90
|
+
export const applyCharge = (session, charge) => {
|
|
91
|
+
session.pay ??= {};
|
|
92
|
+
const pay = session.pay;
|
|
93
|
+
if (charge.creditsGranted > 0) {
|
|
94
|
+
pay.credits = (pay.credits ?? 0) + charge.creditsGranted;
|
|
95
|
+
}
|
|
96
|
+
if (charge.vipRung !== undefined &&
|
|
97
|
+
charge.subscriptionExpiresAt !== undefined) {
|
|
98
|
+
pay.vip = {
|
|
99
|
+
rung: charge.vipRung,
|
|
100
|
+
chargeId: charge.chargeId,
|
|
101
|
+
expiresAt: charge.subscriptionExpiresAt,
|
|
102
|
+
canceled: false,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (charge.perkKey) {
|
|
106
|
+
pay.perks ??= {};
|
|
107
|
+
if (!pay.perks[charge.perkKey]) {
|
|
108
|
+
pay.perks[charge.perkKey] = {
|
|
109
|
+
chargeId: charge.chargeId,
|
|
110
|
+
at: charge.receivedAt,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* Subtract a refunded charge's `creditsGranted` from the session
|
|
117
|
+
* balance, clamping at zero. The "clamp at zero" semantics are
|
|
118
|
+
* deliberate: a user may have already consumed credits when the refund
|
|
119
|
+
* arrives, and going negative would let them buy back into a debt — we
|
|
120
|
+
* refuse that and accept the asymmetry (the refunded Stars are already
|
|
121
|
+
* leaving the bot's balance via `refundStarPayment` either way).
|
|
122
|
+
*/
|
|
123
|
+
export const revertCreditsForCharge = (session, charge) => {
|
|
124
|
+
if (!charge.creditsGranted)
|
|
125
|
+
return;
|
|
126
|
+
session.pay ??= {};
|
|
127
|
+
const pay = session.pay;
|
|
128
|
+
const current = pay.credits ?? 0;
|
|
129
|
+
pay.credits = Math.max(0, current - charge.creditsGranted);
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* Rebuild vip + perks (the fully-derivable parts of session.pay.*)
|
|
133
|
+
* from a fresh chronological view of the charge log, **after** the
|
|
134
|
+
* caller has marked any newly-refunded charges. Credits balance is
|
|
135
|
+
* NOT touched — caller handles that explicitly via
|
|
136
|
+
* `revertCreditsForCharge` for the targeted decrement semantics.
|
|
137
|
+
*
|
|
138
|
+
* Used by:
|
|
139
|
+
* - refund.ts after `refundStarPayment` succeeds
|
|
140
|
+
* - reconcile (admin command) for full state recovery
|
|
141
|
+
*/
|
|
142
|
+
export const rebuildVipAndPerks = (session, charges) => {
|
|
143
|
+
const derived = deriveState(charges);
|
|
144
|
+
session.pay ??= {};
|
|
145
|
+
const pay = session.pay;
|
|
146
|
+
pay.vip = derived.vip;
|
|
147
|
+
pay.perks = derived.perks;
|
|
148
|
+
};
|
|
149
|
+
//# sourceMappingURL=state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.js","sourceRoot":"","sources":["../../../src/bot/payments/state.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The plugin's storage layout, in one place.
|
|
3
|
+
*
|
|
4
|
+
* Replaces the scattered `chargeKey` / `userChargesIndexKey` /
|
|
5
|
+
* `idempotencyKey` / `payoutKey` / `payoutsIndexKey` helpers that used
|
|
6
|
+
* to live in `handlers.ts`, `refund.ts`, `payouts.ts`. Every storage
|
|
7
|
+
* operation in the plugin goes through one of these handles.
|
|
8
|
+
*
|
|
9
|
+
* Built once at plugin construction (in `plugin.ts`) and threaded
|
|
10
|
+
* through every concern that needs it.
|
|
11
|
+
*/
|
|
12
|
+
import type { Storage } from "@gramio/storage";
|
|
13
|
+
import { botIndex, botRecord, botSentinel } from "../storage.js";
|
|
14
|
+
import type { ChargeRecord, PayoutRecord } from "./types.js";
|
|
15
|
+
export type PaymentsStores = {
|
|
16
|
+
/** `pay:charge:<chargeId>` — authoritative per-charge record. */
|
|
17
|
+
readonly charges: ReturnType<typeof botRecord<ChargeRecord>>;
|
|
18
|
+
/** `pay:idem:<chargeId>` — fulfillment idempotency sentinel. */
|
|
19
|
+
readonly idempotency: ReturnType<typeof botSentinel>;
|
|
20
|
+
/**
|
|
21
|
+
* `pay:user:<userId>:charges` — newest-first cap of chargeIds for
|
|
22
|
+
* `/refunds` listing + menu history (v2). Factory because the key
|
|
23
|
+
* is per-user.
|
|
24
|
+
*/
|
|
25
|
+
readonly userCharges: (userId: number) => ReturnType<typeof botIndex>;
|
|
26
|
+
/** `pay:payout:<batchId>` — Fragment payout record. */
|
|
27
|
+
readonly payouts: ReturnType<typeof botRecord<PayoutRecord>>;
|
|
28
|
+
/** `pay:payouts:index` — newest-first batchIds for export. */
|
|
29
|
+
readonly payoutsIndex: ReturnType<typeof botIndex>;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Construct the plugin's storage handles. Called once at plugin
|
|
33
|
+
* construction; the result is passed into every concern that reads or
|
|
34
|
+
* writes plugin state.
|
|
35
|
+
*
|
|
36
|
+
* Schemas are wired in — every `.get()` on `charges` / `payouts` runs
|
|
37
|
+
* the corresponding zod schema's `.parse()` and throws a clear
|
|
38
|
+
* `SourcedError({ source: 'storage', operation: 'validate' })` on a
|
|
39
|
+
* corrupted record (instead of NaN-ing downstream when the consumer
|
|
40
|
+
* reads a now-missing field). `idempotency` is a presence-only sentinel
|
|
41
|
+
* so no schema applies; `*Index` stores `string[]` so likewise.
|
|
42
|
+
*/
|
|
43
|
+
export declare const buildStores: (storage: Storage) => PaymentsStores;
|
|
44
|
+
//# sourceMappingURL=stores.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stores.d.ts","sourceRoot":"","sources":["../../../src/bot/payments/stores.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAE/C,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjE,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAO7D,MAAM,MAAM,cAAc,GAAG;IAC5B,iEAAiE;IACjE,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,YAAY,CAAC,CAAC,CAAC;IAC7D,gEAAgE;IAChE,QAAQ,CAAC,WAAW,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;IACrD;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;IACtE,uDAAuD;IACvD,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,YAAY,CAAC,CAAC,CAAC;IAC7D,8DAA8D;IAC9D,QAAQ,CAAC,YAAY,EAAE,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;CACnD,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,WAAW,GAAI,SAAS,OAAO,KAAG,cAS7C,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The plugin's storage layout, in one place.
|
|
3
|
+
*
|
|
4
|
+
* Replaces the scattered `chargeKey` / `userChargesIndexKey` /
|
|
5
|
+
* `idempotencyKey` / `payoutKey` / `payoutsIndexKey` helpers that used
|
|
6
|
+
* to live in `handlers.ts`, `refund.ts`, `payouts.ts`. Every storage
|
|
7
|
+
* operation in the plugin goes through one of these handles.
|
|
8
|
+
*
|
|
9
|
+
* Built once at plugin construction (in `plugin.ts`) and threaded
|
|
10
|
+
* through every concern that needs it.
|
|
11
|
+
*/
|
|
12
|
+
import { botIndex, botRecord, botSentinel } from "../storage.js";
|
|
13
|
+
import { chargeRecordSchema, payoutRecordSchema } from "./schemas.js";
|
|
14
|
+
/** Cap on the per-user `pay:user:<id>:charges` index. Older charges
|
|
15
|
+
* drop off the list but remain in `pay:charge:<id>` for the gestor
|
|
16
|
+
* export (which scans charges directly, not the index). */
|
|
17
|
+
const USER_CHARGES_CAP = 100;
|
|
18
|
+
/**
|
|
19
|
+
* Construct the plugin's storage handles. Called once at plugin
|
|
20
|
+
* construction; the result is passed into every concern that reads or
|
|
21
|
+
* writes plugin state.
|
|
22
|
+
*
|
|
23
|
+
* Schemas are wired in — every `.get()` on `charges` / `payouts` runs
|
|
24
|
+
* the corresponding zod schema's `.parse()` and throws a clear
|
|
25
|
+
* `SourcedError({ source: 'storage', operation: 'validate' })` on a
|
|
26
|
+
* corrupted record (instead of NaN-ing downstream when the consumer
|
|
27
|
+
* reads a now-missing field). `idempotency` is a presence-only sentinel
|
|
28
|
+
* so no schema applies; `*Index` stores `string[]` so likewise.
|
|
29
|
+
*/
|
|
30
|
+
export const buildStores = (storage) => ({
|
|
31
|
+
charges: botRecord(storage, "pay:charge", chargeRecordSchema),
|
|
32
|
+
idempotency: botSentinel(storage, "pay:idem"),
|
|
33
|
+
userCharges: (userId) => botIndex(storage, `pay:user:${userId}:charges`, {
|
|
34
|
+
capacity: USER_CHARGES_CAP,
|
|
35
|
+
}),
|
|
36
|
+
payouts: botRecord(storage, "pay:payout", payoutRecordSchema),
|
|
37
|
+
payoutsIndex: botIndex(storage, "pay:payouts:index"),
|
|
38
|
+
});
|
|
39
|
+
//# sourceMappingURL=stores.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stores.js","sourceRoot":"","sources":["../../../src/bot/payments/stores.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAGtE;;4DAE4D;AAC5D,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAmB7B;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,OAAgB,EAAkB,EAAE,CAAC,CAAC;IACjE,OAAO,EAAE,SAAS,CAAe,OAAO,EAAE,YAAY,EAAE,kBAAkB,CAAC;IAC3E,WAAW,EAAE,WAAW,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7C,WAAW,EAAE,CAAC,MAAc,EAAE,EAAE,CAC/B,QAAQ,CAAC,OAAO,EAAE,YAAY,MAAM,UAAU,EAAE;QAC/C,QAAQ,EAAE,gBAAgB;KAC1B,CAAC;IACH,OAAO,EAAE,SAAS,CAAe,OAAO,EAAE,YAAY,EAAE,kBAAkB,CAAC;IAC3E,YAAY,EAAE,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC;CACpD,CAAC,CAAC"}
|