@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.
Files changed (101) hide show
  1. package/README.md +3 -1
  2. package/dist/bot/access-control.d.ts.map +1 -1
  3. package/dist/bot/access-control.js +17 -1
  4. package/dist/bot/access-control.js.map +1 -1
  5. package/dist/bot/allow-list.d.ts +38 -0
  6. package/dist/bot/allow-list.d.ts.map +1 -0
  7. package/dist/bot/allow-list.js +41 -0
  8. package/dist/bot/allow-list.js.map +1 -0
  9. package/dist/bot/callbacks.d.ts +89 -0
  10. package/dist/bot/callbacks.d.ts.map +1 -0
  11. package/dist/bot/callbacks.js +128 -0
  12. package/dist/bot/callbacks.js.map +1 -0
  13. package/dist/bot/ctx.d.ts +219 -0
  14. package/dist/bot/ctx.d.ts.map +1 -0
  15. package/dist/bot/ctx.js +52 -0
  16. package/dist/bot/ctx.js.map +1 -0
  17. package/dist/bot/index.d.ts +4 -0
  18. package/dist/bot/index.d.ts.map +1 -1
  19. package/dist/bot/index.js +4 -0
  20. package/dist/bot/index.js.map +1 -1
  21. package/dist/bot/kit.d.ts +1 -1
  22. package/dist/bot/kit.d.ts.map +1 -1
  23. package/dist/bot/kit.js +4 -2
  24. package/dist/bot/kit.js.map +1 -1
  25. package/dist/bot/menu.d.ts +23 -1
  26. package/dist/bot/menu.d.ts.map +1 -1
  27. package/dist/bot/menu.js +30 -1
  28. package/dist/bot/menu.js.map +1 -1
  29. package/dist/bot/payments/callbacks.d.ts +28 -0
  30. package/dist/bot/payments/callbacks.d.ts.map +1 -0
  31. package/dist/bot/payments/callbacks.js +102 -0
  32. package/dist/bot/payments/callbacks.js.map +1 -0
  33. package/dist/bot/payments/commands.d.ts +26 -0
  34. package/dist/bot/payments/commands.d.ts.map +1 -0
  35. package/dist/bot/payments/commands.js +41 -0
  36. package/dist/bot/payments/commands.js.map +1 -0
  37. package/dist/bot/payments/config.d.ts +30 -0
  38. package/dist/bot/payments/config.d.ts.map +1 -0
  39. package/dist/bot/payments/config.js +166 -0
  40. package/dist/bot/payments/config.js.map +1 -0
  41. package/dist/bot/payments/derive.d.ts +75 -0
  42. package/dist/bot/payments/derive.d.ts.map +1 -0
  43. package/dist/bot/payments/derive.js +151 -0
  44. package/dist/bot/payments/derive.js.map +1 -0
  45. package/dist/bot/payments/handlers.d.ts +113 -0
  46. package/dist/bot/payments/handlers.d.ts.map +1 -0
  47. package/dist/bot/payments/handlers.js +284 -0
  48. package/dist/bot/payments/handlers.js.map +1 -0
  49. package/dist/bot/payments/index.d.ts +17 -0
  50. package/dist/bot/payments/index.d.ts.map +1 -0
  51. package/dist/bot/payments/index.js +14 -0
  52. package/dist/bot/payments/index.js.map +1 -0
  53. package/dist/bot/payments/invoice.d.ts +101 -0
  54. package/dist/bot/payments/invoice.d.ts.map +1 -0
  55. package/dist/bot/payments/invoice.js +213 -0
  56. package/dist/bot/payments/invoice.js.map +1 -0
  57. package/dist/bot/payments/menu-item.d.ts +41 -0
  58. package/dist/bot/payments/menu-item.d.ts.map +1 -0
  59. package/dist/bot/payments/menu-item.js +259 -0
  60. package/dist/bot/payments/menu-item.js.map +1 -0
  61. package/dist/bot/payments/payload.d.ts +42 -0
  62. package/dist/bot/payments/payload.d.ts.map +1 -0
  63. package/dist/bot/payments/payload.js +69 -0
  64. package/dist/bot/payments/payload.js.map +1 -0
  65. package/dist/bot/payments/payouts.d.ts +88 -0
  66. package/dist/bot/payments/payouts.d.ts.map +1 -0
  67. package/dist/bot/payments/payouts.js +133 -0
  68. package/dist/bot/payments/payouts.js.map +1 -0
  69. package/dist/bot/payments/plugin.d.ts +271 -0
  70. package/dist/bot/payments/plugin.d.ts.map +1 -0
  71. package/dist/bot/payments/plugin.js +141 -0
  72. package/dist/bot/payments/plugin.js.map +1 -0
  73. package/dist/bot/payments/refund.d.ts +124 -0
  74. package/dist/bot/payments/refund.d.ts.map +1 -0
  75. package/dist/bot/payments/refund.js +308 -0
  76. package/dist/bot/payments/refund.js.map +1 -0
  77. package/dist/bot/payments/schemas.d.ts +58 -0
  78. package/dist/bot/payments/schemas.d.ts.map +1 -0
  79. package/dist/bot/payments/schemas.js +55 -0
  80. package/dist/bot/payments/schemas.js.map +1 -0
  81. package/dist/bot/payments/state.d.ts +90 -0
  82. package/dist/bot/payments/state.d.ts.map +1 -0
  83. package/dist/bot/payments/state.js +149 -0
  84. package/dist/bot/payments/state.js.map +1 -0
  85. package/dist/bot/payments/stores.d.ts +44 -0
  86. package/dist/bot/payments/stores.d.ts.map +1 -0
  87. package/dist/bot/payments/stores.js +39 -0
  88. package/dist/bot/payments/stores.js.map +1 -0
  89. package/dist/bot/payments/types.d.ts +299 -0
  90. package/dist/bot/payments/types.d.ts.map +1 -0
  91. package/dist/bot/payments/types.js +39 -0
  92. package/dist/bot/payments/types.js.map +1 -0
  93. package/dist/bot/payments/waiver.d.ts +81 -0
  94. package/dist/bot/payments/waiver.d.ts.map +1 -0
  95. package/dist/bot/payments/waiver.js +145 -0
  96. package/dist/bot/payments/waiver.js.map +1 -0
  97. package/dist/bot/storage.d.ts +154 -0
  98. package/dist/bot/storage.d.ts.map +1 -0
  99. package/dist/bot/storage.js +222 -0
  100. package/dist/bot/storage.js.map +1 -0
  101. package/package.json +28 -3
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Callback-query handler bodies — extracted from `plugin.ts` per the
3
+ * `bot/CLAUDE.md` plugin file convention (callbacks.ts owns the
4
+ * `.callbackQuery()` handler bodies; plugin.ts is wiring only).
5
+ *
6
+ * The CallbackData schemas themselves live with their owning feature
7
+ * (waiver.ts, refund.ts) — those modules also export `buildXxxHandler`
8
+ * factories for handlers that need richer dependencies (refund's flow
9
+ * needs `stores + storage + cfg`). This file holds the simpler
10
+ * inline-style handlers that didn't justify their own factory.
11
+ *
12
+ * - `buildWaiverConsentHandler({ cfg, catalog })`
13
+ * persists consent, deletes the prompt message, re-enters the
14
+ * invoice flow for the carried productKey.
15
+ * - `buildWaiverCancelHandler()`
16
+ * acknowledges the tap and edits the prompt to a "canceled"
17
+ * message.
18
+ * - `buildRefundCloseHandler()`
19
+ * admin-only: deletes the DM notification on the close button.
20
+ */
21
+ import { say } from "../../say/index.js";
22
+ import { narrow } from "../ctx.js";
23
+ import { presentInvoice } from "./invoice.js";
24
+ import { persistWaiverOnSession } from "./waiver.js";
25
+ const FALLBACK_LANG = "en";
26
+ // ─── waiver consent ───────────────────────────────────────────────
27
+ export const buildWaiverConsentHandler = (args) => async (ctx) => {
28
+ const c = narrow(ctx);
29
+ const lang = c.session.language ?? FALLBACK_LANG;
30
+ const userId = c.from?.id;
31
+ if (!userId) {
32
+ await c.answer({
33
+ text: say({
34
+ en: "Could not identify you.",
35
+ es: "No se pudo identificarte.",
36
+ }, lang),
37
+ });
38
+ return;
39
+ }
40
+ const product = args.catalog.byKey.get(c.queryData.pk);
41
+ if (!product) {
42
+ await c.answer({
43
+ text: say({ en: "Unknown product.", es: "Producto desconocido." }, lang),
44
+ });
45
+ return;
46
+ }
47
+ // Persist consent on the user's session (cur ctx IS the user
48
+ // since they're tapping their own message).
49
+ persistWaiverOnSession(narrow(c), args.cfg.waiver.version, lang);
50
+ // Acknowledge the tap with a toast, then DELETE the consent
51
+ // prompt entirely. Previously we edited the body to
52
+ // "✅ Consent recorded. Sending invoice…" which goes stale the
53
+ // moment the invoice payment sheet appears underneath — visible
54
+ // noise in the chat history forever after. The toast confirms
55
+ // what happened; the user's next interaction is the Telegram
56
+ // payment sheet itself.
57
+ await c.answer({
58
+ text: say({ en: "✅ Consent recorded.", es: "✅ Consentimiento registrado." }, lang),
59
+ });
60
+ try {
61
+ await c.message?.delete?.();
62
+ }
63
+ catch {
64
+ /* message too old to delete — ignore */
65
+ }
66
+ // Re-enter the invoice flow now that consent is fresh.
67
+ await presentInvoice(narrow(c), args.cfg, args.catalog, product.id);
68
+ };
69
+ // ─── waiver cancel ────────────────────────────────────────────────
70
+ export const buildWaiverCancelHandler = () => async (ctx) => {
71
+ const c = narrow(ctx);
72
+ const lang = c.session.language ?? FALLBACK_LANG;
73
+ await c.answer({
74
+ text: say({ en: "Canceled.", es: "Cancelado." }, lang),
75
+ });
76
+ try {
77
+ await c.editText(say({ en: "❌ Purchase canceled.", es: "❌ Compra cancelada." }, lang));
78
+ }
79
+ catch {
80
+ /* ignore — message too old to edit */
81
+ }
82
+ };
83
+ // ─── refund close (admin-only) ────────────────────────────────────
84
+ export const buildRefundCloseHandler = () => async (ctx) => {
85
+ const c = narrow(ctx);
86
+ const lang = c.session.language ?? FALLBACK_LANG;
87
+ if (!c.isAdmin) {
88
+ await c.answer({
89
+ text: say({ en: "Admin only.", es: "Solo admin." }, lang),
90
+ show_alert: true,
91
+ });
92
+ return;
93
+ }
94
+ await c.answer({});
95
+ try {
96
+ await c.message?.delete?.();
97
+ }
98
+ catch {
99
+ /* ignore — message too old to delete */
100
+ }
101
+ };
102
+ //# sourceMappingURL=callbacks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"callbacks.js","sourceRoot":"","sources":["../../../src/bot/payments/callbacks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Slash-command handlers. The `/paysupport` command is mandated by
3
+ * Telegram Bot Developer ToS §6.5 — every bot accepting payments must
4
+ * expose a way for users to reach support about charges.
5
+ *
6
+ * The actual command registration (`.command("paysupport", { … }, fn)`)
7
+ * happens in `plugin.ts`; this module owns the body + the response
8
+ * text so wording stays in one place.
9
+ */
10
+ import type { BotPaymentsConfig } from "./types.js";
11
+ /**
12
+ * Mandatory `/paysupport` text. ToS §6.5 requires every bot accepting
13
+ * payments to expose this. Brief message pointing the user to the menu
14
+ * (where the refund flow lives) and to the human admin contact.
15
+ *
16
+ * Exported because tests + future admin tooling may want to render the
17
+ * exact wording the bot uses without going through the command handler.
18
+ */
19
+ export declare const buildPaysupportText: (cfg: BotPaymentsConfig<string>, lang: string) => string;
20
+ /**
21
+ * Returns the `/paysupport` command handler. Pure factory — `plugin.ts`
22
+ * calls this once at construction and passes the result to gramio's
23
+ * `.command()`.
24
+ */
25
+ export declare const buildPaysupportCommand: (cfg: BotPaymentsConfig<string>) => (ctx: unknown) => Promise<void>;
26
+ //# sourceMappingURL=commands.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../../src/bot/payments/commands.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAmB,MAAM,YAAY,CAAC;AAMrE;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,GAC/B,KAAK,iBAAiB,CAAC,MAAM,CAAC,EAC9B,MAAM,MAAM,KACV,MAeD,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,GACjC,KAAK,iBAAiB,CAAC,MAAM,CAAC,MACxB,KAAK,OAAO,KAAG,OAAO,CAAC,IAAI,CAIjC,CAAC"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Slash-command handlers. The `/paysupport` command is mandated by
3
+ * Telegram Bot Developer ToS §6.5 — every bot accepting payments must
4
+ * expose a way for users to reach support about charges.
5
+ *
6
+ * The actual command registration (`.command("paysupport", { … }, fn)`)
7
+ * happens in `plugin.ts`; this module owns the body + the response
8
+ * text so wording stays in one place.
9
+ */
10
+ import { say } from "../../say/index.js";
11
+ import { narrow } from "../ctx.js";
12
+ const FALLBACK_LANG = "en";
13
+ /**
14
+ * Mandatory `/paysupport` text. ToS §6.5 requires every bot accepting
15
+ * payments to expose this. Brief message pointing the user to the menu
16
+ * (where the refund flow lives) and to the human admin contact.
17
+ *
18
+ * Exported because tests + future admin tooling may want to render the
19
+ * exact wording the bot uses without going through the command handler.
20
+ */
21
+ export const buildPaysupportText = (cfg, lang) => say({
22
+ en: `Need help with a payment?\n\n` +
23
+ `• Open /settings → 💎 VIP → 📜 History to see your charges and request a refund.\n` +
24
+ `• Or contact: ${cfg.paysupport}\n\n` +
25
+ `Refund disputes are handled by the admin within Telegram.`,
26
+ es: `¿Necesitas ayuda con un pago?\n\n` +
27
+ `• Abre /settings → 💎 VIP → 📜 Historial para ver tus cargos y solicitar reembolso.\n` +
28
+ `• O contacta: ${cfg.paysupport}\n\n` +
29
+ `Las disputas las gestiona el admin dentro de Telegram.`,
30
+ }, lang);
31
+ /**
32
+ * Returns the `/paysupport` command handler. Pure factory — `plugin.ts`
33
+ * calls this once at construction and passes the result to gramio's
34
+ * `.command()`.
35
+ */
36
+ export const buildPaysupportCommand = (cfg) => async (ctx) => {
37
+ const c = narrow(ctx);
38
+ const lang = c.session.language ?? FALLBACK_LANG;
39
+ await c.send(buildPaysupportText(cfg, lang));
40
+ };
41
+ //# sourceMappingURL=commands.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.js","sourceRoot":"","sources":["../../../src/bot/payments/commands.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC;AACzC,OAAO,EAAsB,MAAM,EAAE,MAAM,WAAW,CAAC;AAGvD,MAAM,aAAa,GAAG,IAAI,CAAC;AAI3B;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAClC,GAA8B,EAC9B,IAAY,EACH,EAAE,CACX,GAAG,CACF;IACC,EAAE,EACD,+BAA+B;QAC/B,oFAAoF;QACpF,iBAAiB,GAAG,CAAC,UAAU,MAAM;QACrC,2DAA2D;IAC5D,EAAE,EACD,mCAAmC;QACnC,uFAAuF;QACvF,iBAAiB,GAAG,CAAC,UAAU,MAAM;QACrC,wDAAwD;CACzD,EACD,IAAI,CACJ,CAAC;AAEH;;;;GAIG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAClC,CAAC,GAA8B,EAAE,EAAE,CACnC,KAAK,EAAE,GAAY,EAAiB,EAAE;IACrC,MAAM,CAAC,GAAG,MAAM,CAA6B,GAAG,CAAC,CAAC;IAClD,MAAM,IAAI,GAAG,CAAC,CAAC,OAAO,CAAC,QAAQ,IAAI,aAAa,CAAC;IACjD,MAAM,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;AAC9C,CAAC,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Construction-time validation + catalog build.
3
+ *
4
+ * `validateConfig(cfg)` is the gate. It throws `Panic` on every shape
5
+ * problem the bot author can introduce — a misconfigured payments
6
+ * plugin should scream at startup, not silently misbehave at runtime
7
+ * (see `src/offensive.ts` and CLAUDE.md §"Failure modes").
8
+ *
9
+ * `buildCatalog(cfg)` produces the normalized `ProductCatalog` the rest
10
+ * of the plugin consults (positional ids resolved, polyglots passed
11
+ * through, O(1) lookup by ProductKey).
12
+ *
13
+ * Both are pure — no gramio, no storage, no side effects.
14
+ */
15
+ import { type BotPaymentsConfig, type ProductCatalog } from "./types.js";
16
+ /**
17
+ * Validates the config and returns the **locales set** that the plugin
18
+ * will use for cross-checking other Polyglots. Derived from
19
+ * `waiver.text` (the single mandatory Polyglot) — every other Polyglot
20
+ * in the config must cover the same locales.
21
+ *
22
+ * Throws `Panic` on any structural problem.
23
+ */
24
+ export declare const validateConfig: (cfg: BotPaymentsConfig<string>) => ReadonlySet<string>;
25
+ /**
26
+ * Materialize the validated config into the runtime catalog. Assumes
27
+ * `validateConfig(cfg)` has already been called and passed.
28
+ */
29
+ export declare const buildCatalog: (cfg: BotPaymentsConfig<string>) => ProductCatalog;
30
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/bot/payments/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,EACN,KAAK,iBAAiB,EAGtB,KAAK,cAAc,EAGnB,MAAM,YAAY,CAAC;AAwCpB;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,GAC1B,KAAK,iBAAiB,CAAC,MAAM,CAAC,KAC5B,WAAW,CAAC,MAAM,CAuFpB,CAAC;AAIF;;;GAGG;AACH,eAAO,MAAM,YAAY,GACxB,KAAK,iBAAiB,CAAC,MAAM,CAAC,KAC5B,cA2CF,CAAC"}
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Construction-time validation + catalog build.
3
+ *
4
+ * `validateConfig(cfg)` is the gate. It throws `Panic` on every shape
5
+ * problem the bot author can introduce — a misconfigured payments
6
+ * plugin should scream at startup, not silently misbehave at runtime
7
+ * (see `src/offensive.ts` and CLAUDE.md §"Failure modes").
8
+ *
9
+ * `buildCatalog(cfg)` produces the normalized `ProductCatalog` the rest
10
+ * of the plugin consults (positional ids resolved, polyglots passed
11
+ * through, O(1) lookup by ProductKey).
12
+ *
13
+ * Both are pure — no gramio, no storage, no side effects.
14
+ */
15
+ import { panic } from "../../offensive.js";
16
+ import { SUBSCRIPTION_PERIOD_SECONDS, } from "./types.js";
17
+ // ─── validation ────────────────────────────────────────────────────
18
+ const requireNonEmptyString = (value, where) => {
19
+ if (typeof value !== "string" || value.length === 0) {
20
+ panic(`bot/payments: ${where} must be a non-empty string (got ${value})`);
21
+ }
22
+ return value;
23
+ };
24
+ const requirePositiveInt = (value, where) => {
25
+ if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
26
+ panic(`bot/payments: ${where} must be a positive integer (got ${value})`);
27
+ }
28
+ return value;
29
+ };
30
+ const polyglotKeys = (p) => Object.keys(p).filter((k) => typeof p[k] === "string");
31
+ const requirePolyglotCovers = (p, locales, where) => {
32
+ if (!p || typeof p !== "object") {
33
+ panic(`bot/payments: ${where} must be a Polyglot object`);
34
+ }
35
+ if (locales.size === 0)
36
+ return; // no required-locales declared — accept anything non-empty
37
+ for (const loc of locales) {
38
+ const v = p[loc];
39
+ if (typeof v !== "string" || v.length === 0) {
40
+ panic(`bot/payments: ${where} is missing a non-empty entry for locale "${loc}"`);
41
+ }
42
+ }
43
+ };
44
+ /**
45
+ * Validates the config and returns the **locales set** that the plugin
46
+ * will use for cross-checking other Polyglots. Derived from
47
+ * `waiver.text` (the single mandatory Polyglot) — every other Polyglot
48
+ * in the config must cover the same locales.
49
+ *
50
+ * Throws `Panic` on any structural problem.
51
+ */
52
+ export const validateConfig = (cfg) => {
53
+ if (!cfg || typeof cfg !== "object") {
54
+ panic("bot/payments: config must be an object");
55
+ }
56
+ requireNonEmptyString(cfg.paysupport, "paysupport");
57
+ requireNonEmptyString(cfg.legal?.sellerName, "legal.sellerName");
58
+ requireNonEmptyString(cfg.legal?.nif, "legal.nif");
59
+ // termsUrl and privacyUrl are OPTIONAL. privacyUrl gets a sensible
60
+ // Telegram-side default at use time (waiver.ts). termsUrl, when
61
+ // omitted, hides the 📖 Terms button from the consent prompt — no
62
+ // canonical Telegram ToS to fall back to.
63
+ if (cfg.legal?.termsUrl !== undefined) {
64
+ requireNonEmptyString(cfg.legal.termsUrl, "legal.termsUrl");
65
+ }
66
+ if (cfg.legal?.privacyUrl !== undefined) {
67
+ requireNonEmptyString(cfg.legal.privacyUrl, "legal.privacyUrl");
68
+ }
69
+ requireNonEmptyString(cfg.waiver?.version, "waiver.version");
70
+ const waiverKeys = polyglotKeys(cfg.waiver?.text ?? {});
71
+ if (waiverKeys.length === 0) {
72
+ panic("bot/payments: waiver.text must declare at least one locale with non-empty content");
73
+ }
74
+ const locales = new Set(waiverKeys);
75
+ const hasVip = Array.isArray(cfg.vip) && cfg.vip.length > 0;
76
+ const hasCredits = !!cfg.credits &&
77
+ Array.isArray(cfg.credits.packs) &&
78
+ cfg.credits.packs.length > 0;
79
+ const hasPerks = !!cfg.perks && Object.keys(cfg.perks).length > 0;
80
+ if (!hasVip && !hasCredits && !hasPerks) {
81
+ panic("bot/payments: declare at least one of `vip`, `credits.packs`, or `perks` — " +
82
+ "a payments plugin with no products has nothing to sell");
83
+ }
84
+ if (hasVip) {
85
+ cfg.vip?.forEach((rung, i) => {
86
+ const at = `vip[${i}]`;
87
+ requirePositiveInt(rung.xtr, `${at}.xtr`);
88
+ if (rung.period !== "30d") {
89
+ panic(`bot/payments: ${at}.period must be "30d" (Telegram supports no other ` +
90
+ `subscription period as of 2026) — got "${rung.period}"`);
91
+ }
92
+ requirePolyglotCovers(rung.name, locales, `${at}.name`);
93
+ if (rung.grants?.credits !== undefined) {
94
+ requirePositiveInt(rung.grants.credits, `${at}.grants.credits`);
95
+ }
96
+ });
97
+ }
98
+ if (hasCredits) {
99
+ const c = cfg.credits;
100
+ if (!c)
101
+ panic("bot/payments: credits config disappeared mid-validation");
102
+ requirePolyglotCovers(c.unit, locales, "credits.unit");
103
+ c.packs.forEach((pack, i) => {
104
+ const at = `credits.packs[${i}]`;
105
+ requirePositiveInt(pack.xtr, `${at}.xtr`);
106
+ requirePositiveInt(pack.grants?.credits, `${at}.grants.credits`);
107
+ if (pack.name) {
108
+ requirePolyglotCovers(pack.name, locales, `${at}.name`);
109
+ }
110
+ });
111
+ }
112
+ if (hasPerks && cfg.perks) {
113
+ for (const [key, perk] of Object.entries(cfg.perks)) {
114
+ const at = `perks.${key}`;
115
+ if (!/^[a-z][a-z0-9_]*$/i.test(key)) {
116
+ panic(`bot/payments: perk key "${key}" must match /^[a-z][a-z0-9_]*$/i (used in ` +
117
+ `payload encoding, must be safe)`);
118
+ }
119
+ requirePositiveInt(perk.xtr, `${at}.xtr`);
120
+ requirePolyglotCovers(perk.name, locales, `${at}.name`);
121
+ }
122
+ }
123
+ return locales;
124
+ };
125
+ // ─── catalog build ─────────────────────────────────────────────────
126
+ /**
127
+ * Materialize the validated config into the runtime catalog. Assumes
128
+ * `validateConfig(cfg)` has already been called and passed.
129
+ */
130
+ export const buildCatalog = (cfg) => {
131
+ const vip = (cfg.vip ?? []).map((rung, i) => ({
132
+ id: `vip.${i + 1}`,
133
+ rank: i + 1,
134
+ xtr: rung.xtr,
135
+ periodSeconds: SUBSCRIPTION_PERIOD_SECONDS[rung.period],
136
+ name: rung.name,
137
+ creditsGranted: rung.grants?.credits ?? 0,
138
+ }));
139
+ const creditsPacks = (cfg.credits?.packs ?? []).map((pack, i) => ({
140
+ id: `credits.${i + 1}`,
141
+ xtr: pack.xtr,
142
+ creditsGranted: pack.grants.credits,
143
+ name: pack.name,
144
+ }));
145
+ const perks = Object.entries(cfg.perks ?? {}).map(([key, perk]) => ({
146
+ id: `perks.${key}`,
147
+ key,
148
+ xtr: perk.xtr,
149
+ name: perk.name,
150
+ }));
151
+ const byKey = new Map();
152
+ for (const r of vip)
153
+ byKey.set(r.id, r);
154
+ for (const p of creditsPacks)
155
+ byKey.set(p.id, p);
156
+ for (const p of perks)
157
+ byKey.set(p.id, p);
158
+ return {
159
+ vip,
160
+ creditsUnit: cfg.credits?.unit,
161
+ creditsPacks,
162
+ perks,
163
+ byKey,
164
+ };
165
+ };
166
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/bot/payments/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * `ctx.payments` derive — the per-event ctx decoration.
3
+ *
4
+ * Extracted from `plugin.ts` to keep that file pure wiring per the
5
+ * `bot/CLAUDE.md` plugin file convention. Everything that reads or
6
+ * mutates session state inside an event handler goes through one of
7
+ * the methods this module attaches:
8
+ *
9
+ * - `ctx.payments.atLeast(id)` — tier gate (lazy expiry)
10
+ * - `ctx.payments.tier()` — current rung (`'free' | 'vip.N'`)
11
+ * - `ctx.payments.tier.level()` — integer rank
12
+ * - `ctx.payments.tier.label()` — resolved Polyglot label
13
+ * - `ctx.payments.credits.*` — balance / consume / tryConsume
14
+ * - `ctx.payments.has(perkId)` — perk ownership boolean
15
+ * - `ctx.payments.require(...)` — gate + localized upgrade prompt
16
+ * - `ctx.payments.invoice(key)` — purchase entry (waiver-gated)
17
+ *
18
+ * Returned shape is a function so `plugin.ts` can drop it directly
19
+ * into `.derive(["message", "callback_query"], buildPaymentsDerive({...}))`.
20
+ */
21
+ import { type BotPaymentsConfig, type ProductCatalog } from "./types.js";
22
+ type TierKey = "free" | `vip.${number}`;
23
+ /**
24
+ * Callable `tier` namespace. Matches `PaymentsCtx['tier']` from `plugin.ts`
25
+ * — exported there as the public surface. Repeated minimally here so
26
+ * derive.ts isn't import-cycle dependent on plugin.ts.
27
+ */
28
+ export type TierFn = (() => TierKey) & {
29
+ level: () => number;
30
+ label: () => string | undefined;
31
+ };
32
+ export type CreditsApi = {
33
+ balance: () => number;
34
+ consume: (n: number) => void;
35
+ tryConsume: (n: number) => boolean;
36
+ };
37
+ export type RequireOpts = {
38
+ feature?: {
39
+ en: string;
40
+ es: string;
41
+ } | string;
42
+ };
43
+ /**
44
+ * What `buildPaymentsDerive` attaches under `ctx.payments`. Plugin.ts
45
+ * casts this to the typed `PaymentsCtx<Cfg>` (with positional ladder
46
+ * narrowing) before exposing it to bot authors — TypeScript can't
47
+ * preserve the generic narrowing through a function-shaped derive
48
+ * factory cleanly, so the strict type lives at the public surface.
49
+ */
50
+ export type PaymentsDerived = {
51
+ atLeast: (id: string) => boolean;
52
+ tier: TierFn;
53
+ has: (perkId: string) => boolean;
54
+ credits: CreditsApi;
55
+ require: (id: string, opts?: RequireOpts) => Promise<boolean>;
56
+ invoice: (productKey: string) => Promise<"invoice_sent" | "waiver_prompt_sent" | "unknown_product">;
57
+ };
58
+ export type BuildPaymentsDeriveArgs = {
59
+ /** Resolved product catalogue (rungs, packs, perks). */
60
+ catalog: ProductCatalog;
61
+ /**
62
+ * Full config — currently only consulted inside `invoice()` for the
63
+ * waiver wording + product display strings via `presentInvoice`.
64
+ */
65
+ cfg: BotPaymentsConfig<string>;
66
+ };
67
+ /**
68
+ * Returns the function you pass to gramio's `.derive([events], fn)`.
69
+ * The returned function takes ctx, returns `{ payments }`.
70
+ */
71
+ export declare const buildPaymentsDerive: ({ cfg, catalog, }: BuildPaymentsDeriveArgs) => (ctx: unknown) => {
72
+ payments: PaymentsDerived;
73
+ };
74
+ export {};
75
+ //# sourceMappingURL=derive.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"derive.d.ts","sourceRoot":"","sources":["../../../src/bot/payments/derive.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAMH,OAAO,EACN,KAAK,iBAAiB,EAGtB,KAAK,cAAc,EACnB,MAAM,YAAY,CAAC;AAMpB,KAAK,OAAO,GAAG,MAAM,GAAG,OAAO,MAAM,EAAE,CAAC;AAExC;;;;GAIG;AACH,MAAM,MAAM,MAAM,GAAG,CAAC,MAAM,OAAO,CAAC,GAAG;IACtC,KAAK,EAAE,MAAM,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACxB,OAAO,EAAE,MAAM,MAAM,CAAC;IACtB,OAAO,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,UAAU,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;CACnC,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACzB,OAAO,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM,CAAC;CAC9C,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG;IAC7B,OAAO,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;IACjC,OAAO,EAAE,UAAU,CAAC;IACpB,OAAO,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9D,OAAO,EAAE,CACR,UAAU,EAAE,MAAM,KACd,OAAO,CAAC,cAAc,GAAG,oBAAoB,GAAG,iBAAiB,CAAC,CAAC;CACxE,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACrC,wDAAwD;IACxD,OAAO,EAAE,cAAc,CAAC;IACxB;;;OAGG;IACH,GAAG,EAAE,iBAAiB,CAAC,MAAM,CAAC,CAAC;CAC/B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAAI,mBAGjC,uBAAuB,MACjB,KAAK,OAAO,KAAG;IAAE,QAAQ,EAAE,eAAe,CAAA;CA+IlD,CAAC"}
@@ -0,0 +1,151 @@
1
+ /**
2
+ * `ctx.payments` derive — the per-event ctx decoration.
3
+ *
4
+ * Extracted from `plugin.ts` to keep that file pure wiring per the
5
+ * `bot/CLAUDE.md` plugin file convention. Everything that reads or
6
+ * mutates session state inside an event handler goes through one of
7
+ * the methods this module attaches:
8
+ *
9
+ * - `ctx.payments.atLeast(id)` — tier gate (lazy expiry)
10
+ * - `ctx.payments.tier()` — current rung (`'free' | 'vip.N'`)
11
+ * - `ctx.payments.tier.level()` — integer rank
12
+ * - `ctx.payments.tier.label()` — resolved Polyglot label
13
+ * - `ctx.payments.credits.*` — balance / consume / tryConsume
14
+ * - `ctx.payments.has(perkId)` — perk ownership boolean
15
+ * - `ctx.payments.require(...)` — gate + localized upgrade prompt
16
+ * - `ctx.payments.invoice(key)` — purchase entry (waiver-gated)
17
+ *
18
+ * Returned shape is a function so `plugin.ts` can drop it directly
19
+ * into `.derive(["message", "callback_query"], buildPaymentsDerive({...}))`.
20
+ */
21
+ import { say } from "../../say/index.js";
22
+ import { narrow } from "../ctx.js";
23
+ import { menuNavCb } from "../menu.js";
24
+ import { presentInvoice } from "./invoice.js";
25
+ import { InsufficientCredits, } from "./types.js";
26
+ const FALLBACK_LANG = "en";
27
+ /**
28
+ * Returns the function you pass to gramio's `.derive([events], fn)`.
29
+ * The returned function takes ctx, returns `{ payments }`.
30
+ */
31
+ export const buildPaymentsDerive = ({ cfg, catalog, }) => {
32
+ return (ctx) => {
33
+ // Single narrow at the top — every subsequent reference uses
34
+ // `c.<field>` and reads cleanly. The structural type is the
35
+ // intersection of message + callback_query event ctxs so this
36
+ // derive works for both event scopes (gramio's runtime dedup
37
+ // guarantees one execution per update).
38
+ const c = narrow(ctx);
39
+ // Lazy expiry — vip is only "active" while its expiresAt is in
40
+ // the future (CLAUDE.md §"sweep" deferred to v2). Pure read; no
41
+ // cleanup of stale `session.pay.vip` records.
42
+ const isVipActive = () => {
43
+ const v = c.session.pay?.vip;
44
+ if (!v)
45
+ return false;
46
+ return v.expiresAt * 1000 > Date.now();
47
+ };
48
+ const effectiveRung = () => {
49
+ if (!isVipActive())
50
+ return 0;
51
+ return c.session.pay?.vip?.rung ?? 0;
52
+ };
53
+ const atLeast = (id) => {
54
+ if (id === "free")
55
+ return true;
56
+ const rung = effectiveRung();
57
+ if (id === "vip")
58
+ return rung > 0;
59
+ if (id.startsWith("vip.")) {
60
+ const want = Number.parseInt(id.slice(4), 10);
61
+ return rung >= want;
62
+ }
63
+ return false;
64
+ };
65
+ const tierFn = (() => {
66
+ const rung = effectiveRung();
67
+ if (rung === 0)
68
+ return "free";
69
+ return `vip.${rung}`;
70
+ });
71
+ tierFn.level = () => effectiveRung();
72
+ tierFn.label = () => {
73
+ const rung = effectiveRung();
74
+ if (rung === 0)
75
+ return undefined;
76
+ const r = catalog.vip[rung - 1];
77
+ if (!r)
78
+ return undefined;
79
+ const lang = c.session.language ?? FALLBACK_LANG;
80
+ return (r.name[lang] ??
81
+ r.name[FALLBACK_LANG]);
82
+ };
83
+ const credits = {
84
+ balance: () => c.session.pay?.credits ?? 0,
85
+ consume: (n) => {
86
+ const current = c.session.pay?.credits ?? 0;
87
+ if (current < n)
88
+ throw new InsufficientCredits(n, current);
89
+ c.session.pay ??= {};
90
+ c.session.pay.credits = current - n;
91
+ },
92
+ tryConsume: (n) => {
93
+ const current = c.session.pay?.credits ?? 0;
94
+ if (current < n)
95
+ return false;
96
+ c.session.pay ??= {};
97
+ c.session.pay.credits = current - n;
98
+ return true;
99
+ },
100
+ };
101
+ const has = (perkId) => !!c.session.pay?.perks?.[perkId];
102
+ const requireFn = async (id, opts) => {
103
+ if (atLeast(id))
104
+ return true;
105
+ const lang = c.session.language ?? FALLBACK_LANG;
106
+ const featureName = opts?.feature === undefined
107
+ ? undefined
108
+ : typeof opts.feature === "string"
109
+ ? opts.feature
110
+ : (opts.feature[lang] ?? opts.feature.en);
111
+ const body = featureName !== undefined
112
+ ? say({
113
+ en: `💎 "${featureName}" needs VIP. Tap below to upgrade.`,
114
+ es: `💎 "${featureName}" necesita VIP. Toca abajo para mejorar.`,
115
+ }, lang)
116
+ : say({
117
+ en: "💎 This needs VIP. Tap below to upgrade.",
118
+ es: "💎 Esto necesita VIP. Toca abajo para mejorar.",
119
+ }, lang);
120
+ // Deep-link into our menuItem (id='pay') via botMenu's
121
+ // shared menuNavCb schema. v1 opens VIP root; deep-linking to
122
+ // a specific rung is a v2 TODO (see CLAUDE.md).
123
+ const button = say({ en: "💎 Open VIP plans", es: "💎 Ver planes VIP" }, lang);
124
+ await c.send(body, {
125
+ reply_markup: {
126
+ inline_keyboard: [
127
+ [
128
+ {
129
+ text: button,
130
+ callback_data: menuNavCb.pack({ path: "pay" }),
131
+ },
132
+ ],
133
+ ],
134
+ },
135
+ });
136
+ return false;
137
+ };
138
+ const invoiceFn = (productKey) => presentInvoice(narrow(c), cfg, catalog, productKey);
139
+ return {
140
+ payments: {
141
+ atLeast,
142
+ tier: tierFn,
143
+ has,
144
+ credits,
145
+ require: requireFn,
146
+ invoice: invoiceFn,
147
+ },
148
+ };
149
+ };
150
+ };
151
+ //# sourceMappingURL=derive.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"derive.js","sourceRoot":"","sources":["../../../src/bot/payments/derive.ts"],"names":[],"mappings":"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"}