@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,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type surface for `bot/payments`. Two layers:
|
|
3
|
+
*
|
|
4
|
+
* 1. **Config shapes** the consumer authors (`BotPaymentsConfig`).
|
|
5
|
+
* 2. **Derived types** the consumer reads through `ctx.payments`
|
|
6
|
+
* (`TierKey`, `AtLeastKey`, `ProductKey`) — narrowed from their
|
|
7
|
+
* config so e.g. `atLeast('vip.5')` is a TS error when only 2
|
|
8
|
+
* rungs are declared.
|
|
9
|
+
*
|
|
10
|
+
* Internal types (the ledger records, the catalog) live below and aren't
|
|
11
|
+
* exported through the package entrypoint.
|
|
12
|
+
*
|
|
13
|
+
* See `CLAUDE.md` in this folder for the design rationale.
|
|
14
|
+
*/
|
|
15
|
+
import type { Polyglot } from "../../say/index.js";
|
|
16
|
+
/**
|
|
17
|
+
* The only Telegram-supported subscription period as of 2026 is exactly
|
|
18
|
+
* 2,592,000 seconds (30 days). Modeled as a literal so misconfigurations
|
|
19
|
+
* fail at compile time. See `sendInvoice` /
|
|
20
|
+
* `createInvoiceLink` docs at https://core.telegram.org/bots/api.
|
|
21
|
+
*/
|
|
22
|
+
export type SubscriptionPeriod = "30d";
|
|
23
|
+
/** Internal value Telegram expects on the wire (seconds). */
|
|
24
|
+
export declare const SUBSCRIPTION_PERIOD_SECONDS: Readonly<Record<SubscriptionPeriod, number>>;
|
|
25
|
+
/**
|
|
26
|
+
* One rung of the VIP ladder. Position in the array is the rank (1-based
|
|
27
|
+
* id at the call site: `vip.1`, `vip.2`, …). Insert a rung at position 2
|
|
28
|
+
* → existing subscribers on rung 2+ shift by one, plus a trivial storage
|
|
29
|
+
* migration; no string-id churn.
|
|
30
|
+
*
|
|
31
|
+
* `name` is display-only Polyglot — renaming a rung is a config edit; the
|
|
32
|
+
* call sites (`atLeast('vip.2')`) are positional and untouched.
|
|
33
|
+
*
|
|
34
|
+
* `grants.credits` is applied **on every renewal** of this rung (not just
|
|
35
|
+
* the initial purchase). That's the canonical SaaS shape: "VIP gets 1000
|
|
36
|
+
* messages per month."
|
|
37
|
+
*/
|
|
38
|
+
export type VipRung<L extends string = string> = {
|
|
39
|
+
readonly xtr: number;
|
|
40
|
+
readonly period: SubscriptionPeriod;
|
|
41
|
+
readonly name: Polyglot<L>;
|
|
42
|
+
readonly grants?: {
|
|
43
|
+
readonly credits?: number;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* A consumable credit pack the user buys one-shot to top up their
|
|
48
|
+
* `session.pay.credits` balance. Like VIP rungs, packs are positionally
|
|
49
|
+
* identified (`credits.1`, `credits.2`, …) so the call site doesn't carry
|
|
50
|
+
* a name. Adding/removing packs shifts ids; storage is the charge log
|
|
51
|
+
* (which records the exact productKey at purchase time), so historical
|
|
52
|
+
* charges stay valid even if you remove a pack later.
|
|
53
|
+
*/
|
|
54
|
+
export type CreditsPack<L extends string = string> = {
|
|
55
|
+
readonly xtr: number;
|
|
56
|
+
readonly grants: {
|
|
57
|
+
readonly credits: number;
|
|
58
|
+
};
|
|
59
|
+
readonly name?: Polyglot<L>;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Credits axis config. `unit` is the user-facing word for one credit
|
|
63
|
+
* (the menu says e.g. "1234 mensajes"). `packs` is the catalogue of
|
|
64
|
+
* top-up bundles surfaced in the menu and via `invoice('credits.N')`.
|
|
65
|
+
*/
|
|
66
|
+
export type CreditsConfig<L extends string = string> = {
|
|
67
|
+
readonly unit: Polyglot<L>;
|
|
68
|
+
readonly packs: ReadonlyArray<CreditsPack<L>>;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Orthogonal one-shot unlock (a "perk"). Named via the user's own key in
|
|
72
|
+
* the config (`perks: { voice_mode: { … } }`), surfaced as
|
|
73
|
+
* `perks.voice_mode` everywhere.
|
|
74
|
+
*/
|
|
75
|
+
export type Perk<L extends string = string> = {
|
|
76
|
+
readonly xtr: number;
|
|
77
|
+
readonly name: Polyglot<L>;
|
|
78
|
+
};
|
|
79
|
+
export type PerksConfig<L extends string = string> = Readonly<Record<string, Perk<L>>>;
|
|
80
|
+
export type LegalConfig = {
|
|
81
|
+
readonly sellerName: string;
|
|
82
|
+
readonly nif: string;
|
|
83
|
+
/**
|
|
84
|
+
* Optional terms-of-service URL. When omitted, the consent prompt's
|
|
85
|
+
* 📖 Terms button is hidden — there's no Telegram-side standard ToS
|
|
86
|
+
* to default to (unlike `privacyUrl`).
|
|
87
|
+
*/
|
|
88
|
+
readonly termsUrl?: string;
|
|
89
|
+
/**
|
|
90
|
+
* Optional privacy policy URL. When omitted, defaults to Telegram's
|
|
91
|
+
* [Standard Bot Privacy Policy](https://telegram.org/privacy-tpa),
|
|
92
|
+
* which covers what this library's plugins retain (session record,
|
|
93
|
+
* waiver consent, charge log) under "data necessary to function".
|
|
94
|
+
* Override only when your bot retains data beyond what the standard
|
|
95
|
+
* covers.
|
|
96
|
+
*/
|
|
97
|
+
readonly privacyUrl?: string;
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Fallback when `legal.privacyUrl` is omitted. Identical to the menu
|
|
101
|
+
* plugin's default — both surface the same policy text.
|
|
102
|
+
*/
|
|
103
|
+
export declare const DEFAULT_PRIVACY_URL = "https://telegram.org/privacy-tpa";
|
|
104
|
+
/**
|
|
105
|
+
* Art. 103(m) TRLGDCU waiver text. Versioned so a wording change forces
|
|
106
|
+
* re-consent; the version string is stored alongside each charge for audit.
|
|
107
|
+
*
|
|
108
|
+
* The Polyglot covers every locale the bot supports — the plugin
|
|
109
|
+
* validates this at construction.
|
|
110
|
+
*/
|
|
111
|
+
export type WaiverConfig<L extends string = string> = {
|
|
112
|
+
readonly version: string;
|
|
113
|
+
readonly text: Polyglot<L>;
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* What the bot author passes to `botPayments({...})`. The generic `L`
|
|
117
|
+
* carries the bot's Polyglot language union forward into every nested
|
|
118
|
+
* `Polyglot<L>`, so TS catches a missing locale in any sub-config.
|
|
119
|
+
*/
|
|
120
|
+
export type BotPaymentsConfig<L extends string = string> = {
|
|
121
|
+
readonly paysupport: string;
|
|
122
|
+
readonly legal: LegalConfig;
|
|
123
|
+
readonly waiver: WaiverConfig<L>;
|
|
124
|
+
readonly vip?: ReadonlyArray<VipRung<L>>;
|
|
125
|
+
readonly credits?: CreditsConfig<L>;
|
|
126
|
+
readonly perks?: PerksConfig<L>;
|
|
127
|
+
};
|
|
128
|
+
type _Range1To<N extends number, Acc extends unknown[] = [unknown]> = Acc["length"] extends N ? Acc["length"] : Acc["length"] | _Range1To<N, [...Acc, unknown]>;
|
|
129
|
+
/**
|
|
130
|
+
* Positional 1-based ids for a tuple. `[a,b,c]` → `1 | 2 | 3`.
|
|
131
|
+
*
|
|
132
|
+
* Empty tuple → `never` (no ids exist).
|
|
133
|
+
*/
|
|
134
|
+
export type Indices1<V extends ReadonlyArray<unknown>> = V["length"] extends 0 ? never : V["length"] extends number ? _Range1To<V["length"]> : never;
|
|
135
|
+
/**
|
|
136
|
+
* Tier ids derivable from a `vip` config array. Bare `'vip'` is also
|
|
137
|
+
* accepted as "any rung" by `atLeast` (see `AtLeastKey`).
|
|
138
|
+
*/
|
|
139
|
+
export type VipPositionalKeys<V extends ReadonlyArray<unknown> | undefined> = V extends ReadonlyArray<unknown> ? `vip.${Indices1<V>}` : never;
|
|
140
|
+
export type CreditsPackKeys<C extends CreditsConfig<string> | undefined> = C extends {
|
|
141
|
+
packs: infer P;
|
|
142
|
+
} ? P extends ReadonlyArray<unknown> ? `credits.${Indices1<P>}` : never : never;
|
|
143
|
+
export type PerkKeys<P extends PerksConfig<string> | undefined> = P extends PerksConfig<string> ? `perks.${keyof P & string}` : never;
|
|
144
|
+
/**
|
|
145
|
+
* Every product the user can purchase. Discriminated by prefix. Note
|
|
146
|
+
* `perks.X` uses the **author-supplied key** verbatim (e.g.
|
|
147
|
+
* `perks.voice_mode`) while `vip.N` / `credits.N` are positional.
|
|
148
|
+
*/
|
|
149
|
+
export type ProductKey<Cfg extends BotPaymentsConfig<string>> = VipPositionalKeys<Cfg["vip"]> | CreditsPackKeys<Cfg["credits"]> | PerkKeys<Cfg["perks"]>;
|
|
150
|
+
/**
|
|
151
|
+
* What `ctx.payments.tier()` can return. `'free'` is the bottom rung;
|
|
152
|
+
* `vip.N` follows the ladder.
|
|
153
|
+
*/
|
|
154
|
+
export type TierKey<Cfg extends BotPaymentsConfig<string>> = "free" | VipPositionalKeys<Cfg["vip"]>;
|
|
155
|
+
/**
|
|
156
|
+
* What `ctx.payments.atLeast(...)` accepts. Same as `TierKey` minus
|
|
157
|
+
* `'free'` (atLeast('free') is trivially always true and unhelpful as a
|
|
158
|
+
* gate) plus the bare `'vip'` synonym for "any rung in the namespace".
|
|
159
|
+
*/
|
|
160
|
+
export type AtLeastKey<Cfg extends BotPaymentsConfig<string>> = "vip" | VipPositionalKeys<Cfg["vip"]>;
|
|
161
|
+
export type VipRungResolved = {
|
|
162
|
+
readonly id: `vip.${number}`;
|
|
163
|
+
readonly rank: number;
|
|
164
|
+
readonly xtr: number;
|
|
165
|
+
readonly periodSeconds: number;
|
|
166
|
+
readonly name: Polyglot<string>;
|
|
167
|
+
readonly creditsGranted: number;
|
|
168
|
+
};
|
|
169
|
+
export type CreditsPackResolved = {
|
|
170
|
+
readonly id: `credits.${number}`;
|
|
171
|
+
readonly xtr: number;
|
|
172
|
+
readonly creditsGranted: number;
|
|
173
|
+
readonly name?: Polyglot<string>;
|
|
174
|
+
};
|
|
175
|
+
export type PerkResolved = {
|
|
176
|
+
readonly id: `perks.${string}`;
|
|
177
|
+
readonly key: string;
|
|
178
|
+
readonly xtr: number;
|
|
179
|
+
readonly name: Polyglot<string>;
|
|
180
|
+
};
|
|
181
|
+
export type ProductCatalog = {
|
|
182
|
+
readonly vip: ReadonlyArray<VipRungResolved>;
|
|
183
|
+
readonly creditsUnit: Polyglot<string> | undefined;
|
|
184
|
+
readonly creditsPacks: ReadonlyArray<CreditsPackResolved>;
|
|
185
|
+
readonly perks: ReadonlyArray<PerkResolved>;
|
|
186
|
+
/** O(1) lookup by ProductKey. */
|
|
187
|
+
readonly byKey: ReadonlyMap<string, VipRungResolved | CreditsPackResolved | PerkResolved>;
|
|
188
|
+
};
|
|
189
|
+
export type WaiverRecord = {
|
|
190
|
+
readonly at: number;
|
|
191
|
+
readonly version: string;
|
|
192
|
+
readonly locale: string;
|
|
193
|
+
};
|
|
194
|
+
export type VipState = {
|
|
195
|
+
/** 1-based rank — index into the resolved vip ladder + 1. */
|
|
196
|
+
readonly rung: number;
|
|
197
|
+
/** Original `telegram_payment_charge_id` of the active subscription. */
|
|
198
|
+
readonly chargeId: string;
|
|
199
|
+
/** Telegram's `subscription_expiration_date` — unix seconds. */
|
|
200
|
+
readonly expiresAt: number;
|
|
201
|
+
/** True after the user toggled cancel-renewal; access keeps until `expiresAt`. */
|
|
202
|
+
readonly canceled: boolean;
|
|
203
|
+
};
|
|
204
|
+
export type PerkState = {
|
|
205
|
+
readonly chargeId: string;
|
|
206
|
+
readonly at: number;
|
|
207
|
+
};
|
|
208
|
+
export type PaymentsSession = {
|
|
209
|
+
waiver?: WaiverRecord;
|
|
210
|
+
credits?: number;
|
|
211
|
+
vip?: VipState;
|
|
212
|
+
perks?: Record<string, PerkState>;
|
|
213
|
+
};
|
|
214
|
+
export type PaysupportState = "none" | "opened" | "refunded";
|
|
215
|
+
export type ChargeRecord = {
|
|
216
|
+
readonly chargeId: string;
|
|
217
|
+
readonly userId: number;
|
|
218
|
+
readonly productKey: string;
|
|
219
|
+
readonly xtr: number;
|
|
220
|
+
readonly receivedAt: number;
|
|
221
|
+
/** Decoded invoice payload echoed back from Telegram. */
|
|
222
|
+
readonly payload: string;
|
|
223
|
+
/**
|
|
224
|
+
* Waiver snapshot at purchase time. Captured even if the user
|
|
225
|
+
* already consented earlier — defensive forensics.
|
|
226
|
+
*/
|
|
227
|
+
readonly waiverSnapshot: WaiverRecord;
|
|
228
|
+
/**
|
|
229
|
+
* Telegram's `subscription_expiration_date` (unix seconds) if this
|
|
230
|
+
* charge was a subscription purchase. Used to compute tier expiry.
|
|
231
|
+
*/
|
|
232
|
+
readonly subscriptionExpiresAt?: number;
|
|
233
|
+
/**
|
|
234
|
+
* Credits this charge grants at fulfillment / renewal. Denormalized
|
|
235
|
+
* onto the record so derivation works even if the catalog config
|
|
236
|
+
* later changes (rare, but the charge log must remain authoritative
|
|
237
|
+
* regardless of source-code edits). Zero for products that don't
|
|
238
|
+
* grant credits.
|
|
239
|
+
*/
|
|
240
|
+
readonly creditsGranted: number;
|
|
241
|
+
/**
|
|
242
|
+
* 1-based vip rung this charge subscribed to, denormalized for the
|
|
243
|
+
* same reason as `creditsGranted`. Only set for `vip.*` charges.
|
|
244
|
+
*/
|
|
245
|
+
readonly vipRung?: number;
|
|
246
|
+
/**
|
|
247
|
+
* Perk key this charge unlocked, denormalized. Only set for
|
|
248
|
+
* `perks.*` charges.
|
|
249
|
+
*/
|
|
250
|
+
readonly perkKey?: string;
|
|
251
|
+
paysupportState: PaysupportState;
|
|
252
|
+
refundedAt?: number | null;
|
|
253
|
+
/** Set after `record()` claims this charge into a payout batch. */
|
|
254
|
+
payoutBatchId?: string | null;
|
|
255
|
+
};
|
|
256
|
+
export type PayoutRecord = {
|
|
257
|
+
readonly batchId: string;
|
|
258
|
+
readonly fromMs: number;
|
|
259
|
+
readonly toMs: number;
|
|
260
|
+
readonly tonAmount: number;
|
|
261
|
+
readonly eurAtReceipt: number;
|
|
262
|
+
readonly recordedAt: number;
|
|
263
|
+
/** Optional Spanish factura number issued for this payout. */
|
|
264
|
+
facturaNumber?: string;
|
|
265
|
+
};
|
|
266
|
+
/**
|
|
267
|
+
* Snapshot returned by `derive.ts` — pure function of the charge log
|
|
268
|
+
* for a given user. The plugin writes this into `session.pay.*` so
|
|
269
|
+
* runtime checks are O(1), but the log is always the truth.
|
|
270
|
+
*/
|
|
271
|
+
export type DerivedPaymentsState = {
|
|
272
|
+
readonly credits: number;
|
|
273
|
+
readonly vip: VipState | undefined;
|
|
274
|
+
readonly perks: Record<string, PerkState>;
|
|
275
|
+
};
|
|
276
|
+
/**
|
|
277
|
+
* What `payments.onFulfilled(productKey, handler)` receives. Sync
|
|
278
|
+
* signature on purpose — the plugin always answers the
|
|
279
|
+
* `successful_payment` event itself; the handler does its own async work
|
|
280
|
+
* fire-and-forget.
|
|
281
|
+
*/
|
|
282
|
+
export type FulfillmentEvent = {
|
|
283
|
+
readonly productKey: string;
|
|
284
|
+
readonly userId: number;
|
|
285
|
+
readonly chargeId: string;
|
|
286
|
+
readonly xtr: number;
|
|
287
|
+
readonly receivedAt: number;
|
|
288
|
+
};
|
|
289
|
+
/**
|
|
290
|
+
* Thrown by `credits.consume(n)` when the balance is below `n`.
|
|
291
|
+
* `tryConsume(n)` returns `false` instead.
|
|
292
|
+
*/
|
|
293
|
+
export declare class InsufficientCredits extends Error {
|
|
294
|
+
readonly requested: number;
|
|
295
|
+
readonly available: number;
|
|
296
|
+
constructor(requested: number, available: number);
|
|
297
|
+
}
|
|
298
|
+
export {};
|
|
299
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/bot/payments/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type surface for `bot/payments`. Two layers:
|
|
3
|
+
*
|
|
4
|
+
* 1. **Config shapes** the consumer authors (`BotPaymentsConfig`).
|
|
5
|
+
* 2. **Derived types** the consumer reads through `ctx.payments`
|
|
6
|
+
* (`TierKey`, `AtLeastKey`, `ProductKey`) — narrowed from their
|
|
7
|
+
* config so e.g. `atLeast('vip.5')` is a TS error when only 2
|
|
8
|
+
* rungs are declared.
|
|
9
|
+
*
|
|
10
|
+
* Internal types (the ledger records, the catalog) live below and aren't
|
|
11
|
+
* exported through the package entrypoint.
|
|
12
|
+
*
|
|
13
|
+
* See `CLAUDE.md` in this folder for the design rationale.
|
|
14
|
+
*/
|
|
15
|
+
/** Internal value Telegram expects on the wire (seconds). */
|
|
16
|
+
export const SUBSCRIPTION_PERIOD_SECONDS = {
|
|
17
|
+
"30d": 2_592_000,
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Fallback when `legal.privacyUrl` is omitted. Identical to the menu
|
|
21
|
+
* plugin's default — both surface the same policy text.
|
|
22
|
+
*/
|
|
23
|
+
export const DEFAULT_PRIVACY_URL = "https://telegram.org/privacy-tpa";
|
|
24
|
+
// ─── error sentinel ────────────────────────────────────────────────
|
|
25
|
+
/**
|
|
26
|
+
* Thrown by `credits.consume(n)` when the balance is below `n`.
|
|
27
|
+
* `tryConsume(n)` returns `false` instead.
|
|
28
|
+
*/
|
|
29
|
+
export class InsufficientCredits extends Error {
|
|
30
|
+
requested;
|
|
31
|
+
available;
|
|
32
|
+
constructor(requested, available) {
|
|
33
|
+
super(`InsufficientCredits: requested ${requested}, available ${available}`);
|
|
34
|
+
this.name = "InsufficientCredits";
|
|
35
|
+
this.requested = requested;
|
|
36
|
+
this.available = available;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/bot/payments/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAcH,6DAA6D;AAC7D,MAAM,CAAC,MAAM,2BAA2B,GAEpC;IACH,KAAK,EAAE,SAAS;CAChB,CAAC;AAwFF;;;GAGG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,kCAAkC,CAAC;AAoQtE,sEAAsE;AAEtE;;;GAGG;AACH,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IACpC,SAAS,CAAS;IAClB,SAAS,CAAS;IAC3B,YAAY,SAAiB,EAAE,SAAiB;QAC/C,KAAK,CACJ,kCAAkC,SAAS,eAAe,SAAS,EAAE,CACrE,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;QAClC,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC5B,CAAC;CACD"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Art. 103(m) TRLGDCU waiver flow.
|
|
3
|
+
*
|
|
4
|
+
* Three things have to happen together (see CLAUDE.md §5):
|
|
5
|
+
*
|
|
6
|
+
* 1. The user expresses *prior express consent* to begin execution.
|
|
7
|
+
* 2. They acknowledge they thereby lose the 14-day right of withdrawal.
|
|
8
|
+
* 3. The contract is confirmed on a durable medium.
|
|
9
|
+
*
|
|
10
|
+
* This module owns #1 and #2 via a separate, unbundled consent prompt
|
|
11
|
+
* — one inline message with the waiver text plus a single affirmative
|
|
12
|
+
* button. Bundling this into ToS acceptance voids the waiver under
|
|
13
|
+
* Spanish consumer law. #3 is satisfied implicitly by Telegram: every
|
|
14
|
+
* outgoing message the bot sends is preserved in the chat (durable).
|
|
15
|
+
*
|
|
16
|
+
* The bot author supplies the wording (Polyglot text + version string).
|
|
17
|
+
* Bumping `waiver.version` forces re-consent on the next purchase
|
|
18
|
+
* attempt; old consents stay attached to old charges for audit (see
|
|
19
|
+
* `ChargeRecord.waiverSnapshot`).
|
|
20
|
+
*/
|
|
21
|
+
import type { Storage } from "@gramio/storage";
|
|
22
|
+
import { InlineKeyboard } from "gramio";
|
|
23
|
+
import { type BotPaymentsConfig, type PaymentsSession, type WaiverRecord } from "./types.js";
|
|
24
|
+
/**
|
|
25
|
+
* Callback fired when the user taps "✅ Consiento y comprendo" in the
|
|
26
|
+
* waiver consent prompt. Carries the productKey of the in-flight
|
|
27
|
+
* purchase so we can resume after persisting consent.
|
|
28
|
+
*
|
|
29
|
+
* `pk` (productKey) follows the shape validated by `payload.ts` —
|
|
30
|
+
* `vip.N` / `credits.N` / `perks.X` — all ASCII-safe.
|
|
31
|
+
*/
|
|
32
|
+
export declare const waiverConsentCb: import("gramio").CallbackData<Record<never, never>, Record<never, never>>;
|
|
33
|
+
/** Fired when the user taps "❌ Cancelar" in the consent prompt. */
|
|
34
|
+
export declare const waiverCancelCb: import("gramio").CallbackData<Record<never, never>, Record<never, never>>;
|
|
35
|
+
/**
|
|
36
|
+
* Returns true when the stored consent matches the current waiver
|
|
37
|
+
* version — i.e. no re-consent needed before a purchase.
|
|
38
|
+
*/
|
|
39
|
+
export declare const isWaiverFresh: (stored: WaiverRecord | undefined, currentVersion: string) => boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Build the inline keyboard that accompanies the consent prompt. Three
|
|
42
|
+
* buttons:
|
|
43
|
+
*
|
|
44
|
+
* 📖 Términos — URL to `legal.termsUrl` (informational, not the
|
|
45
|
+
* affirmative action)
|
|
46
|
+
* 🔒 Privacidad — URL to `legal.privacyUrl` (same)
|
|
47
|
+
* ✅ Consiento y comprendo — the single affirmative action
|
|
48
|
+
* ❌ Cancelar — back out without consenting
|
|
49
|
+
*
|
|
50
|
+
* The "✅" button carries the productKey the user was about to buy, so
|
|
51
|
+
* the consent handler can resume the invoice flow on tap.
|
|
52
|
+
*/
|
|
53
|
+
export declare const waiverKeyboard: (cfg: BotPaymentsConfig<string>, productKey: string, lang: string) => InlineKeyboard;
|
|
54
|
+
/**
|
|
55
|
+
* Render the consent body the user sees. Includes a short, neutral
|
|
56
|
+
* header above the wording supplied by the bot author. The header is
|
|
57
|
+
* separated by a blank line so reading order matches what consumer
|
|
58
|
+
* authorities expect (waiver text is unambiguous and prominent).
|
|
59
|
+
*/
|
|
60
|
+
export declare const renderConsentBody: (cfg: BotPaymentsConfig<string>, lang: string) => string;
|
|
61
|
+
/**
|
|
62
|
+
* Persist a fresh waiver record onto the current ctx's session. Callers
|
|
63
|
+
* own the session shape via a structural narrow — this avoids a generic
|
|
64
|
+
* type bloom across the plugin.
|
|
65
|
+
*/
|
|
66
|
+
export declare const persistWaiverOnSession: (ctx: {
|
|
67
|
+
session: {
|
|
68
|
+
pay?: PaymentsSession;
|
|
69
|
+
};
|
|
70
|
+
}, version: string, locale: string) => WaiverRecord;
|
|
71
|
+
/**
|
|
72
|
+
* Persist a waiver record onto a SPECIFIC user's stored session (used
|
|
73
|
+
* when the consent tap arrives on a different ctx than the original
|
|
74
|
+
* `invoice()` call — rare, but possible if the user wedges multiple
|
|
75
|
+
* flows). The cross-user write is via storage directly, mirroring the
|
|
76
|
+
* pattern in `access-control.ts`.
|
|
77
|
+
*/
|
|
78
|
+
export declare const persistWaiverForUser: (storage: Storage, ctx: {
|
|
79
|
+
bot: unknown;
|
|
80
|
+
}, userId: number, version: string, locale: string) => Promise<WaiverRecord>;
|
|
81
|
+
//# sourceMappingURL=waiver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"waiver.d.ts","sourceRoot":"","sources":["../../../src/bot/payments/waiver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAMxC,OAAO,EACN,KAAK,iBAAiB,EAEtB,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,MAAM,YAAY,CAAC;AAOpB;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,2EAA8C,CAAC;AAE3E,mEAAmE;AACnE,eAAO,MAAM,cAAc,2EAA+B,CAAC;AAI3D;;;GAGG;AACH,eAAO,MAAM,aAAa,GACzB,QAAQ,YAAY,GAAG,SAAS,EAChC,gBAAgB,MAAM,KACpB,OAGF,CAAC;AAiBF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,cAAc,GAC1B,KAAK,iBAAiB,CAAC,MAAM,CAAC,EAC9B,YAAY,MAAM,EAClB,MAAM,MAAM,KACV,cA8BF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAC7B,KAAK,iBAAiB,CAAC,MAAM,CAAC,EAC9B,MAAM,MAAM,KACV,MAWU,CAAC;AAId;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,GAClC,KAAK;IAAE,OAAO,EAAE;QAAE,GAAG,CAAC,EAAE,eAAe,CAAA;KAAE,CAAA;CAAE,EAC3C,SAAS,MAAM,EACf,QAAQ,MAAM,KACZ,YAUF,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAChC,SAAS,OAAO,EAChB,KAAK;IAAE,GAAG,EAAE,OAAO,CAAA;CAAE,EACrB,QAAQ,MAAM,EACd,SAAS,MAAM,EACf,QAAQ,MAAM,KACZ,OAAO,CAAC,YAAY,CAatB,CAAC"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Art. 103(m) TRLGDCU waiver flow.
|
|
3
|
+
*
|
|
4
|
+
* Three things have to happen together (see CLAUDE.md §5):
|
|
5
|
+
*
|
|
6
|
+
* 1. The user expresses *prior express consent* to begin execution.
|
|
7
|
+
* 2. They acknowledge they thereby lose the 14-day right of withdrawal.
|
|
8
|
+
* 3. The contract is confirmed on a durable medium.
|
|
9
|
+
*
|
|
10
|
+
* This module owns #1 and #2 via a separate, unbundled consent prompt
|
|
11
|
+
* — one inline message with the waiver text plus a single affirmative
|
|
12
|
+
* button. Bundling this into ToS acceptance voids the waiver under
|
|
13
|
+
* Spanish consumer law. #3 is satisfied implicitly by Telegram: every
|
|
14
|
+
* outgoing message the bot sends is preserved in the chat (durable).
|
|
15
|
+
*
|
|
16
|
+
* The bot author supplies the wording (Polyglot text + version string).
|
|
17
|
+
* Bumping `waiver.version` forces re-consent on the next purchase
|
|
18
|
+
* attempt; old consents stay attached to old charges for audit (see
|
|
19
|
+
* `ChargeRecord.waiverSnapshot`).
|
|
20
|
+
*/
|
|
21
|
+
import { InlineKeyboard } from "gramio";
|
|
22
|
+
import { say } from "../../say/index.js";
|
|
23
|
+
import { createLogger } from "../../universal/log.js";
|
|
24
|
+
import { callbackNs } from "../callbacks.js";
|
|
25
|
+
import { botStorageKey } from "../kit.js";
|
|
26
|
+
import { DEFAULT_PRIVACY_URL, } from "./types.js";
|
|
27
|
+
const FALLBACK_LANG = "en";
|
|
28
|
+
const log = createLogger("bot/payments");
|
|
29
|
+
const cb = callbackNs("pay");
|
|
30
|
+
/**
|
|
31
|
+
* Callback fired when the user taps "✅ Consiento y comprendo" in the
|
|
32
|
+
* waiver consent prompt. Carries the productKey of the in-flight
|
|
33
|
+
* purchase so we can resume after persisting consent.
|
|
34
|
+
*
|
|
35
|
+
* `pk` (productKey) follows the shape validated by `payload.ts` —
|
|
36
|
+
* `vip.N` / `credits.N` / `perks.X` — all ASCII-safe.
|
|
37
|
+
*/
|
|
38
|
+
export const waiverConsentCb = cb.data("waiver:consent", { pk: "string" });
|
|
39
|
+
/** Fired when the user taps "❌ Cancelar" in the consent prompt. */
|
|
40
|
+
export const waiverCancelCb = cb.data("waiver:cancel", {});
|
|
41
|
+
// ─── pure helpers ──────────────────────────────────────────────────
|
|
42
|
+
/**
|
|
43
|
+
* Returns true when the stored consent matches the current waiver
|
|
44
|
+
* version — i.e. no re-consent needed before a purchase.
|
|
45
|
+
*/
|
|
46
|
+
export const isWaiverFresh = (stored, currentVersion) => {
|
|
47
|
+
if (!stored)
|
|
48
|
+
return false;
|
|
49
|
+
return stored.version === currentVersion;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Resolve the waiver text Polyglot to a single string for the recipient's
|
|
53
|
+
* locale, with `'en'` fallback. Matches `say()`'s semantics for the
|
|
54
|
+
* dynamic-string escape hatch.
|
|
55
|
+
*/
|
|
56
|
+
const resolveWaiverText = (cfg, lang) => {
|
|
57
|
+
const text = cfg.waiver.text;
|
|
58
|
+
return text[lang] ?? text[FALLBACK_LANG] ?? Object.values(text)[0] ?? "";
|
|
59
|
+
};
|
|
60
|
+
// ─── consent prompt rendering ──────────────────────────────────────
|
|
61
|
+
/**
|
|
62
|
+
* Build the inline keyboard that accompanies the consent prompt. Three
|
|
63
|
+
* buttons:
|
|
64
|
+
*
|
|
65
|
+
* 📖 Términos — URL to `legal.termsUrl` (informational, not the
|
|
66
|
+
* affirmative action)
|
|
67
|
+
* 🔒 Privacidad — URL to `legal.privacyUrl` (same)
|
|
68
|
+
* ✅ Consiento y comprendo — the single affirmative action
|
|
69
|
+
* ❌ Cancelar — back out without consenting
|
|
70
|
+
*
|
|
71
|
+
* The "✅" button carries the productKey the user was about to buy, so
|
|
72
|
+
* the consent handler can resume the invoice flow on tap.
|
|
73
|
+
*/
|
|
74
|
+
export const waiverKeyboard = (cfg, productKey, lang) => {
|
|
75
|
+
const kb = new InlineKeyboard();
|
|
76
|
+
const termsUrl = cfg.legal.termsUrl;
|
|
77
|
+
const privacyUrl = cfg.legal.privacyUrl ?? DEFAULT_PRIVACY_URL;
|
|
78
|
+
// Terms button only renders if the bot configured a termsUrl —
|
|
79
|
+
// there's no Telegram-side standard ToS to default to. Privacy
|
|
80
|
+
// always renders; it falls back to Telegram's Standard Bot Privacy
|
|
81
|
+
// Policy when unset (covers what this library retains by default).
|
|
82
|
+
if (termsUrl) {
|
|
83
|
+
kb.url(say({ en: "📖 Terms", es: "📖 Términos" }, lang), termsUrl);
|
|
84
|
+
}
|
|
85
|
+
kb.url(say({ en: "🔒 Privacy", es: "🔒 Privacidad" }, lang), privacyUrl);
|
|
86
|
+
kb.row();
|
|
87
|
+
kb.text(say({
|
|
88
|
+
en: "✅ I consent and understand",
|
|
89
|
+
es: "✅ Consiento y comprendo",
|
|
90
|
+
}, lang), waiverConsentCb.pack({ pk: productKey }), { style: "primary" });
|
|
91
|
+
kb.row();
|
|
92
|
+
kb.text(say({ en: "❌ Cancel", es: "❌ Cancelar" }, lang), waiverCancelCb.pack({}));
|
|
93
|
+
return kb;
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Render the consent body the user sees. Includes a short, neutral
|
|
97
|
+
* header above the wording supplied by the bot author. The header is
|
|
98
|
+
* separated by a blank line so reading order matches what consumer
|
|
99
|
+
* authorities expect (waiver text is unambiguous and prominent).
|
|
100
|
+
*/
|
|
101
|
+
export const renderConsentBody = (cfg, lang) => [
|
|
102
|
+
say({
|
|
103
|
+
en: "Before continuing, please read and consent:",
|
|
104
|
+
es: "Antes de continuar, lee y consiente:",
|
|
105
|
+
}, lang),
|
|
106
|
+
"",
|
|
107
|
+
resolveWaiverText(cfg, lang),
|
|
108
|
+
].join("\n");
|
|
109
|
+
// ─── persistence ───────────────────────────────────────────────────
|
|
110
|
+
/**
|
|
111
|
+
* Persist a fresh waiver record onto the current ctx's session. Callers
|
|
112
|
+
* own the session shape via a structural narrow — this avoids a generic
|
|
113
|
+
* type bloom across the plugin.
|
|
114
|
+
*/
|
|
115
|
+
export const persistWaiverOnSession = (ctx, version, locale) => {
|
|
116
|
+
const record = {
|
|
117
|
+
at: Date.now(),
|
|
118
|
+
version,
|
|
119
|
+
locale,
|
|
120
|
+
};
|
|
121
|
+
ctx.session.pay ??= {};
|
|
122
|
+
ctx.session.pay.waiver = record;
|
|
123
|
+
log.success(`waiver consent persisted: version=${version} locale=${locale}`);
|
|
124
|
+
return record;
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* Persist a waiver record onto a SPECIFIC user's stored session (used
|
|
128
|
+
* when the consent tap arrives on a different ctx than the original
|
|
129
|
+
* `invoice()` call — rare, but possible if the user wedges multiple
|
|
130
|
+
* flows). The cross-user write is via storage directly, mirroring the
|
|
131
|
+
* pattern in `access-control.ts`.
|
|
132
|
+
*/
|
|
133
|
+
export const persistWaiverForUser = async (storage, ctx, userId, version, locale) => {
|
|
134
|
+
const key = botStorageKey(ctx, userId);
|
|
135
|
+
const full = ((await storage.get(key)) ?? {});
|
|
136
|
+
const record = {
|
|
137
|
+
at: Date.now(),
|
|
138
|
+
version,
|
|
139
|
+
locale,
|
|
140
|
+
};
|
|
141
|
+
full.pay = { ...(full.pay ?? {}), waiver: record };
|
|
142
|
+
await storage.set(key, full);
|
|
143
|
+
return record;
|
|
144
|
+
};
|
|
145
|
+
//# sourceMappingURL=waiver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"waiver.js","sourceRoot":"","sources":["../../../src/bot/payments/waiver.ts"],"names":[],"mappings":"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"}
|