@adriangalilea/utils 0.18.0 → 0.19.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 (97) hide show
  1. package/README.md +2 -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/callbacks.d.ts +89 -0
  6. package/dist/bot/callbacks.d.ts.map +1 -0
  7. package/dist/bot/callbacks.js +128 -0
  8. package/dist/bot/callbacks.js.map +1 -0
  9. package/dist/bot/ctx.d.ts +219 -0
  10. package/dist/bot/ctx.d.ts.map +1 -0
  11. package/dist/bot/ctx.js +52 -0
  12. package/dist/bot/ctx.js.map +1 -0
  13. package/dist/bot/index.d.ts +4 -0
  14. package/dist/bot/index.d.ts.map +1 -1
  15. package/dist/bot/index.js +4 -0
  16. package/dist/bot/index.js.map +1 -1
  17. package/dist/bot/kit.d.ts +1 -1
  18. package/dist/bot/kit.d.ts.map +1 -1
  19. package/dist/bot/kit.js +4 -2
  20. package/dist/bot/kit.js.map +1 -1
  21. package/dist/bot/menu.d.ts +23 -1
  22. package/dist/bot/menu.d.ts.map +1 -1
  23. package/dist/bot/menu.js +30 -1
  24. package/dist/bot/menu.js.map +1 -1
  25. package/dist/bot/payments/callbacks.d.ts +28 -0
  26. package/dist/bot/payments/callbacks.d.ts.map +1 -0
  27. package/dist/bot/payments/callbacks.js +102 -0
  28. package/dist/bot/payments/callbacks.js.map +1 -0
  29. package/dist/bot/payments/commands.d.ts +26 -0
  30. package/dist/bot/payments/commands.d.ts.map +1 -0
  31. package/dist/bot/payments/commands.js +41 -0
  32. package/dist/bot/payments/commands.js.map +1 -0
  33. package/dist/bot/payments/config.d.ts +30 -0
  34. package/dist/bot/payments/config.d.ts.map +1 -0
  35. package/dist/bot/payments/config.js +166 -0
  36. package/dist/bot/payments/config.js.map +1 -0
  37. package/dist/bot/payments/derive.d.ts +75 -0
  38. package/dist/bot/payments/derive.d.ts.map +1 -0
  39. package/dist/bot/payments/derive.js +151 -0
  40. package/dist/bot/payments/derive.js.map +1 -0
  41. package/dist/bot/payments/handlers.d.ts +113 -0
  42. package/dist/bot/payments/handlers.d.ts.map +1 -0
  43. package/dist/bot/payments/handlers.js +284 -0
  44. package/dist/bot/payments/handlers.js.map +1 -0
  45. package/dist/bot/payments/index.d.ts +17 -0
  46. package/dist/bot/payments/index.d.ts.map +1 -0
  47. package/dist/bot/payments/index.js +14 -0
  48. package/dist/bot/payments/index.js.map +1 -0
  49. package/dist/bot/payments/invoice.d.ts +101 -0
  50. package/dist/bot/payments/invoice.d.ts.map +1 -0
  51. package/dist/bot/payments/invoice.js +213 -0
  52. package/dist/bot/payments/invoice.js.map +1 -0
  53. package/dist/bot/payments/menu-item.d.ts +41 -0
  54. package/dist/bot/payments/menu-item.d.ts.map +1 -0
  55. package/dist/bot/payments/menu-item.js +259 -0
  56. package/dist/bot/payments/menu-item.js.map +1 -0
  57. package/dist/bot/payments/payload.d.ts +42 -0
  58. package/dist/bot/payments/payload.d.ts.map +1 -0
  59. package/dist/bot/payments/payload.js +69 -0
  60. package/dist/bot/payments/payload.js.map +1 -0
  61. package/dist/bot/payments/payouts.d.ts +88 -0
  62. package/dist/bot/payments/payouts.d.ts.map +1 -0
  63. package/dist/bot/payments/payouts.js +133 -0
  64. package/dist/bot/payments/payouts.js.map +1 -0
  65. package/dist/bot/payments/plugin.d.ts +271 -0
  66. package/dist/bot/payments/plugin.d.ts.map +1 -0
  67. package/dist/bot/payments/plugin.js +141 -0
  68. package/dist/bot/payments/plugin.js.map +1 -0
  69. package/dist/bot/payments/refund.d.ts +124 -0
  70. package/dist/bot/payments/refund.d.ts.map +1 -0
  71. package/dist/bot/payments/refund.js +308 -0
  72. package/dist/bot/payments/refund.js.map +1 -0
  73. package/dist/bot/payments/schemas.d.ts +58 -0
  74. package/dist/bot/payments/schemas.d.ts.map +1 -0
  75. package/dist/bot/payments/schemas.js +55 -0
  76. package/dist/bot/payments/schemas.js.map +1 -0
  77. package/dist/bot/payments/state.d.ts +90 -0
  78. package/dist/bot/payments/state.d.ts.map +1 -0
  79. package/dist/bot/payments/state.js +149 -0
  80. package/dist/bot/payments/state.js.map +1 -0
  81. package/dist/bot/payments/stores.d.ts +44 -0
  82. package/dist/bot/payments/stores.d.ts.map +1 -0
  83. package/dist/bot/payments/stores.js +39 -0
  84. package/dist/bot/payments/stores.js.map +1 -0
  85. package/dist/bot/payments/types.d.ts +299 -0
  86. package/dist/bot/payments/types.d.ts.map +1 -0
  87. package/dist/bot/payments/types.js +39 -0
  88. package/dist/bot/payments/types.js.map +1 -0
  89. package/dist/bot/payments/waiver.d.ts +81 -0
  90. package/dist/bot/payments/waiver.d.ts.map +1 -0
  91. package/dist/bot/payments/waiver.js +145 -0
  92. package/dist/bot/payments/waiver.js.map +1 -0
  93. package/dist/bot/storage.d.ts +154 -0
  94. package/dist/bot/storage.d.ts.map +1 -0
  95. package/dist/bot/storage.js +222 -0
  96. package/dist/bot/storage.js.map +1 -0
  97. package/package.json +24 -3
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Refund flow — user-initiated request, admin-approved, executed via
3
+ * `refundStarPayment` on Telegram's side, reversed in our cache via
4
+ * `derive.ts`'s incremental + full-rebuild helpers.
5
+ *
6
+ * The flow mirrors `access-control.ts`'s admin-DM approval pattern:
7
+ *
8
+ * user opens /settings → 💎 VIP → 📜 History → tap a charge
9
+ * │
10
+ * "💸 Request refund" → confirm overlay → tap "✅"
11
+ * │
12
+ * pay:charge.paysupportState = 'opened'
13
+ * DM admin with charge details + [✅ Approve] [❌ Deny]
14
+ * │
15
+ * admin taps
16
+ * │
17
+ * Approve → refundStarPayment → pay:refund + revert state
18
+ * Deny → paysupportState = 'none'
19
+ * │
20
+ * user gets DM with the outcome
21
+ *
22
+ * Callback data carries the chargeId only — everything else is read
23
+ * from the persisted record. Telegram's `telegram_payment_charge_id` is
24
+ * a moderately long string (~40 chars in practice); with our short
25
+ * `payRf*` schema names this stays under the 64-byte callback_data cap.
26
+ */
27
+ import type { Storage } from "@gramio/storage";
28
+ import type { BotCallbackCtx } from "../ctx.js";
29
+ import type { PaymentsStores } from "./stores.js";
30
+ import type { BotPaymentsConfig, PaymentsSession } from "./types.js";
31
+ /** User tap on a charge entry — opens confirmation overlay then admin DM. */
32
+ export declare const refundRequestCb: import("gramio").CallbackData<Record<never, never>, Record<never, never>>;
33
+ /** Admin tap on the DM notification — approve. */
34
+ export declare const refundApproveCb: import("gramio").CallbackData<Record<never, never>, Record<never, never>>;
35
+ /** Admin tap on the DM notification — deny. */
36
+ export declare const refundDenyCb: import("gramio").CallbackData<Record<never, never>, Record<never, never>>;
37
+ /** Admin tap on the DM notification — close (no action). */
38
+ export declare const refundCloseCb: import("gramio").CallbackData<Record<never, never>, Record<never, never>>;
39
+ /** Strict bot.api surface this module exercises. */
40
+ type RefundBotApi = {
41
+ refundStarPayment: (params: {
42
+ user_id: number;
43
+ telegram_payment_charge_id: string;
44
+ }) => Promise<unknown>;
45
+ editUserStarSubscription: (params: {
46
+ user_id: number;
47
+ telegram_payment_charge_id: string;
48
+ is_canceled: boolean;
49
+ }) => Promise<unknown>;
50
+ sendMessage: (params: {
51
+ chat_id: number;
52
+ message_thread_id?: number;
53
+ text: string;
54
+ reply_markup?: unknown;
55
+ }) => Promise<unknown>;
56
+ };
57
+ type SessionLike = {
58
+ pay?: PaymentsSession;
59
+ language?: string;
60
+ };
61
+ /**
62
+ * Callback-query ctx shape this module's handlers receive. Composed
63
+ * from the canonical `BotCallbackCtx` with `{ cid: string }` queryData,
64
+ * intersected with the admin-context derives (`adminId`, `isAdmin`)
65
+ * the handlers depend on for the approve/deny/close paths.
66
+ */
67
+ type CommonCtx = BotCallbackCtx<SessionLike, {
68
+ cid: string;
69
+ }, RefundBotApi> & {
70
+ adminId: number;
71
+ isAdmin: boolean;
72
+ };
73
+ export type RefundHandlersOptions = {
74
+ stores: PaymentsStores;
75
+ /**
76
+ * Raw storage for cross-user session record reads/writes (the
77
+ * `@gramio/session` keyspace, outside our `pay:*` stores). Used by
78
+ * `applyRefundToUser` + `loadFullRecord` to reach a target user's
79
+ * full session record (preserving fields owned by other plugins).
80
+ */
81
+ storage: Storage;
82
+ cfg: BotPaymentsConfig<string>;
83
+ };
84
+ /**
85
+ * User taps "💸 Request refund" on a charge entry inside the menu's
86
+ * History view. The menu plugin's `confirm:` overlay already handled
87
+ * the "Are you sure?" step; this fires on the confirmed tap and:
88
+ *
89
+ * 1. Marks the charge `paysupportState = 'opened'`
90
+ * 2. DMs the admin with [Approve][Deny] buttons
91
+ * 3. Returns a toast acknowledging the request was sent
92
+ *
93
+ * The user's menu remains on the History view (menu's
94
+ * `confirm`-confirmed action returns to root, but the toast confirms
95
+ * the action so the user knows what happened).
96
+ */
97
+ export declare const buildRefundRequestHandler: (opts: RefundHandlersOptions) => (ctx: CommonCtx & {
98
+ queryData: {
99
+ cid: string;
100
+ };
101
+ }) => Promise<string | undefined>;
102
+ /**
103
+ * Admin taps "✅ Approve". Calls Telegram's `refundStarPayment`,
104
+ * persists the refund record + flips charge state, applies the refund
105
+ * to the target user's session (cross-user write), and notifies the
106
+ * user.
107
+ */
108
+ export declare const buildRefundApproveHandler: (opts: RefundHandlersOptions) => (ctx: CommonCtx & {
109
+ queryData: {
110
+ cid: string;
111
+ };
112
+ }) => Promise<void>;
113
+ /**
114
+ * Admin taps "❌ Deny". Reverts `paysupportState` to `'none'` so the
115
+ * user can try again or the admin can reconsider later. Notifies the
116
+ * user with the configured paysupport contact for further escalation.
117
+ */
118
+ export declare const buildRefundDenyHandler: (opts: RefundHandlersOptions) => (ctx: CommonCtx & {
119
+ queryData: {
120
+ cid: string;
121
+ };
122
+ }) => Promise<void>;
123
+ export {};
124
+ //# sourceMappingURL=refund.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refund.d.ts","sourceRoot":"","sources":["../../../src/bot/payments/refund.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAM/C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAGhD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EACX,iBAAiB,EAEjB,eAAe,EACf,MAAM,YAAY,CAAC;AASpB,6EAA6E;AAC7E,eAAO,MAAM,eAAe,2EAA+C,CAAC;AAC5E,kDAAkD;AAClD,eAAO,MAAM,eAAe,2EAA+C,CAAC;AAC5E,+CAA+C;AAC/C,eAAO,MAAM,YAAY,2EAA4C,CAAC;AACtE,4DAA4D;AAC5D,eAAO,MAAM,aAAa,2EAA8B,CAAC;AAIzD,oDAAoD;AACpD,KAAK,YAAY,GAAG;IACnB,iBAAiB,EAAE,CAAC,MAAM,EAAE;QAC3B,OAAO,EAAE,MAAM,CAAC;QAChB,0BAA0B,EAAE,MAAM,CAAC;KACnC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACvB,wBAAwB,EAAE,CAAC,MAAM,EAAE;QAClC,OAAO,EAAE,MAAM,CAAC;QAChB,0BAA0B,EAAE,MAAM,CAAC;QACnC,WAAW,EAAE,OAAO,CAAC;KACrB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACvB,WAAW,EAAE,CAAC,MAAM,EAAE;QACrB,OAAO,EAAE,MAAM,CAAC;QAChB,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAC3B,IAAI,EAAE,MAAM,CAAC;QACb,YAAY,CAAC,EAAE,OAAO,CAAC;KACvB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACvB,CAAC;AAEF,KAAK,WAAW,GAAG;IAAE,GAAG,CAAC,EAAE,eAAe,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEhE;;;;;GAKG;AACH,KAAK,SAAS,GAAG,cAAc,CAAC,WAAW,EAAE;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,EAAE,YAAY,CAAC,GAAG;IAC7E,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;CACjB,CAAC;AAyGF,MAAM,MAAM,qBAAqB,GAAG;IACnC,MAAM,EAAE,cAAc,CAAC;IACvB;;;;;OAKG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,iBAAiB,CAAC,MAAM,CAAC,CAAC;CAC/B,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,yBAAyB,GACpC,MAAM,qBAAqB,MAE3B,KAAK,SAAS,GAAG;IAAE,SAAS,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,KAC7C,OAAO,CAAC,MAAM,GAAG,SAAS,CA8D5B,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,GACpC,MAAM,qBAAqB,MACrB,KAAK,SAAS,GAAG;IAAE,SAAS,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,KAAG,OAAO,CAAC,IAAI,CAgHpE,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,GACjC,MAAM,qBAAqB,MACrB,KAAK,SAAS,GAAG;IAAE,SAAS,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,KAAG,OAAO,CAAC,IAAI,CAwDpE,CAAC"}
@@ -0,0 +1,308 @@
1
+ /**
2
+ * Refund flow — user-initiated request, admin-approved, executed via
3
+ * `refundStarPayment` on Telegram's side, reversed in our cache via
4
+ * `derive.ts`'s incremental + full-rebuild helpers.
5
+ *
6
+ * The flow mirrors `access-control.ts`'s admin-DM approval pattern:
7
+ *
8
+ * user opens /settings → 💎 VIP → 📜 History → tap a charge
9
+ * │
10
+ * "💸 Request refund" → confirm overlay → tap "✅"
11
+ * │
12
+ * pay:charge.paysupportState = 'opened'
13
+ * DM admin with charge details + [✅ Approve] [❌ Deny]
14
+ * │
15
+ * admin taps
16
+ * │
17
+ * Approve → refundStarPayment → pay:refund + revert state
18
+ * Deny → paysupportState = 'none'
19
+ * │
20
+ * user gets DM with the outcome
21
+ *
22
+ * Callback data carries the chargeId only — everything else is read
23
+ * from the persisted record. Telegram's `telegram_payment_charge_id` is
24
+ * a moderately long string (~40 chars in practice); with our short
25
+ * `payRf*` schema names this stays under the 64-byte callback_data cap.
26
+ */
27
+ import { InlineKeyboard } from "gramio";
28
+ import { say } from "../../say/index.js";
29
+ import { createLogger } from "../../universal/log.js";
30
+ import { callbackNs } from "../callbacks.js";
31
+ import { botStorageKey } from "../kit.js";
32
+ import { rebuildVipAndPerks, revertCreditsForCharge } from "./state.js";
33
+ const FALLBACK_LANG = "en";
34
+ const log = createLogger("bot/payments");
35
+ const cb = callbackNs("pay");
36
+ // ─── callback data schemas ─────────────────────────────────────────
37
+ /** User tap on a charge entry — opens confirmation overlay then admin DM. */
38
+ export const refundRequestCb = cb.data("refund:request", { cid: "string" });
39
+ /** Admin tap on the DM notification — approve. */
40
+ export const refundApproveCb = cb.data("refund:approve", { cid: "string" });
41
+ /** Admin tap on the DM notification — deny. */
42
+ export const refundDenyCb = cb.data("refund:deny", { cid: "string" });
43
+ /** Admin tap on the DM notification — close (no action). */
44
+ export const refundCloseCb = cb.data("refund:close", {});
45
+ const ctxLang = (ctx) => ctx.session?.language ?? FALLBACK_LANG;
46
+ // ─── cross-user session mutation ───────────────────────────────────
47
+ //
48
+ // The user's session record lives in @gramio/session's keyspace
49
+ // (`bot-<id>:<userId>` via `botStorageKey`), not in our `pay:*` stores.
50
+ // We still hit `storage` directly here because the session record holds
51
+ // many fields owned by other plugins (access, language, llm) and we
52
+ // must preserve them via read-modify-write.
53
+ const loadFullRecord = async (storage, ctx, userId) => (await storage.get(botStorageKey(ctx, userId))) ?? {};
54
+ /**
55
+ * Apply the refund's side effect to the target user's session record
56
+ * (which is NOT `ctx.session` when the admin is the actor).
57
+ *
58
+ * - credits: targeted decrement (clamped at zero — see derive.ts)
59
+ * - vip + perks: full rebuild from the post-refund charge log
60
+ *
61
+ * Read-modify-write preserves other plugins' fields in the same record.
62
+ */
63
+ const applyRefundToUser = async (stores, storage, ctx, userId, refundedCharge) => {
64
+ const full = await loadFullRecord(storage, ctx, userId);
65
+ const session = { pay: full.pay };
66
+ revertCreditsForCharge(session, refundedCharge);
67
+ // charges already include the refunded one with state='refunded'
68
+ const chargeIds = await stores.userCharges(userId).list(ctx);
69
+ const charges = (await Promise.all(chargeIds.map((id) => stores.charges.get(ctx, id)))).filter((c) => c !== undefined);
70
+ rebuildVipAndPerks(session, charges);
71
+ full.pay = session.pay;
72
+ await storage.set(botStorageKey(ctx, userId), full);
73
+ };
74
+ // ─── notification rendering ────────────────────────────────────────
75
+ const fmtAge = (ms) => {
76
+ const s = Math.floor(ms / 1000);
77
+ if (s < 60)
78
+ return `${s}s`;
79
+ const m = Math.floor(s / 60);
80
+ if (m < 60)
81
+ return `${m}min`;
82
+ const h = Math.floor(m / 60);
83
+ if (h < 24)
84
+ return `${h}h`;
85
+ return `${Math.floor(h / 24)}d`;
86
+ };
87
+ const adminNotificationText = (charge, lang) => [
88
+ say({ en: "💸 Refund requested", es: "💸 Reembolso solicitado" }, lang),
89
+ "",
90
+ `👤 ${say({ en: "user", es: "usuario" }, lang)}: ${charge.userId}`,
91
+ `📦 ${charge.productKey}`,
92
+ `⭐ ${charge.xtr}`,
93
+ `🆔 ${charge.chargeId}`,
94
+ `⏰ ${say({ en: "purchased", es: "comprado" }, lang)}: ${fmtAge(Date.now() - charge.receivedAt)} ${say({ en: "ago", es: "atrás" }, lang)}`,
95
+ ].join("\n");
96
+ const adminKeyboard = (chargeId, lang) => new InlineKeyboard()
97
+ .text(say({ en: "✅ Approve", es: "✅ Aprobar" }, lang), refundApproveCb.pack({ cid: chargeId }), { style: "success" })
98
+ .text(say({ en: "❌ Deny", es: "❌ Denegar" }, lang), refundDenyCb.pack({ cid: chargeId }), { style: "danger" })
99
+ .row()
100
+ .text(say({ en: "✖️ Close", es: "✖️ Cerrar" }, lang), refundCloseCb.pack({}));
101
+ const adminLangOfUser = async (storage, ctx, userId) => {
102
+ const full = await loadFullRecord(storage, ctx, userId);
103
+ return full.language ?? FALLBACK_LANG;
104
+ };
105
+ /**
106
+ * User taps "💸 Request refund" on a charge entry inside the menu's
107
+ * History view. The menu plugin's `confirm:` overlay already handled
108
+ * the "Are you sure?" step; this fires on the confirmed tap and:
109
+ *
110
+ * 1. Marks the charge `paysupportState = 'opened'`
111
+ * 2. DMs the admin with [Approve][Deny] buttons
112
+ * 3. Returns a toast acknowledging the request was sent
113
+ *
114
+ * The user's menu remains on the History view (menu's
115
+ * `confirm`-confirmed action returns to root, but the toast confirms
116
+ * the action so the user knows what happened).
117
+ */
118
+ export const buildRefundRequestHandler = (opts) => async (ctx) => {
119
+ const lang = ctxLang(ctx);
120
+ const userId = ctx.from?.id;
121
+ if (userId === undefined) {
122
+ return say({ en: "Could not identify you.", es: "No se pudo identificarte." }, lang);
123
+ }
124
+ const charge = await opts.stores.charges.get(ctx, ctx.queryData.cid);
125
+ if (!charge) {
126
+ return say({ en: "Charge not found.", es: "Cargo no encontrado." }, lang);
127
+ }
128
+ if (charge.userId !== userId) {
129
+ // Anti-tamper — never let user A request refund of user B's charge.
130
+ return say({ en: "Not your charge.", es: "No es tu cargo." }, lang);
131
+ }
132
+ if (charge.paysupportState !== "none") {
133
+ return say({
134
+ en: "Refund already in progress or completed.",
135
+ es: "Reembolso ya en curso o completado.",
136
+ }, lang);
137
+ }
138
+ charge.paysupportState = "opened";
139
+ await opts.stores.charges.set(ctx, charge.chargeId, charge);
140
+ log.event(`refund requested by user: chargeId=${charge.chargeId} product=${charge.productKey} xtr=${charge.xtr}`);
141
+ const adminLang = await adminLangOfUser(opts.storage, ctx, ctx.adminId);
142
+ try {
143
+ await ctx.bot.api.sendMessage({
144
+ chat_id: ctx.adminId,
145
+ text: adminNotificationText(charge, adminLang),
146
+ reply_markup: adminKeyboard(charge.chargeId, adminLang),
147
+ });
148
+ }
149
+ catch (e) {
150
+ log.error(`failed to DM admin: ${e}`);
151
+ // Roll back the open state so the user can try again later.
152
+ charge.paysupportState = "none";
153
+ await opts.stores.charges.set(ctx, charge.chargeId, charge);
154
+ return say({
155
+ en: `Couldn't reach the admin. Contact ${opts.cfg.paysupport}.`,
156
+ es: `No se pudo contactar al admin. Contacta con ${opts.cfg.paysupport}.`,
157
+ }, lang);
158
+ }
159
+ return say({
160
+ en: "💸 Refund request sent.",
161
+ es: "💸 Solicitud de reembolso enviada.",
162
+ }, lang);
163
+ };
164
+ /**
165
+ * Admin taps "✅ Approve". Calls Telegram's `refundStarPayment`,
166
+ * persists the refund record + flips charge state, applies the refund
167
+ * to the target user's session (cross-user write), and notifies the
168
+ * user.
169
+ */
170
+ export const buildRefundApproveHandler = (opts) => async (ctx) => {
171
+ const aLang = ctxLang(ctx);
172
+ if (!ctx.isAdmin) {
173
+ await ctx.answer({
174
+ text: say({ en: "Admin only.", es: "Solo admin." }, aLang),
175
+ show_alert: true,
176
+ });
177
+ return;
178
+ }
179
+ const charge = await opts.stores.charges.get(ctx, ctx.queryData.cid);
180
+ if (!charge) {
181
+ await ctx.answer({
182
+ text: say({ en: "Charge not found.", es: "Cargo no encontrado." }, aLang),
183
+ });
184
+ return;
185
+ }
186
+ if (charge.paysupportState === "refunded") {
187
+ await ctx.answer({
188
+ text: say({ en: "Already refunded.", es: "Ya reembolsado." }, aLang),
189
+ });
190
+ return;
191
+ }
192
+ log.event(`admin approving refund: chargeId=${charge.chargeId} user=${charge.userId} xtr=${charge.xtr}`);
193
+ try {
194
+ await ctx.bot.api.refundStarPayment({
195
+ user_id: charge.userId,
196
+ telegram_payment_charge_id: charge.chargeId,
197
+ });
198
+ }
199
+ catch (e) {
200
+ log.error(`refundStarPayment failed: ${e}`);
201
+ await ctx.answer({
202
+ text: say({
203
+ en: `❌ Refund failed: ${e instanceof Error ? e.message : String(e)}`,
204
+ es: `❌ Reembolso falló: ${e instanceof Error ? e.message : String(e)}`,
205
+ }, aLang),
206
+ show_alert: true,
207
+ });
208
+ return;
209
+ }
210
+ // If this charge funded a still-active vip subscription, also
211
+ // cancel its auto-renewal so the user isn't charged again at the
212
+ // next period boundary. Best-effort; refund itself already
213
+ // succeeded.
214
+ if (charge.vipRung !== undefined) {
215
+ try {
216
+ await ctx.bot.api.editUserStarSubscription({
217
+ user_id: charge.userId,
218
+ telegram_payment_charge_id: charge.chargeId,
219
+ is_canceled: true,
220
+ });
221
+ }
222
+ catch (e) {
223
+ log.warn(`post-refund cancel renewal failed (probably already canceled): ${e}`);
224
+ }
225
+ }
226
+ charge.paysupportState = "refunded";
227
+ charge.refundedAt = Date.now();
228
+ await opts.stores.charges.set(ctx, charge.chargeId, charge);
229
+ await applyRefundToUser(opts.stores, opts.storage, ctx, charge.userId, charge);
230
+ // User notification
231
+ const userFull = await loadFullRecord(opts.storage, ctx, charge.userId);
232
+ const uLang = userFull.language ?? FALLBACK_LANG;
233
+ try {
234
+ await ctx.bot.api.sendMessage({
235
+ chat_id: charge.userId,
236
+ text: say({
237
+ en: `✅ Your refund of ${charge.xtr} ⭐ has been approved.`,
238
+ es: `✅ Tu reembolso de ${charge.xtr} ⭐ ha sido aprobado.`,
239
+ }, uLang),
240
+ });
241
+ }
242
+ catch (e) {
243
+ log.error(`failed to notify user of approval: ${e}`);
244
+ }
245
+ await ctx.answer({
246
+ text: say({ en: "✅ Refund approved.", es: "✅ Reembolso aprobado." }, aLang),
247
+ });
248
+ if (ctx.editText) {
249
+ try {
250
+ await ctx.editText(`${say({ en: "✅ Refunded", es: "✅ Reembolsado" }, aLang)} · ${charge.chargeId}`);
251
+ }
252
+ catch {
253
+ /* message too old to edit */
254
+ }
255
+ }
256
+ };
257
+ /**
258
+ * Admin taps "❌ Deny". Reverts `paysupportState` to `'none'` so the
259
+ * user can try again or the admin can reconsider later. Notifies the
260
+ * user with the configured paysupport contact for further escalation.
261
+ */
262
+ export const buildRefundDenyHandler = (opts) => async (ctx) => {
263
+ const aLang = ctxLang(ctx);
264
+ if (!ctx.isAdmin) {
265
+ await ctx.answer({
266
+ text: say({ en: "Admin only.", es: "Solo admin." }, aLang),
267
+ show_alert: true,
268
+ });
269
+ return;
270
+ }
271
+ const charge = await opts.stores.charges.get(ctx, ctx.queryData.cid);
272
+ if (!charge) {
273
+ await ctx.answer({
274
+ text: say({ en: "Charge not found.", es: "Cargo no encontrado." }, aLang),
275
+ });
276
+ return;
277
+ }
278
+ charge.paysupportState = "none";
279
+ await opts.stores.charges.set(ctx, charge.chargeId, charge);
280
+ const userFull = await loadFullRecord(opts.storage, ctx, charge.userId);
281
+ const uLang = userFull.language ?? FALLBACK_LANG;
282
+ try {
283
+ await ctx.bot.api.sendMessage({
284
+ chat_id: charge.userId,
285
+ text: say({
286
+ en: `❌ Your refund request was denied.\n\n` +
287
+ `For further questions, contact ${opts.cfg.paysupport}.`,
288
+ es: `❌ Tu solicitud de reembolso fue denegada.\n\n` +
289
+ `Para más consultas, contacta con ${opts.cfg.paysupport}.`,
290
+ }, uLang),
291
+ });
292
+ }
293
+ catch (e) {
294
+ log.error("failed to notify user of denial", e);
295
+ }
296
+ await ctx.answer({
297
+ text: say({ en: "❌ Denied", es: "❌ Denegado" }, aLang),
298
+ });
299
+ if (ctx.editText) {
300
+ try {
301
+ await ctx.editText(`${say({ en: "❌ Denied", es: "❌ Denegado" }, aLang)} · ${charge.chargeId}`);
302
+ }
303
+ catch {
304
+ /* ignore */
305
+ }
306
+ }
307
+ };
308
+ //# sourceMappingURL=refund.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refund.js","sourceRoot":"","sources":["../../../src/bot/payments/refund.ts"],"names":[],"mappings":"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"}
@@ -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"}