@adriangalilea/utils 0.17.1 → 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.
- package/README.md +9 -9
- package/dist/bot/access-control.d.ts.map +1 -1
- package/dist/bot/access-control.js +62 -45
- package/dist/bot/access-control.js.map +1 -1
- 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 +93 -41
- package/dist/bot/kit.d.ts.map +1 -1
- package/dist/bot/kit.js +153 -30
- package/dist/bot/kit.js.map +1 -1
- package/dist/bot/menu.d.ts +31 -11
- package/dist/bot/menu.d.ts.map +1 -1
- package/dist/bot/menu.js +34 -9
- 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 +24 -3
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sendInvoice` wrappers — one per product kind, plus the high-level
|
|
3
|
+
* `presentInvoice` entry that the `ctx.payments.invoice(...)` surface
|
|
4
|
+
* routes to.
|
|
5
|
+
*
|
|
6
|
+
* Stars-only by design (see CLAUDE.md §1, §2). Every invoice goes out
|
|
7
|
+
* with `currency: 'XTR'` and `provider_token: ""` — Telegram rejects
|
|
8
|
+
* provider_token for XTR. The `prices` array must contain exactly one
|
|
9
|
+
* `LabeledPrice` for XTR per the Bot API docs.
|
|
10
|
+
*
|
|
11
|
+
* The function calls `bot.api.sendInvoice` directly (not `ctx.sendInvoice`)
|
|
12
|
+
* so the same path works from both message and callback_query contexts
|
|
13
|
+
* — `message_thread_id` is plumbed explicitly so threaded replies land
|
|
14
|
+
* in the right thread regardless of which mixin would otherwise inject it.
|
|
15
|
+
*
|
|
16
|
+
* https://core.telegram.org/bots/api#sendinvoice
|
|
17
|
+
* https://core.telegram.org/bots/payments-stars
|
|
18
|
+
*/
|
|
19
|
+
import { panic, SourcedError } from "../../offensive.js";
|
|
20
|
+
import { say } from "../../say/index.js";
|
|
21
|
+
import { createLogger } from "../../universal/log.js";
|
|
22
|
+
import { encodePayload } from "./payload.js";
|
|
23
|
+
import { renderConsentBody, waiverKeyboard } from "./waiver.js";
|
|
24
|
+
const log = createLogger("bot/payments");
|
|
25
|
+
// Telegram caps from the Bot API (sendInvoice §):
|
|
26
|
+
const TITLE_MAX = 32;
|
|
27
|
+
const DESCRIPTION_MAX = 255;
|
|
28
|
+
const FALLBACK_LANG = "en";
|
|
29
|
+
// ─── small helpers ─────────────────────────────────────────────────
|
|
30
|
+
const ctxLang = (ctx) => ctx.session?.language ?? FALLBACK_LANG;
|
|
31
|
+
const ctxChatId = (ctx) => {
|
|
32
|
+
const id = ctx.chat?.id ?? ctx.message?.chat?.id;
|
|
33
|
+
if (id === undefined) {
|
|
34
|
+
panic("bot/payments/invoice: ctx is missing chat.id (neither top-level nor message.chat)");
|
|
35
|
+
}
|
|
36
|
+
return id;
|
|
37
|
+
};
|
|
38
|
+
const ctxThreadId = (ctx) => ctx.threadId ?? ctx.message?.threadId;
|
|
39
|
+
/**
|
|
40
|
+
* Resolve a Polyglot to a single string with a fallback chain. Trims
|
|
41
|
+
* + caps at `maxLen`.
|
|
42
|
+
*/
|
|
43
|
+
const resolveLabel = (value, lang, maxLen) => {
|
|
44
|
+
if (!value)
|
|
45
|
+
return undefined;
|
|
46
|
+
const v = value[lang] ?? value[FALLBACK_LANG] ?? Object.values(value)[0];
|
|
47
|
+
if (typeof v !== "string")
|
|
48
|
+
return undefined;
|
|
49
|
+
const trimmed = v.trim();
|
|
50
|
+
return trimmed.length > maxLen ? trimmed.slice(0, maxLen) : trimmed;
|
|
51
|
+
};
|
|
52
|
+
// ─── title / description per product kind ──────────────────────────
|
|
53
|
+
const titleFor = (product, cfg, lang) => {
|
|
54
|
+
if (product.id.startsWith("vip.")) {
|
|
55
|
+
return (resolveLabel(product.name, lang, TITLE_MAX) ??
|
|
56
|
+
say({ en: "VIP", es: "VIP" }, lang));
|
|
57
|
+
}
|
|
58
|
+
if (product.id.startsWith("credits.")) {
|
|
59
|
+
const p = product;
|
|
60
|
+
const explicit = resolveLabel(p.name, lang, TITLE_MAX);
|
|
61
|
+
if (explicit)
|
|
62
|
+
return explicit;
|
|
63
|
+
const unit = resolveLabel(cfg.credits?.unit, lang, 32) ??
|
|
64
|
+
"credits";
|
|
65
|
+
// "100 credits" / "100 mensajes" — fits in 32 chars for reasonable values.
|
|
66
|
+
const base = `${p.creditsGranted} ${unit}`;
|
|
67
|
+
return base.length > TITLE_MAX ? base.slice(0, TITLE_MAX) : base;
|
|
68
|
+
}
|
|
69
|
+
// perk
|
|
70
|
+
return (resolveLabel(product.name, lang, TITLE_MAX) ??
|
|
71
|
+
product.key);
|
|
72
|
+
};
|
|
73
|
+
const descriptionFor = (product, cfg, lang) => {
|
|
74
|
+
if (product.id.startsWith("vip.")) {
|
|
75
|
+
const v = product;
|
|
76
|
+
const name = resolveLabel(v.name, lang, 24) ?? say({ en: "VIP", es: "VIP" }, lang);
|
|
77
|
+
const baseEn = `30 days of ${name} access`;
|
|
78
|
+
const baseEs = `30 días de acceso ${name}`;
|
|
79
|
+
const base = say({ en: baseEn, es: baseEs }, lang);
|
|
80
|
+
if (v.creditsGranted > 0) {
|
|
81
|
+
const unit = resolveLabel(cfg.credits?.unit, lang, 24) ??
|
|
82
|
+
"credits";
|
|
83
|
+
const tailEn = `. Includes ${v.creditsGranted} ${unit} per month.`;
|
|
84
|
+
const tailEs = `. Incluye ${v.creditsGranted} ${unit} al mes.`;
|
|
85
|
+
const tail = say({ en: tailEn, es: tailEs }, lang);
|
|
86
|
+
const full = base + tail;
|
|
87
|
+
return full.length > DESCRIPTION_MAX
|
|
88
|
+
? full.slice(0, DESCRIPTION_MAX)
|
|
89
|
+
: full;
|
|
90
|
+
}
|
|
91
|
+
return base.length > DESCRIPTION_MAX
|
|
92
|
+
? base.slice(0, DESCRIPTION_MAX)
|
|
93
|
+
: base;
|
|
94
|
+
}
|
|
95
|
+
if (product.id.startsWith("credits.")) {
|
|
96
|
+
const p = product;
|
|
97
|
+
const unit = resolveLabel(cfg.credits?.unit, lang, 24) ??
|
|
98
|
+
"credits";
|
|
99
|
+
const base = say({
|
|
100
|
+
en: `Top up your balance by ${p.creditsGranted} ${unit}.`,
|
|
101
|
+
es: `Recarga tu saldo con ${p.creditsGranted} ${unit}.`,
|
|
102
|
+
}, lang);
|
|
103
|
+
return base.length > DESCRIPTION_MAX
|
|
104
|
+
? base.slice(0, DESCRIPTION_MAX)
|
|
105
|
+
: base;
|
|
106
|
+
}
|
|
107
|
+
// perk
|
|
108
|
+
const p = product;
|
|
109
|
+
const name = resolveLabel(p.name, lang, 24) ?? p.key;
|
|
110
|
+
const base = say({
|
|
111
|
+
en: `Unlock ${name} — one-time.`,
|
|
112
|
+
es: `Desbloquear ${name} — único pago.`,
|
|
113
|
+
}, lang);
|
|
114
|
+
return base.length > DESCRIPTION_MAX ? base.slice(0, DESCRIPTION_MAX) : base;
|
|
115
|
+
};
|
|
116
|
+
// ─── core send ─────────────────────────────────────────────────────
|
|
117
|
+
/**
|
|
118
|
+
* Issue a Telegram Stars invoice for `product`. Caller is expected to
|
|
119
|
+
* have already cleared waiver consent — `presentInvoice` (the public
|
|
120
|
+
* entry) handles that gate. This function does the wire call only.
|
|
121
|
+
*
|
|
122
|
+
* Wraps Telegram-side failures in a typed `SourcedError({ source:
|
|
123
|
+
* 'telegram', operation: 'sendInvoice' })` so the catch boundary in
|
|
124
|
+
* `plugin.ts` can render a localized fallback message.
|
|
125
|
+
*/
|
|
126
|
+
export const sendInvoiceForProduct = async (ctx, cfg, product, userId) => {
|
|
127
|
+
const lang = ctxLang(ctx);
|
|
128
|
+
const chatId = ctxChatId(ctx);
|
|
129
|
+
const threadId = ctxThreadId(ctx);
|
|
130
|
+
const title = titleFor(product, cfg, lang);
|
|
131
|
+
const description = descriptionFor(product, cfg, lang);
|
|
132
|
+
const payload = encodePayload(product.id, userId);
|
|
133
|
+
const priceLabel = resolveLabel(product.name, lang, 32) ??
|
|
134
|
+
title;
|
|
135
|
+
const params = {
|
|
136
|
+
chat_id: chatId,
|
|
137
|
+
...(threadId !== undefined && { message_thread_id: threadId }),
|
|
138
|
+
title,
|
|
139
|
+
description,
|
|
140
|
+
payload,
|
|
141
|
+
provider_token: "", // XTR — must be empty
|
|
142
|
+
currency: "XTR",
|
|
143
|
+
prices: [{ label: priceLabel, amount: product.xtr }],
|
|
144
|
+
};
|
|
145
|
+
if (product.id.startsWith("vip.")) {
|
|
146
|
+
params.subscription_period = product.periodSeconds;
|
|
147
|
+
}
|
|
148
|
+
log.event(`sendInvoice → telegram: product=${product.id} user=${userId} xtr=${product.xtr} title="${title}" subscription_period=${params.subscription_period ?? "none"}`);
|
|
149
|
+
try {
|
|
150
|
+
await ctx.bot.api.sendInvoice(params);
|
|
151
|
+
log.success(`sendInvoice accepted by telegram: product=${product.id} user=${userId}`);
|
|
152
|
+
}
|
|
153
|
+
catch (e) {
|
|
154
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
155
|
+
log.error(`sendInvoice rejected by telegram: ${msg}`);
|
|
156
|
+
// Hint on the specific subscription-export setup gotcha — there's
|
|
157
|
+
// no documented fix path in gramio/telegram docs, so surface ours.
|
|
158
|
+
if (msg.includes("SUBSCRIPTION_EXPORT_MISSING")) {
|
|
159
|
+
log.warn("hint: open BotFather → /mybots → your bot → Bot Settings → Star Subscriptions and link a Fragment payout method before sending subscription invoices");
|
|
160
|
+
}
|
|
161
|
+
throw new SourcedError({
|
|
162
|
+
source: "telegram",
|
|
163
|
+
operation: "sendInvoice",
|
|
164
|
+
message: msg,
|
|
165
|
+
cause: e,
|
|
166
|
+
context: { productKey: product.id, userId, chatId, threadId },
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
/**
|
|
171
|
+
* High-level entry for `ctx.payments.invoice(productKey)`.
|
|
172
|
+
*
|
|
173
|
+
* Validates the productKey against the catalog, gates on waiver
|
|
174
|
+
* freshness, and either:
|
|
175
|
+
*
|
|
176
|
+
* - sends the consent prompt (one-tap "✅ Consiento" button carrying
|
|
177
|
+
* the productKey, so the user resumes the purchase on tap), OR
|
|
178
|
+
* - calls `sendInvoiceForProduct` directly when consent is already
|
|
179
|
+
* fresh.
|
|
180
|
+
*
|
|
181
|
+
* Returns a discriminated tag so callers can branch on what the user
|
|
182
|
+
* saw — useful for `require()`-style helpers.
|
|
183
|
+
*/
|
|
184
|
+
export const presentInvoice = async (ctx, cfg, catalog, productKey) => {
|
|
185
|
+
const product = catalog.byKey.get(productKey);
|
|
186
|
+
if (!product) {
|
|
187
|
+
log.warn(`presentInvoice unknown productKey "${productKey}"`);
|
|
188
|
+
const lang = ctxLang(ctx);
|
|
189
|
+
await ctx.send(say({
|
|
190
|
+
en: `❌ Unknown product "${productKey}".`,
|
|
191
|
+
es: `❌ Producto desconocido "${productKey}".`,
|
|
192
|
+
}, lang));
|
|
193
|
+
return "unknown_product";
|
|
194
|
+
}
|
|
195
|
+
const userId = ctx.from?.id;
|
|
196
|
+
if (!userId) {
|
|
197
|
+
panic("bot/payments/invoice: presentInvoice called with no ctx.from.id");
|
|
198
|
+
}
|
|
199
|
+
const stored = ctx.session?.pay?.waiver;
|
|
200
|
+
const fresh = stored && stored.version === cfg.waiver.version;
|
|
201
|
+
if (!fresh) {
|
|
202
|
+
log.info(`presentInvoice: waiver ${stored ? `stale (${stored.version} ≠ ${cfg.waiver.version})` : "missing"} — sending consent prompt for ${product.id} user=${userId}`);
|
|
203
|
+
const lang = ctxLang(ctx);
|
|
204
|
+
await ctx.send(renderConsentBody(cfg, lang), {
|
|
205
|
+
reply_markup: waiverKeyboard(cfg, product.id, lang),
|
|
206
|
+
});
|
|
207
|
+
return "waiver_prompt_sent";
|
|
208
|
+
}
|
|
209
|
+
log.info(`presentInvoice: waiver fresh — going straight to sendInvoice for ${product.id} user=${userId}`);
|
|
210
|
+
await sendInvoiceForProduct(ctx, cfg, product, userId);
|
|
211
|
+
return "invoice_sent";
|
|
212
|
+
};
|
|
213
|
+
//# sourceMappingURL=invoice.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invoice.js","sourceRoot":"","sources":["../../../src/bot/payments/invoice.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `payments.menuItem` — single drop-in entry for `botMenu({ items: […] })`.
|
|
3
|
+
*
|
|
4
|
+
* Label, style, and submenu adapt to the user's current state via live
|
|
5
|
+
* resolvers reading `ctx.session.pay.*` (see `bot/CLAUDE.md` § "Snapshot
|
|
6
|
+
* vs live derives"). The plugin owns the whole submenu — bot author
|
|
7
|
+
* just adds `payments.menuItem` to their items array.
|
|
8
|
+
*
|
|
9
|
+
* Submenu shape (v1):
|
|
10
|
+
*
|
|
11
|
+
* /settings → 💎 VIP
|
|
12
|
+
* ├── ── tier cards ──
|
|
13
|
+
* │ [💎 VIP — 500 ⭐] buy / current state via style
|
|
14
|
+
* │ [🌟 VIP Max — 2000 ⭐]
|
|
15
|
+
* ├── ── credits packs (if axis used) ──
|
|
16
|
+
* │ [💬 +100 — 100 ⭐]
|
|
17
|
+
* │ [💬 +500 — 400 ⭐]
|
|
18
|
+
* ├── ── perks (if axis used) ──
|
|
19
|
+
* │ [🎁 Voice mode — 1500 ⭐] hidden when owned
|
|
20
|
+
* ├── ── manage (when vip active) ──
|
|
21
|
+
* │ [⛔ Cancel renewal] or [↩️ Resume renewal] when canceled
|
|
22
|
+
* ├── 💬 Help / refunds → ctx.send(paysupport blurb)
|
|
23
|
+
* ├── ⬅️ Back ✖️ Close (botMenu auto-renders these in submenus)
|
|
24
|
+
*
|
|
25
|
+
* What's deliberately NOT here yet:
|
|
26
|
+
*
|
|
27
|
+
* - Per-charge **History** view + in-menu refund request. The compliance
|
|
28
|
+
* surface (`/paysupport` + admin-mediated refund) is fulfilled by
|
|
29
|
+
* the admin commands and the message-side flow in `refund.ts`. The
|
|
30
|
+
* history UI is a v2 follow-up (see CLAUDE.md §"Open follow-ups").
|
|
31
|
+
*
|
|
32
|
+
* - Deep-link `require()` → exact rung. v1 opens VIP root; deep-linking
|
|
33
|
+
* needs menu-state encoding in callback data.
|
|
34
|
+
*/
|
|
35
|
+
import type { MenuItem } from "../menu.js";
|
|
36
|
+
import type { BotPaymentsConfig, ProductCatalog } from "./types.js";
|
|
37
|
+
export declare const buildPaymentsMenuItem: (args: {
|
|
38
|
+
cfg: BotPaymentsConfig<string>;
|
|
39
|
+
catalog: ProductCatalog;
|
|
40
|
+
}) => MenuItem;
|
|
41
|
+
//# sourceMappingURL=menu-item.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"menu-item.d.ts","sourceRoot":"","sources":["../../../src/bot/payments/menu-item.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAIH,OAAO,KAAK,EAAW,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEpD,OAAO,KAAK,EACX,iBAAiB,EAEjB,cAAc,EACd,MAAM,YAAY,CAAC;AAiCpB,eAAO,MAAM,qBAAqB,GAAI,MAAM;IAC3C,GAAG,EAAE,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC/B,OAAO,EAAE,cAAc,CAAC;CACxB,KAAG,QAqRH,CAAC"}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `payments.menuItem` — single drop-in entry for `botMenu({ items: […] })`.
|
|
3
|
+
*
|
|
4
|
+
* Label, style, and submenu adapt to the user's current state via live
|
|
5
|
+
* resolvers reading `ctx.session.pay.*` (see `bot/CLAUDE.md` § "Snapshot
|
|
6
|
+
* vs live derives"). The plugin owns the whole submenu — bot author
|
|
7
|
+
* just adds `payments.menuItem` to their items array.
|
|
8
|
+
*
|
|
9
|
+
* Submenu shape (v1):
|
|
10
|
+
*
|
|
11
|
+
* /settings → 💎 VIP
|
|
12
|
+
* ├── ── tier cards ──
|
|
13
|
+
* │ [💎 VIP — 500 ⭐] buy / current state via style
|
|
14
|
+
* │ [🌟 VIP Max — 2000 ⭐]
|
|
15
|
+
* ├── ── credits packs (if axis used) ──
|
|
16
|
+
* │ [💬 +100 — 100 ⭐]
|
|
17
|
+
* │ [💬 +500 — 400 ⭐]
|
|
18
|
+
* ├── ── perks (if axis used) ──
|
|
19
|
+
* │ [🎁 Voice mode — 1500 ⭐] hidden when owned
|
|
20
|
+
* ├── ── manage (when vip active) ──
|
|
21
|
+
* │ [⛔ Cancel renewal] or [↩️ Resume renewal] when canceled
|
|
22
|
+
* ├── 💬 Help / refunds → ctx.send(paysupport blurb)
|
|
23
|
+
* ├── ⬅️ Back ✖️ Close (botMenu auto-renders these in submenus)
|
|
24
|
+
*
|
|
25
|
+
* What's deliberately NOT here yet:
|
|
26
|
+
*
|
|
27
|
+
* - Per-charge **History** view + in-menu refund request. The compliance
|
|
28
|
+
* surface (`/paysupport` + admin-mediated refund) is fulfilled by
|
|
29
|
+
* the admin commands and the message-side flow in `refund.ts`. The
|
|
30
|
+
* history UI is a v2 follow-up (see CLAUDE.md §"Open follow-ups").
|
|
31
|
+
*
|
|
32
|
+
* - Deep-link `require()` → exact rung. v1 opens VIP root; deep-linking
|
|
33
|
+
* needs menu-state encoding in callback data.
|
|
34
|
+
*/
|
|
35
|
+
import { say } from "../../say/index.js";
|
|
36
|
+
import { narrow } from "../ctx.js";
|
|
37
|
+
import { presentInvoice } from "./invoice.js";
|
|
38
|
+
const FALLBACK_LANG = "en";
|
|
39
|
+
const ctxLang = (ctx) => narrow(ctx).session?.language ?? FALLBACK_LANG;
|
|
40
|
+
const ctxPay = (ctx) => narrow(ctx).session?.pay;
|
|
41
|
+
const resolveName = (value, lang) => {
|
|
42
|
+
if (!value)
|
|
43
|
+
return "";
|
|
44
|
+
return value[lang] ?? value[FALLBACK_LANG] ?? Object.values(value)[0] ?? "";
|
|
45
|
+
};
|
|
46
|
+
// ─── public factory ────────────────────────────────────────────────
|
|
47
|
+
export const buildPaymentsMenuItem = (args) => {
|
|
48
|
+
const { cfg, catalog } = args;
|
|
49
|
+
// Root entry label + style adapt to current tier.
|
|
50
|
+
const rootLabel = (ctx) => {
|
|
51
|
+
const lang = ctxLang(ctx);
|
|
52
|
+
const pay = ctxPay(ctx);
|
|
53
|
+
if (!pay?.vip) {
|
|
54
|
+
return say({ en: "💎 VIP", es: "💎 VIP" }, lang);
|
|
55
|
+
}
|
|
56
|
+
// Active subscription — show name + days remaining.
|
|
57
|
+
const rung = catalog.vip[pay.vip.rung - 1];
|
|
58
|
+
const name = rung
|
|
59
|
+
? resolveName(rung.name, lang)
|
|
60
|
+
: "VIP";
|
|
61
|
+
const daysLeft = Math.max(0, Math.ceil((pay.vip.expiresAt * 1000 - Date.now()) / 86_400_000));
|
|
62
|
+
// Two visual states: active+renewing → no marker; canceled → "expira".
|
|
63
|
+
if (pay.vip.canceled) {
|
|
64
|
+
return say({
|
|
65
|
+
en: `💎 ${name} · expires in ${daysLeft} d`,
|
|
66
|
+
es: `💎 ${name} · expira en ${daysLeft} d`,
|
|
67
|
+
}, lang);
|
|
68
|
+
}
|
|
69
|
+
return say({ en: `💎 ${name} · ${daysLeft} d`, es: `💎 ${name} · ${daysLeft} d` }, lang);
|
|
70
|
+
};
|
|
71
|
+
const rootStyle = (ctx) => {
|
|
72
|
+
const pay = ctxPay(ctx);
|
|
73
|
+
if (!pay?.vip)
|
|
74
|
+
return "primary"; // free → blue, "buy me" affordance
|
|
75
|
+
if (pay.vip.canceled)
|
|
76
|
+
return "danger"; // about to lapse
|
|
77
|
+
return undefined; // active+renewing → neutral
|
|
78
|
+
};
|
|
79
|
+
// ── submenu items ───────────────────────────────────────────────
|
|
80
|
+
//
|
|
81
|
+
// Menu item ids must not contain `.` — bot/menu joins paths with
|
|
82
|
+
// `.` (`pay.<itemId>`), so a dot in the id collides with the path
|
|
83
|
+
// separator and `itemForPath()` mis-parses the segments → "Item
|
|
84
|
+
// not found" with no logging. We replace dots with `_` for the
|
|
85
|
+
// menu id only; the action closure still uses the canonical
|
|
86
|
+
// `productKey` ("vip.1", "credits.1", "perks.voice_mode") for the
|
|
87
|
+
// catalog lookup and invoice payload.
|
|
88
|
+
const menuId = (productKey) => productKey.replace(/\./g, "_");
|
|
89
|
+
const items = [];
|
|
90
|
+
// 1. Tier cards (one per declared rung)
|
|
91
|
+
for (const rung of catalog.vip) {
|
|
92
|
+
items.push({
|
|
93
|
+
id: menuId(rung.id), // e.g. 'vip_1' — path-safe menu id
|
|
94
|
+
label: (ctx) => {
|
|
95
|
+
const lang = ctxLang(ctx);
|
|
96
|
+
const name = resolveName(rung.name, lang);
|
|
97
|
+
const pay = ctxPay(ctx);
|
|
98
|
+
if (pay?.vip?.rung === rung.rank) {
|
|
99
|
+
return say({
|
|
100
|
+
en: `✓ ${name} — ${rung.xtr} ⭐ (current)`,
|
|
101
|
+
es: `✓ ${name} — ${rung.xtr} ⭐ (actual)`,
|
|
102
|
+
}, lang);
|
|
103
|
+
}
|
|
104
|
+
const isUpgrade = (pay?.vip?.rung ?? 0) < rung.rank;
|
|
105
|
+
const tag = isUpgrade
|
|
106
|
+
? say({ en: "Upgrade", es: "Subir" }, lang)
|
|
107
|
+
: say({ en: "Switch", es: "Cambiar" }, lang);
|
|
108
|
+
return `💎 ${name} — ${rung.xtr} ⭐ · ${tag}`;
|
|
109
|
+
},
|
|
110
|
+
style: (ctx) => {
|
|
111
|
+
const pay = ctxPay(ctx);
|
|
112
|
+
if (pay?.vip?.rung === rung.rank)
|
|
113
|
+
return "primary";
|
|
114
|
+
return undefined;
|
|
115
|
+
},
|
|
116
|
+
// Don't re-show the buy card for the currently-active rung —
|
|
117
|
+
// it's already labeled "current" and tapping would re-invoice
|
|
118
|
+
// the same product, which Telegram allows but isn't useful UX.
|
|
119
|
+
visible: (ctx) => ctxPay(ctx)?.vip?.rung !== rung.rank,
|
|
120
|
+
action: async (ctx) => {
|
|
121
|
+
await presentInvoice(narrow(ctx), cfg, catalog, rung.id);
|
|
122
|
+
// Toast is the only thing the menu plugin will send for this
|
|
123
|
+
// tap; the invoice / waiver prompt itself goes out as a
|
|
124
|
+
// fresh message via ctx.send inside presentInvoice.
|
|
125
|
+
return undefined;
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
// 2. Credits packs (if axis configured)
|
|
130
|
+
for (const pack of catalog.creditsPacks) {
|
|
131
|
+
items.push({
|
|
132
|
+
id: menuId(pack.id), // e.g. 'credits_1' — path-safe menu id
|
|
133
|
+
label: (ctx) => {
|
|
134
|
+
const lang = ctxLang(ctx);
|
|
135
|
+
const unit = catalog.creditsUnit
|
|
136
|
+
? resolveName(catalog.creditsUnit, lang)
|
|
137
|
+
: "credits";
|
|
138
|
+
const explicit = pack.name
|
|
139
|
+
? resolveName(pack.name, lang)
|
|
140
|
+
: undefined;
|
|
141
|
+
const body = explicit ?? `+${pack.creditsGranted} ${unit}`;
|
|
142
|
+
return `💬 ${body} — ${pack.xtr} ⭐`;
|
|
143
|
+
},
|
|
144
|
+
action: async (ctx) => {
|
|
145
|
+
await presentInvoice(narrow(ctx), cfg, catalog, pack.id);
|
|
146
|
+
return undefined;
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
// 3. Perks — hide when already owned (no "tap to re-buy" UX trap)
|
|
151
|
+
for (const perk of catalog.perks) {
|
|
152
|
+
items.push({
|
|
153
|
+
id: menuId(perk.id), // e.g. 'perks_voice_mode' — path-safe
|
|
154
|
+
label: (ctx) => {
|
|
155
|
+
const lang = ctxLang(ctx);
|
|
156
|
+
const name = resolveName(perk.name, lang);
|
|
157
|
+
return `🎁 ${name} — ${perk.xtr} ⭐`;
|
|
158
|
+
},
|
|
159
|
+
visible: (ctx) => !ctxPay(ctx)?.perks?.[perk.key],
|
|
160
|
+
action: async (ctx) => {
|
|
161
|
+
await presentInvoice(narrow(ctx), cfg, catalog, perk.id);
|
|
162
|
+
return undefined;
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
// 4. Manage: cancel renewal / resume renewal (single dynamic button)
|
|
167
|
+
items.push({
|
|
168
|
+
id: "manage",
|
|
169
|
+
label: (ctx) => {
|
|
170
|
+
const lang = ctxLang(ctx);
|
|
171
|
+
const pay = ctxPay(ctx);
|
|
172
|
+
if (!pay?.vip)
|
|
173
|
+
return ""; // hidden when free
|
|
174
|
+
return pay.vip.canceled
|
|
175
|
+
? say({ en: "↩️ Resume renewal", es: "↩️ Reanudar renovación" }, lang)
|
|
176
|
+
: say({ en: "⛔ Cancel renewal", es: "⛔ Cancelar renovación" }, lang);
|
|
177
|
+
},
|
|
178
|
+
style: (ctx) => {
|
|
179
|
+
const pay = ctxPay(ctx);
|
|
180
|
+
return pay?.vip?.canceled ? "primary" : "danger";
|
|
181
|
+
},
|
|
182
|
+
visible: (ctx) => !!ctxPay(ctx)?.vip,
|
|
183
|
+
refresh: true,
|
|
184
|
+
confirm: {
|
|
185
|
+
prompt: {
|
|
186
|
+
en: "⚠️ Toggle auto-renewal for your VIP subscription?\n\n" +
|
|
187
|
+
"Canceling: access continues until the current period ends; no renewal charge.\n" +
|
|
188
|
+
"Resuming: auto-renewal turns back on.",
|
|
189
|
+
es: "⚠️ ¿Cambiar la renovación automática de tu suscripción VIP?\n\n" +
|
|
190
|
+
"Cancelar: mantienes acceso hasta el fin del periodo actual; no se cobra renovación.\n" +
|
|
191
|
+
"Reanudar: se vuelve a activar la renovación automática.",
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
action: async (ctx) => {
|
|
195
|
+
const lang = ctxLang(ctx);
|
|
196
|
+
const pay = ctxPay(ctx);
|
|
197
|
+
if (!pay?.vip) {
|
|
198
|
+
return say({ en: "Nothing to do.", es: "Nada que hacer." }, lang);
|
|
199
|
+
}
|
|
200
|
+
const c = narrow(ctx);
|
|
201
|
+
const userId = c.from?.id;
|
|
202
|
+
if (!userId) {
|
|
203
|
+
return say({ en: "No user.", es: "Sin usuario." }, lang);
|
|
204
|
+
}
|
|
205
|
+
const wantCanceled = !pay.vip.canceled;
|
|
206
|
+
try {
|
|
207
|
+
await c.bot.api.editUserStarSubscription({
|
|
208
|
+
user_id: userId,
|
|
209
|
+
telegram_payment_charge_id: pay.vip.chargeId,
|
|
210
|
+
is_canceled: wantCanceled,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
catch (e) {
|
|
214
|
+
console.error("[bot/payments/menu] editUserStarSubscription failed", e);
|
|
215
|
+
return say({ en: "Failed — try later.", es: "Falló — prueba luego." }, lang);
|
|
216
|
+
}
|
|
217
|
+
// Reflect the new state in session so the menu re-renders
|
|
218
|
+
// correctly (refresh: true will fire the label/style resolvers
|
|
219
|
+
// again immediately after this returns).
|
|
220
|
+
const session = c.session;
|
|
221
|
+
if (session.pay?.vip) {
|
|
222
|
+
session.pay = {
|
|
223
|
+
...session.pay,
|
|
224
|
+
vip: { ...session.pay.vip, canceled: wantCanceled },
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
return wantCanceled
|
|
228
|
+
? say({ en: "⛔ Renewal canceled.", es: "⛔ Renovación cancelada." }, lang)
|
|
229
|
+
: say({ en: "↩️ Renewal resumed.", es: "↩️ Renovación reanudada." }, lang);
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
// 5. Help / refunds — URL-style action that just sends the paysupport blurb
|
|
233
|
+
items.push({
|
|
234
|
+
id: "help",
|
|
235
|
+
label: (ctx) => say({ en: "💬 Help & refunds", es: "💬 Ayuda y reembolsos" }, ctxLang(ctx)),
|
|
236
|
+
action: async (ctx) => {
|
|
237
|
+
const lang = ctxLang(ctx);
|
|
238
|
+
const c = narrow(ctx);
|
|
239
|
+
await c.send(say({
|
|
240
|
+
en: `Need help with a payment?\n\n` +
|
|
241
|
+
`• Contact: ${cfg.paysupport}\n` +
|
|
242
|
+
`• Include the chargeId from your purchase confirmation.\n\n` +
|
|
243
|
+
`Refunds are processed by the admin within Telegram.`,
|
|
244
|
+
es: `¿Necesitas ayuda con un pago?\n\n` +
|
|
245
|
+
`• Contacto: ${cfg.paysupport}\n` +
|
|
246
|
+
`• Incluye el chargeId de tu confirmación de compra.\n\n` +
|
|
247
|
+
`Los reembolsos los gestiona el admin dentro de Telegram.`,
|
|
248
|
+
}, lang));
|
|
249
|
+
return undefined;
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
return {
|
|
253
|
+
id: "pay",
|
|
254
|
+
label: rootLabel,
|
|
255
|
+
style: rootStyle,
|
|
256
|
+
submenu: items,
|
|
257
|
+
};
|
|
258
|
+
};
|
|
259
|
+
//# sourceMappingURL=menu-item.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"menu-item.js","sourceRoot":"","sources":["../../../src/bot/payments/menu-item.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Invoice payload codec. Telegram echoes the `invoice_payload` string
|
|
3
|
+
* back on `pre_checkout_query` and `successful_payment`; we use it to
|
|
4
|
+
* recover the `(productKey, userId)` pair that the in-flight purchase
|
|
5
|
+
* refers to.
|
|
6
|
+
*
|
|
7
|
+
* Constraints from Telegram:
|
|
8
|
+
* - Max 128 bytes.
|
|
9
|
+
* - Must be a string (we use printable ASCII).
|
|
10
|
+
*
|
|
11
|
+
* Format: `${productKey}|${userId}`
|
|
12
|
+
*
|
|
13
|
+
* The `|` delimiter is unambiguous because productKey is one of:
|
|
14
|
+
* - `vip.${number}` — only digits and `.`
|
|
15
|
+
* - `credits.${number}` — only digits and `.`
|
|
16
|
+
* - `perks.${key}` — key restricted to `/^[a-z][a-z0-9_]*$/i`
|
|
17
|
+
* by `config.ts` validation
|
|
18
|
+
*
|
|
19
|
+
* None of those contain `|`, so the first `|` is always the delimiter.
|
|
20
|
+
* Worst case length is ~`perks.${64-char-key}|${20-digit-int}` ≈ 90 bytes
|
|
21
|
+
* — comfortably under the 128-byte cap. We assert the length to fail
|
|
22
|
+
* loud if the cap is ever approached.
|
|
23
|
+
*/
|
|
24
|
+
export type DecodedPayload = {
|
|
25
|
+
readonly productKey: string;
|
|
26
|
+
readonly userId: number;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Build the `invoice_payload` string for a `sendInvoice` call. Throws
|
|
30
|
+
* `Panic` if the result would exceed Telegram's 128-byte cap — that's
|
|
31
|
+
* either a bad productKey or a userId out of bounds, both of which are
|
|
32
|
+
* bugs we want to scream about.
|
|
33
|
+
*/
|
|
34
|
+
export declare const encodePayload: (productKey: string, userId: number) => string;
|
|
35
|
+
/**
|
|
36
|
+
* Decode the `invoice_payload` Telegram echoes back. Returns `undefined`
|
|
37
|
+
* (NOT throws) on malformed input — Telegram could in principle deliver
|
|
38
|
+
* a payload from a different bot version, and we want the pre_checkout
|
|
39
|
+
* handler to be able to reject it gracefully rather than crashing.
|
|
40
|
+
*/
|
|
41
|
+
export declare const decodePayload: (payload: string) => DecodedPayload | undefined;
|
|
42
|
+
//# sourceMappingURL=payload.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payload.d.ts","sourceRoot":"","sources":["../../../src/bot/payments/payload.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAMH,MAAM,MAAM,cAAc,GAAG;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,aAAa,GAAI,YAAY,MAAM,EAAE,QAAQ,MAAM,KAAG,MAmBlE,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,aAAa,GAAI,SAAS,MAAM,KAAG,cAAc,GAAG,SAUhE,CAAC"}
|