@adriangalilea/utils 0.18.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/README.md +3 -1
  2. package/dist/bot/access-control.d.ts.map +1 -1
  3. package/dist/bot/access-control.js +17 -1
  4. package/dist/bot/access-control.js.map +1 -1
  5. package/dist/bot/allow-list.d.ts +38 -0
  6. package/dist/bot/allow-list.d.ts.map +1 -0
  7. package/dist/bot/allow-list.js +41 -0
  8. package/dist/bot/allow-list.js.map +1 -0
  9. package/dist/bot/callbacks.d.ts +89 -0
  10. package/dist/bot/callbacks.d.ts.map +1 -0
  11. package/dist/bot/callbacks.js +128 -0
  12. package/dist/bot/callbacks.js.map +1 -0
  13. package/dist/bot/ctx.d.ts +219 -0
  14. package/dist/bot/ctx.d.ts.map +1 -0
  15. package/dist/bot/ctx.js +52 -0
  16. package/dist/bot/ctx.js.map +1 -0
  17. package/dist/bot/index.d.ts +4 -0
  18. package/dist/bot/index.d.ts.map +1 -1
  19. package/dist/bot/index.js +4 -0
  20. package/dist/bot/index.js.map +1 -1
  21. package/dist/bot/kit.d.ts +1 -1
  22. package/dist/bot/kit.d.ts.map +1 -1
  23. package/dist/bot/kit.js +4 -2
  24. package/dist/bot/kit.js.map +1 -1
  25. package/dist/bot/menu.d.ts +23 -1
  26. package/dist/bot/menu.d.ts.map +1 -1
  27. package/dist/bot/menu.js +30 -1
  28. package/dist/bot/menu.js.map +1 -1
  29. package/dist/bot/payments/callbacks.d.ts +28 -0
  30. package/dist/bot/payments/callbacks.d.ts.map +1 -0
  31. package/dist/bot/payments/callbacks.js +102 -0
  32. package/dist/bot/payments/callbacks.js.map +1 -0
  33. package/dist/bot/payments/commands.d.ts +26 -0
  34. package/dist/bot/payments/commands.d.ts.map +1 -0
  35. package/dist/bot/payments/commands.js +41 -0
  36. package/dist/bot/payments/commands.js.map +1 -0
  37. package/dist/bot/payments/config.d.ts +30 -0
  38. package/dist/bot/payments/config.d.ts.map +1 -0
  39. package/dist/bot/payments/config.js +166 -0
  40. package/dist/bot/payments/config.js.map +1 -0
  41. package/dist/bot/payments/derive.d.ts +75 -0
  42. package/dist/bot/payments/derive.d.ts.map +1 -0
  43. package/dist/bot/payments/derive.js +151 -0
  44. package/dist/bot/payments/derive.js.map +1 -0
  45. package/dist/bot/payments/handlers.d.ts +113 -0
  46. package/dist/bot/payments/handlers.d.ts.map +1 -0
  47. package/dist/bot/payments/handlers.js +284 -0
  48. package/dist/bot/payments/handlers.js.map +1 -0
  49. package/dist/bot/payments/index.d.ts +17 -0
  50. package/dist/bot/payments/index.d.ts.map +1 -0
  51. package/dist/bot/payments/index.js +14 -0
  52. package/dist/bot/payments/index.js.map +1 -0
  53. package/dist/bot/payments/invoice.d.ts +101 -0
  54. package/dist/bot/payments/invoice.d.ts.map +1 -0
  55. package/dist/bot/payments/invoice.js +213 -0
  56. package/dist/bot/payments/invoice.js.map +1 -0
  57. package/dist/bot/payments/menu-item.d.ts +41 -0
  58. package/dist/bot/payments/menu-item.d.ts.map +1 -0
  59. package/dist/bot/payments/menu-item.js +259 -0
  60. package/dist/bot/payments/menu-item.js.map +1 -0
  61. package/dist/bot/payments/payload.d.ts +42 -0
  62. package/dist/bot/payments/payload.d.ts.map +1 -0
  63. package/dist/bot/payments/payload.js +69 -0
  64. package/dist/bot/payments/payload.js.map +1 -0
  65. package/dist/bot/payments/payouts.d.ts +88 -0
  66. package/dist/bot/payments/payouts.d.ts.map +1 -0
  67. package/dist/bot/payments/payouts.js +133 -0
  68. package/dist/bot/payments/payouts.js.map +1 -0
  69. package/dist/bot/payments/plugin.d.ts +271 -0
  70. package/dist/bot/payments/plugin.d.ts.map +1 -0
  71. package/dist/bot/payments/plugin.js +141 -0
  72. package/dist/bot/payments/plugin.js.map +1 -0
  73. package/dist/bot/payments/refund.d.ts +124 -0
  74. package/dist/bot/payments/refund.d.ts.map +1 -0
  75. package/dist/bot/payments/refund.js +308 -0
  76. package/dist/bot/payments/refund.js.map +1 -0
  77. package/dist/bot/payments/schemas.d.ts +58 -0
  78. package/dist/bot/payments/schemas.d.ts.map +1 -0
  79. package/dist/bot/payments/schemas.js +55 -0
  80. package/dist/bot/payments/schemas.js.map +1 -0
  81. package/dist/bot/payments/state.d.ts +90 -0
  82. package/dist/bot/payments/state.d.ts.map +1 -0
  83. package/dist/bot/payments/state.js +149 -0
  84. package/dist/bot/payments/state.js.map +1 -0
  85. package/dist/bot/payments/stores.d.ts +44 -0
  86. package/dist/bot/payments/stores.d.ts.map +1 -0
  87. package/dist/bot/payments/stores.js +39 -0
  88. package/dist/bot/payments/stores.js.map +1 -0
  89. package/dist/bot/payments/types.d.ts +299 -0
  90. package/dist/bot/payments/types.d.ts.map +1 -0
  91. package/dist/bot/payments/types.js +39 -0
  92. package/dist/bot/payments/types.js.map +1 -0
  93. package/dist/bot/payments/waiver.d.ts +81 -0
  94. package/dist/bot/payments/waiver.d.ts.map +1 -0
  95. package/dist/bot/payments/waiver.js +145 -0
  96. package/dist/bot/payments/waiver.js.map +1 -0
  97. package/dist/bot/storage.d.ts +154 -0
  98. package/dist/bot/storage.d.ts.map +1 -0
  99. package/dist/bot/storage.js +222 -0
  100. package/dist/bot/storage.js.map +1 -0
  101. package/package.json +28 -3
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Telegram event handlers: `pre_checkout_query`, `successful_payment`,
3
+ * and the auto-installed `/paysupport` command.
4
+ *
5
+ * Two hot paths the handlers MUST get right:
6
+ *
7
+ * - `pre_checkout_query` has a **10 s hard deadline** at Telegram's
8
+ * side. We answer synchronously after a single payload decode +
9
+ * catalog lookup + xtr match check. No storage round-trips.
10
+ *
11
+ * - `successful_payment` is **idempotent by `telegram_payment_charge_id`**.
12
+ * We claim the chargeId via a sentinel key (`pay:idempotency:{id}`)
13
+ * using set-if-absent semantics; a duplicate delivery returns a
14
+ * no-op. Telegram retries this event in some failure modes (see
15
+ * `sendInvoice` docs § Tips), so this guard is load-bearing.
16
+ *
17
+ * `/paysupport` is mandated by the Bot Developer ToS §6.5 — every bot
18
+ * accepting payments must surface a way for users to request refunds.
19
+ * We register the command and route users to `/settings → 💎 VIP`
20
+ * where the refund flow lives (mirrors how `bot/menu` keeps every
21
+ * user-facing flow under one slash command).
22
+ */
23
+ import { SourcedError } from "../../offensive.js";
24
+ import type { BotPaymentCtx, BotPreCheckoutCtx } from "../ctx.js";
25
+ import type { PaymentsStores } from "./stores.js";
26
+ import type { BotPaymentsConfig, FulfillmentEvent, PaymentsSession, ProductCatalog } from "./types.js";
27
+ /**
28
+ * Strict bot.api shape for this plugin. Composes with the canonical
29
+ * `BotMessageCtx` / `BotPreCheckoutCtx` / `BotPaymentCtx` via their
30
+ * `Api` generic, so a typo on a method name or wrong param shape is a
31
+ * compile error here.
32
+ */
33
+ type PaymentBotApi = {
34
+ answerPreCheckoutQuery: (params: {
35
+ pre_checkout_query_id: string;
36
+ ok: boolean;
37
+ error_message?: string;
38
+ }) => Promise<unknown>;
39
+ editUserStarSubscription: (params: {
40
+ user_id: number;
41
+ telegram_payment_charge_id: string;
42
+ is_canceled: boolean;
43
+ }) => Promise<unknown>;
44
+ sendMessage: (params: {
45
+ chat_id: number;
46
+ message_thread_id?: number;
47
+ text: string;
48
+ reply_markup?: unknown;
49
+ }) => Promise<unknown>;
50
+ };
51
+ type SessionLike = {
52
+ pay?: PaymentsSession;
53
+ language?: string;
54
+ };
55
+ /**
56
+ * Pre-checkout event ctx (Telegram's 10s deadline). Strict bot.api;
57
+ * everything else is structural. No session on this event scope.
58
+ */
59
+ type PreCheckoutCtx = BotPreCheckoutCtx<PaymentBotApi>;
60
+ /**
61
+ * Successful-payment event ctx. Reads the raw `.payload` (snake_case
62
+ * Telegram fields) because every downstream consumer already speaks
63
+ * snake_case, and going through gramio's wrapper getters would mean
64
+ * a second translation layer. See `bot/ctx.ts` § BotPaymentCtx for
65
+ * the historical bug that produced this convention (mixing wrapper
66
+ * camelCase with snake_case yields silent `undefined`).
67
+ */
68
+ type MessageCtx = BotPaymentCtx<SessionLike, PaymentBotApi>;
69
+ /**
70
+ * Validate the in-flight purchase synchronously and ack within 10 s.
71
+ *
72
+ * - Currency must be `XTR` (we only sell digital goods; fiat would
73
+ * have a `provider_token` and not reach this plugin).
74
+ * - Payload must decode + match `ctx.from.id` (anti-tamper).
75
+ * - Product must exist in the catalog.
76
+ * - `total_amount` must match the catalog's declared xtr — defense
77
+ * against a stale invoice or a config edit between invoice send
78
+ * and tap.
79
+ *
80
+ * On any failure: ack `ok: false` with a brief localized reason. The
81
+ * user sees Telegram's "Payment failed" sheet with that reason.
82
+ */
83
+ export declare const buildPreCheckoutHandler: (catalog: ProductCatalog) => (ctx: PreCheckoutCtx) => Promise<void>;
84
+ export type SuccessfulPaymentHandlerOptions = {
85
+ stores: PaymentsStores;
86
+ cfg: BotPaymentsConfig<string>;
87
+ catalog: ProductCatalog;
88
+ /**
89
+ * Map of productKey → callbacks fired AFTER fulfillment succeeds.
90
+ * Key `"*"` is the catch-all, run on every fulfillment in addition
91
+ * to the productKey-specific list.
92
+ *
93
+ * Sync return signature; the plugin always answers Telegram itself.
94
+ * Handlers are fire-and-forget — async work inside is OK but the
95
+ * plugin doesn't await them.
96
+ */
97
+ onFulfilled: ReadonlyMap<string, ReadonlyArray<(event: FulfillmentEvent, ctx: MessageCtx) => void>>;
98
+ };
99
+ /**
100
+ * Process a `successful_payment` event. Idempotent on chargeId.
101
+ *
102
+ * On first delivery:
103
+ * 1. Decode payload, look up product
104
+ * 2. Persist `ChargeRecord` + idempotency sentinel + user index
105
+ * 3. Mutate `ctx.session.pay.*` via `applyCharge`
106
+ * 4. Auto-cancel any LOWER vip rung (tier upgrade case)
107
+ * 5. Send user confirmation + emit onFulfilled hooks
108
+ *
109
+ * On duplicate delivery: returns silently.
110
+ */
111
+ export declare const buildSuccessfulPaymentHandler: (opts: SuccessfulPaymentHandlerOptions) => (ctx: MessageCtx) => Promise<void>;
112
+ export { SourcedError };
113
+ //# sourceMappingURL=handlers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../../src/bot/payments/handlers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAS,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGzD,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAGlE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EACX,iBAAiB,EAGjB,gBAAgB,EAChB,eAAe,EAEf,cAAc,EAEd,MAAM,YAAY,CAAC;AAQpB;;;;;GAKG;AACH,KAAK,aAAa,GAAG;IACpB,sBAAsB,EAAE,CAAC,MAAM,EAAE;QAChC,qBAAqB,EAAE,MAAM,CAAC;QAC9B,EAAE,EAAE,OAAO,CAAC;QACZ,aAAa,CAAC,EAAE,MAAM,CAAC;KACvB,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;;;GAGG;AACH,KAAK,cAAc,GAAG,iBAAiB,CAAC,aAAa,CAAC,CAAC;AAEvD;;;;;;;GAOG;AACH,KAAK,UAAU,GAAG,aAAa,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC;AAU5D;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,uBAAuB,GAClC,SAAS,cAAc,MACjB,KAAK,cAAc,KAAG,OAAO,CAAC,IAAI,CA+DxC,CAAC;AAyCH,MAAM,MAAM,+BAA+B,GAAG;IAC7C,MAAM,EAAE,cAAc,CAAC;IACvB,GAAG,EAAE,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC/B,OAAO,EAAE,cAAc,CAAC;IACxB;;;;;;;;OAQG;IACH,WAAW,EAAE,WAAW,CACvB,MAAM,EACN,aAAa,CAAC,CAAC,KAAK,EAAE,gBAAgB,EAAE,GAAG,EAAE,UAAU,KAAK,IAAI,CAAC,CACjE,CAAC;CACF,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,6BAA6B,GACxC,MAAM,+BAA+B,MAC/B,KAAK,UAAU,KAAG,OAAO,CAAC,IAAI,CA2JpC,CAAC;AA+CH,OAAO,EAAE,YAAY,EAAE,CAAC"}
@@ -0,0 +1,284 @@
1
+ /**
2
+ * Telegram event handlers: `pre_checkout_query`, `successful_payment`,
3
+ * and the auto-installed `/paysupport` command.
4
+ *
5
+ * Two hot paths the handlers MUST get right:
6
+ *
7
+ * - `pre_checkout_query` has a **10 s hard deadline** at Telegram's
8
+ * side. We answer synchronously after a single payload decode +
9
+ * catalog lookup + xtr match check. No storage round-trips.
10
+ *
11
+ * - `successful_payment` is **idempotent by `telegram_payment_charge_id`**.
12
+ * We claim the chargeId via a sentinel key (`pay:idempotency:{id}`)
13
+ * using set-if-absent semantics; a duplicate delivery returns a
14
+ * no-op. Telegram retries this event in some failure modes (see
15
+ * `sendInvoice` docs § Tips), so this guard is load-bearing.
16
+ *
17
+ * `/paysupport` is mandated by the Bot Developer ToS §6.5 — every bot
18
+ * accepting payments must surface a way for users to request refunds.
19
+ * We register the command and route users to `/settings → 💎 VIP`
20
+ * where the refund flow lives (mirrors how `bot/menu` keeps every
21
+ * user-facing flow under one slash command).
22
+ */
23
+ import { panic, SourcedError } from "../../offensive.js";
24
+ import { say } from "../../say/index.js";
25
+ import { createLogger } from "../../universal/log.js";
26
+ import { decodePayload } from "./payload.js";
27
+ import { applyCharge } from "./state.js";
28
+ const log = createLogger("bot/payments");
29
+ const FALLBACK_LANG = "en";
30
+ const ctxLang = (ctx) => ctx.session?.language ?? FALLBACK_LANG;
31
+ // ─── pre_checkout_query handler ────────────────────────────────────
32
+ /**
33
+ * Validate the in-flight purchase synchronously and ack within 10 s.
34
+ *
35
+ * - Currency must be `XTR` (we only sell digital goods; fiat would
36
+ * have a `provider_token` and not reach this plugin).
37
+ * - Payload must decode + match `ctx.from.id` (anti-tamper).
38
+ * - Product must exist in the catalog.
39
+ * - `total_amount` must match the catalog's declared xtr — defense
40
+ * against a stale invoice or a config edit between invoice send
41
+ * and tap.
42
+ *
43
+ * On any failure: ack `ok: false` with a brief localized reason. The
44
+ * user sees Telegram's "Payment failed" sheet with that reason.
45
+ */
46
+ export const buildPreCheckoutHandler = (catalog) => async (ctx) => {
47
+ log.event(`pre_checkout_query received: user=${ctx.from.id} currency=${ctx.currency} amount=${ctx.totalAmount} payload="${ctx.invoicePayload}" id=${ctx.id}`);
48
+ const reject = async (reasonEn, reasonEs) => {
49
+ log.warn(`pre_checkout rejecting: ${reasonEn} (id=${ctx.id})`);
50
+ try {
51
+ await ctx.bot.api.answerPreCheckoutQuery({
52
+ pre_checkout_query_id: ctx.id,
53
+ ok: false,
54
+ // We don't have ctx.session on pre_checkout (it's a different
55
+ // event scope per gramio), so we can't read user lang. Default
56
+ // to a bilingual blurb that's intelligible to both en/es users.
57
+ error_message: `${reasonEn} · ${reasonEs}`,
58
+ });
59
+ }
60
+ catch (e) {
61
+ log.error(`answerPreCheckoutQuery(false) failed: ${e}`);
62
+ }
63
+ };
64
+ if (ctx.currency !== "XTR") {
65
+ await reject("Unsupported currency.", "Moneda no soportada.");
66
+ return;
67
+ }
68
+ const decoded = decodePayload(ctx.invoicePayload);
69
+ if (!decoded) {
70
+ await reject("Malformed invoice.", "Factura inválida.");
71
+ return;
72
+ }
73
+ if (decoded.userId !== ctx.from.id) {
74
+ await reject("User mismatch.", "Usuario no coincide.");
75
+ return;
76
+ }
77
+ const product = catalog.byKey.get(decoded.productKey);
78
+ if (!product) {
79
+ await reject("Unknown product.", "Producto desconocido.");
80
+ return;
81
+ }
82
+ if (product.xtr !== ctx.totalAmount) {
83
+ await reject(`Price changed (expected ${product.xtr}, got ${ctx.totalAmount}).`, "Precio cambiado.");
84
+ return;
85
+ }
86
+ log.success(`pre_checkout approving: product=${decoded.productKey} user=${ctx.from.id} amount=${ctx.totalAmount}`);
87
+ try {
88
+ await ctx.bot.api.answerPreCheckoutQuery({
89
+ pre_checkout_query_id: ctx.id,
90
+ ok: true,
91
+ });
92
+ }
93
+ catch (e) {
94
+ log.error(`answerPreCheckoutQuery(true) failed: ${e}`);
95
+ }
96
+ };
97
+ // ─── successful_payment handler ────────────────────────────────────
98
+ const buildChargeRecord = (product, userId, payment, waiverSnapshot) => {
99
+ const base = {
100
+ chargeId: payment.telegram_payment_charge_id,
101
+ userId,
102
+ productKey: product.id,
103
+ xtr: payment.total_amount,
104
+ receivedAt: Date.now(),
105
+ payload: payment.invoice_payload,
106
+ waiverSnapshot,
107
+ paysupportState: "none",
108
+ refundedAt: null,
109
+ payoutBatchId: null,
110
+ creditsGranted: 0,
111
+ };
112
+ if (product.id.startsWith("vip.")) {
113
+ const v = product;
114
+ return {
115
+ ...base,
116
+ vipRung: v.rank,
117
+ subscriptionExpiresAt: payment.subscription_expiration_date,
118
+ creditsGranted: v.creditsGranted,
119
+ };
120
+ }
121
+ if (product.id.startsWith("credits.")) {
122
+ const c = product;
123
+ return { ...base, creditsGranted: c.creditsGranted };
124
+ }
125
+ // perk
126
+ const p = product;
127
+ return { ...base, perkKey: p.key };
128
+ };
129
+ /**
130
+ * Process a `successful_payment` event. Idempotent on chargeId.
131
+ *
132
+ * On first delivery:
133
+ * 1. Decode payload, look up product
134
+ * 2. Persist `ChargeRecord` + idempotency sentinel + user index
135
+ * 3. Mutate `ctx.session.pay.*` via `applyCharge`
136
+ * 4. Auto-cancel any LOWER vip rung (tier upgrade case)
137
+ * 5. Send user confirmation + emit onFulfilled hooks
138
+ *
139
+ * On duplicate delivery: returns silently.
140
+ */
141
+ export const buildSuccessfulPaymentHandler = (opts) => async (ctx) => {
142
+ const payment = ctx.eventPayment?.payload;
143
+ if (!payment) {
144
+ log.warn("successful_payment handler fired but ctx.eventPayment.payload is undefined — gramio context shape changed?");
145
+ return;
146
+ }
147
+ if (payment.currency !== "XTR")
148
+ return; // fiat — not ours
149
+ log.event(`successful_payment received: chargeId=${payment.telegram_payment_charge_id} amount=${payment.total_amount} payload="${payment.invoice_payload}" subExpires=${payment.subscription_expiration_date ?? "n/a"}`);
150
+ const decoded = decodePayload(payment.invoice_payload);
151
+ if (!decoded) {
152
+ log.error(`successful_payment un-decodable payload — fulfillment skipped: chargeId=${payment.telegram_payment_charge_id} payload="${payment.invoice_payload}"`);
153
+ return;
154
+ }
155
+ const userId = ctx.from?.id ?? decoded.userId;
156
+ // Idempotency: claim chargeId via the sentinel store. Returns
157
+ // `true` on first claim, `false` if already claimed (duplicate
158
+ // delivery — Telegram retries `successful_payment` in some
159
+ // failure modes per the sendInvoice doc). NOT atomic under
160
+ // concurrent load (storage doesn't expose setNX); the window is
161
+ // bounded by Telegram's retry delay and acceptable for v1.
162
+ const claimed = await opts.stores.idempotency.claim(ctx, payment.telegram_payment_charge_id);
163
+ if (!claimed) {
164
+ log.info(`successful_payment idempotent no-op: chargeId=${payment.telegram_payment_charge_id} already fulfilled`);
165
+ return;
166
+ }
167
+ const product = opts.catalog.byKey.get(decoded.productKey);
168
+ if (!product) {
169
+ log.error(`successful_payment for unknown productKey "${decoded.productKey}" — config drift? chargeId=${payment.telegram_payment_charge_id}`);
170
+ return;
171
+ }
172
+ const waiverSnapshot = ctx.session.pay?.waiver;
173
+ if (!waiverSnapshot) {
174
+ // Should never happen: every invoice flow gates on waiver
175
+ // freshness. If we reach this point, our own gating logic has
176
+ // a hole. Scream so it gets found, but DON'T drop the user's
177
+ // money — finish fulfillment with a synthetic "missing
178
+ // snapshot" entry that's auditable. The `panic` is
179
+ // intentionally after the persistence path on purpose to keep
180
+ // the user whole; the next event will crash.
181
+ log.error(`CRITICAL: successful_payment without prior waiver — gate bypassed? userId=${userId} chargeId=${payment.telegram_payment_charge_id}`);
182
+ }
183
+ const charge = buildChargeRecord(product, userId, payment, waiverSnapshot ?? {
184
+ at: Date.now(),
185
+ version: opts.cfg.waiver.version,
186
+ locale: ctxLang(ctx),
187
+ });
188
+ // 1. Write the charge log + per-user index.
189
+ // (Idempotency sentinel was already claimed above.)
190
+ await opts.stores.charges.set(ctx, charge.chargeId, charge);
191
+ await opts.stores.userCharges(userId).prepend(ctx, charge.chargeId);
192
+ log.info(`charge persisted: chargeId=${charge.chargeId} product=${charge.productKey} user=${userId} xtr=${charge.xtr} creditsGranted=${charge.creditsGranted}`);
193
+ // 2. Apply to session (cache). Capture the previous vip chargeId
194
+ // FIRST so we can cancel its renewal below if this was an upgrade.
195
+ const previousVipChargeId = ctx.session.pay?.vip?.chargeId;
196
+ const previousVipRung = ctx.session.pay?.vip?.rung;
197
+ applyCharge(ctx.session, charge);
198
+ // 3. Tier-upgrade auto-cancel: when the user buys a HIGHER rung
199
+ // than their currently-active subscription, cancel the lower one's
200
+ // auto-renewal so they aren't double-billed at the next cycle. The
201
+ // lower rung keeps access until its period end — Telegram handles
202
+ // that silently because the higher rung's rank-based `atLeast`
203
+ // check already satisfies any vip.N check from now on.
204
+ if (charge.vipRung !== undefined &&
205
+ previousVipChargeId &&
206
+ previousVipChargeId !== charge.chargeId &&
207
+ previousVipRung !== undefined &&
208
+ previousVipRung < charge.vipRung) {
209
+ try {
210
+ await ctx.bot.api.editUserStarSubscription({
211
+ user_id: userId,
212
+ telegram_payment_charge_id: previousVipChargeId,
213
+ is_canceled: true,
214
+ });
215
+ log.info(`auto-canceled previous vip rung ${previousVipRung} on upgrade to ${charge.vipRung} (user=${userId})`);
216
+ }
217
+ catch (e) {
218
+ // Already canceled, expired, etc. Log; don't block fulfillment.
219
+ log.warn(`auto-cancel previous vip subscription failed (likely already canceled/expired): user=${userId} previousChargeId=${previousVipChargeId} err=${e instanceof Error ? e.message : String(e)}`);
220
+ }
221
+ }
222
+ // 4. User-facing confirmation
223
+ const lang = ctxLang(ctx);
224
+ await ctx.send(buildSuccessMessage(product, opts.cfg, lang));
225
+ // 5. Fire onFulfilled hooks (fire-and-forget)
226
+ const event = {
227
+ productKey: product.id,
228
+ userId,
229
+ chargeId: charge.chargeId,
230
+ xtr: charge.xtr,
231
+ receivedAt: charge.receivedAt,
232
+ };
233
+ const fireBucket = (key) => {
234
+ const arr = opts.onFulfilled.get(key);
235
+ if (!arr)
236
+ return;
237
+ for (const fn of arr) {
238
+ try {
239
+ fn(event, ctx);
240
+ }
241
+ catch (e) {
242
+ log.error(`onFulfilled handler for "${key}" threw: ${e}`);
243
+ }
244
+ }
245
+ };
246
+ fireBucket(product.id);
247
+ fireBucket("*");
248
+ log.success(`fulfillment complete: product=${product.id} user=${userId} chargeId=${charge.chargeId}`);
249
+ // 6. Sanity-fail AFTER everything has been persisted + the user
250
+ // has their confirmation — defensive, see CRITICAL log above.
251
+ if (!waiverSnapshot) {
252
+ panic("bot/payments: successful_payment fulfilled without a stored waiver " +
253
+ "snapshot. Gate logic has a hole. User was made whole; investigate.");
254
+ }
255
+ };
256
+ const buildSuccessMessage = (product, cfg, lang) => {
257
+ if (product.id.startsWith("vip.")) {
258
+ const v = product;
259
+ const name = v.name[lang] ??
260
+ v.name[FALLBACK_LANG] ??
261
+ "VIP";
262
+ return say({
263
+ en: `✅ Welcome to ${name}! Active for 30 days.`,
264
+ es: `✅ ¡Bienvenido a ${name}! Activo durante 30 días.`,
265
+ }, lang);
266
+ }
267
+ if (product.id.startsWith("credits.")) {
268
+ const c = product;
269
+ const unit = cfg.credits?.unit?.[lang] ??
270
+ "credits";
271
+ return say({
272
+ en: `✅ +${c.creditsGranted} ${unit} added to your balance.`,
273
+ es: `✅ +${c.creditsGranted} ${unit} añadidos a tu saldo.`,
274
+ }, lang);
275
+ }
276
+ const p = product;
277
+ const name = p.name[lang] ??
278
+ p.name[FALLBACK_LANG] ??
279
+ p.key;
280
+ return say({ en: `✅ Unlocked: ${name}.`, es: `✅ Desbloqueado: ${name}.` }, lang);
281
+ };
282
+ // ─── re-export SourcedError for catch sites ───────────────────────
283
+ export { SourcedError };
284
+ //# sourceMappingURL=handlers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handlers.js","sourceRoot":"","sources":["../../../src/bot/payments/handlers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * `bot/payments` — public exports.
3
+ *
4
+ * The factory `botPayments({...})` is the only thing most consumers
5
+ * need; everything else is exported for advanced wiring, tests, and
6
+ * type narrowing at consumer side.
7
+ *
8
+ * See `CLAUDE.md` in this folder for the design rationale + compliance
9
+ * memo (Spanish autónomo, Art. 103(m), Telegram Stars seller-of-record
10
+ * analysis, …).
11
+ */
12
+ export type { ExportInput, ExportResult, PayoutsApi, RecordPayoutInput, } from "./payouts.js";
13
+ export type { BotPaymentsOptions, BotPaymentsResult, PaymentsCtx, } from "./plugin.js";
14
+ export { botPayments } from "./plugin.js";
15
+ export type { AtLeastKey, BotPaymentsConfig, ChargeRecord, CreditsConfig, CreditsPack, DerivedPaymentsState, FulfillmentEvent, LegalConfig, PaymentsSession, PayoutRecord, PaysupportState, Perk, PerkState, PerksConfig, ProductKey, SubscriptionPeriod, TierKey, VipRung, VipState, WaiverConfig, WaiverRecord, } from "./types.js";
16
+ export { InsufficientCredits } from "./types.js";
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/bot/payments/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,YAAY,EACX,WAAW,EACX,YAAY,EACZ,UAAU,EACV,iBAAiB,GACjB,MAAM,cAAc,CAAC;AACtB,YAAY,EACX,kBAAkB,EAClB,iBAAiB,EACjB,WAAW,GACX,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,YAAY,EACX,UAAU,EACV,iBAAiB,EACjB,YAAY,EACZ,aAAa,EACb,WAAW,EACX,oBAAoB,EACpB,gBAAgB,EAChB,WAAW,EACX,eAAe,EACf,YAAY,EACZ,eAAe,EACf,IAAI,EACJ,SAAS,EACT,WAAW,EACX,UAAU,EACV,kBAAkB,EAClB,OAAO,EACP,OAAO,EACP,QAAQ,EACR,YAAY,EACZ,YAAY,GACZ,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * `bot/payments` — public exports.
3
+ *
4
+ * The factory `botPayments({...})` is the only thing most consumers
5
+ * need; everything else is exported for advanced wiring, tests, and
6
+ * type narrowing at consumer side.
7
+ *
8
+ * See `CLAUDE.md` in this folder for the design rationale + compliance
9
+ * memo (Spanish autónomo, Art. 103(m), Telegram Stars seller-of-record
10
+ * analysis, …).
11
+ */
12
+ export { botPayments } from "./plugin.js";
13
+ export { InsufficientCredits } from "./types.js";
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/bot/payments/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAaH,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAwB1C,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,101 @@
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 type { BotPaymentsConfig, CreditsPackResolved, PaymentsSession, PerkResolved, ProductCatalog, VipRungResolved } from "./types.js";
20
+ /**
21
+ * Structural shape we need from the calling ctx. Matches both
22
+ * `MessageContext` and `CallbackQueryContext` after gramio's mixins —
23
+ * we pull `chat.id` / `threadId` from either layout via short-circuits.
24
+ */
25
+ export type InvoiceCtx = {
26
+ bot: {
27
+ api: {
28
+ sendInvoice: (params: SendInvoiceParams) => Promise<unknown>;
29
+ };
30
+ };
31
+ from?: {
32
+ id: number;
33
+ };
34
+ chat?: {
35
+ id: number;
36
+ };
37
+ threadId?: number;
38
+ message?: {
39
+ threadId?: number;
40
+ chat?: {
41
+ id: number;
42
+ };
43
+ };
44
+ session?: {
45
+ pay?: PaymentsSession;
46
+ language?: string;
47
+ };
48
+ };
49
+ /** Subset of the Telegram `sendInvoice` schema this module needs. */
50
+ type SendInvoiceParams = {
51
+ chat_id: number;
52
+ message_thread_id?: number;
53
+ title: string;
54
+ description: string;
55
+ payload: string;
56
+ provider_token: string;
57
+ currency: "XTR";
58
+ prices: Array<{
59
+ label: string;
60
+ amount: number;
61
+ }>;
62
+ subscription_period?: number;
63
+ start_parameter?: string;
64
+ };
65
+ /**
66
+ * Issue a Telegram Stars invoice for `product`. Caller is expected to
67
+ * have already cleared waiver consent — `presentInvoice` (the public
68
+ * entry) handles that gate. This function does the wire call only.
69
+ *
70
+ * Wraps Telegram-side failures in a typed `SourcedError({ source:
71
+ * 'telegram', operation: 'sendInvoice' })` so the catch boundary in
72
+ * `plugin.ts` can render a localized fallback message.
73
+ */
74
+ export declare const sendInvoiceForProduct: (ctx: InvoiceCtx, cfg: BotPaymentsConfig<string>, product: VipRungResolved | CreditsPackResolved | PerkResolved, userId: number) => Promise<void>;
75
+ /**
76
+ * What the entry returned to mean. `'invoice_sent'` = the user got a
77
+ * Telegram payment sheet; `'waiver_prompt_sent'` = the user got the
78
+ * consent prompt and must tap before the invoice goes out;
79
+ * `'unknown_product'` = the key didn't resolve and we already told the
80
+ * user nothing happened.
81
+ */
82
+ export type PresentInvoiceResult = "invoice_sent" | "waiver_prompt_sent" | "unknown_product";
83
+ /**
84
+ * High-level entry for `ctx.payments.invoice(productKey)`.
85
+ *
86
+ * Validates the productKey against the catalog, gates on waiver
87
+ * freshness, and either:
88
+ *
89
+ * - sends the consent prompt (one-tap "✅ Consiento" button carrying
90
+ * the productKey, so the user resumes the purchase on tap), OR
91
+ * - calls `sendInvoiceForProduct` directly when consent is already
92
+ * fresh.
93
+ *
94
+ * Returns a discriminated tag so callers can branch on what the user
95
+ * saw — useful for `require()`-style helpers.
96
+ */
97
+ export declare const presentInvoice: (ctx: InvoiceCtx & {
98
+ send: (text: string, params?: object) => Promise<unknown>;
99
+ }, cfg: BotPaymentsConfig<string>, catalog: ProductCatalog, productKey: string) => Promise<PresentInvoiceResult>;
100
+ export {};
101
+ //# sourceMappingURL=invoice.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invoice.d.ts","sourceRoot":"","sources":["../../../src/bot/payments/invoice.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAMH,OAAO,KAAK,EACX,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,EACf,YAAY,EACZ,cAAc,EACd,eAAe,EACf,MAAM,YAAY,CAAC;AAWpB;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG;IACxB,GAAG,EAAE;QACJ,GAAG,EAAE;YACJ,WAAW,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;SAC7D,CAAC;KACF,CAAC;IACF,IAAI,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IACtB,IAAI,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE;YAAE,EAAE,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC;IACvD,OAAO,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,eAAe,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACvD,CAAC;AAEF,qEAAqE;AACrE,KAAK,iBAAiB,GAAG;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,KAAK,CAAC;IAChB,MAAM,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,eAAe,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AA8HF;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB,GACjC,KAAK,UAAU,EACf,KAAK,iBAAiB,CAAC,MAAM,CAAC,EAC9B,SAAS,eAAe,GAAG,mBAAmB,GAAG,YAAY,EAC7D,QAAQ,MAAM,KACZ,OAAO,CAAC,IAAI,CAsDd,CAAC;AAIF;;;;;;GAMG;AACH,MAAM,MAAM,oBAAoB,GAC7B,cAAc,GACd,oBAAoB,GACpB,iBAAiB,CAAC;AAErB;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,cAAc,GAC1B,KAAK,UAAU,GAAG;IACjB,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC1D,EACD,KAAK,iBAAiB,CAAC,MAAM,CAAC,EAC9B,SAAS,cAAc,EACvB,YAAY,MAAM,KAChB,OAAO,CAAC,oBAAoB,CAuC9B,CAAC"}