/** * Telegram event handlers: `pre_checkout_query`, `successful_payment`, * and the auto-installed `/paysupport` command. * * Two hot paths the handlers MUST get right: * * - `pre_checkout_query` has a **10 s hard deadline** at Telegram's * side. We answer synchronously after a single payload decode + * catalog lookup + xtr match check. No storage round-trips. * * - `successful_payment` is **idempotent by `telegram_payment_charge_id`**. * We claim the chargeId via a sentinel key (`pay:idempotency:{id}`) * using set-if-absent semantics; a duplicate delivery returns a * no-op. Telegram retries this event in some failure modes (see * `sendInvoice` docs ยง Tips), so this guard is load-bearing. * * `/paysupport` is mandated by the Bot Developer ToS ยง6.5 โ€” every bot * accepting payments must surface a way for users to request refunds. * We register the command and route users to `/settings โ†’ ๐Ÿ’Ž VIP` * where the refund flow lives (mirrors how `bot/menu` keeps every * user-facing flow under one slash command). */ import { SourcedError } from "../../offensive.js"; import type { BotPaymentCtx, BotPreCheckoutCtx } from "../ctx.js"; import type { PaymentsStores } from "./stores.js"; import type { BotPaymentsConfig, FulfillmentEvent, ProductCatalog, SessionLike } from "./types.js"; /** * Strict bot.api shape for this plugin. Composes with the canonical * `BotMessageCtx` / `BotPreCheckoutCtx` / `BotPaymentCtx` via their * `Api` generic, so a typo on a method name or wrong param shape is a * compile error here. */ type PaymentBotApi = { answerPreCheckoutQuery: (params: { pre_checkout_query_id: string; ok: boolean; error_message?: string; }) => Promise; editUserStarSubscription: (params: { user_id: number; telegram_payment_charge_id: string; is_canceled: boolean; }) => Promise; sendMessage: (params: { chat_id: number; message_thread_id?: number; text: string; reply_markup?: unknown; }) => Promise; }; /** * Pre-checkout event ctx (Telegram's 10s deadline). Strict bot.api; * everything else is structural. No session on this event scope. */ type PreCheckoutCtx = BotPreCheckoutCtx; /** * Successful-payment event ctx. Reads the raw `.payload` (snake_case * Telegram fields) because every downstream consumer already speaks * snake_case, and going through gramio's wrapper getters would mean * a second translation layer. See `bot/ctx.ts` ยง BotPaymentCtx for * the historical bug that produced this convention (mixing wrapper * camelCase with snake_case yields silent `undefined`). */ type MessageCtx = BotPaymentCtx; /** * Validate the in-flight purchase synchronously and ack within 10 s. * * - Currency must be `XTR` (we only sell digital goods; fiat would * have a `provider_token` and not reach this plugin). * - Payload must decode + match `ctx.from.id` (anti-tamper). * - Product must exist in the catalog. * - `total_amount` must match the catalog's declared xtr โ€” defense * against a stale invoice or a config edit between invoice send * and tap. * * On any failure: ack `ok: false` with a brief localized reason. The * user sees Telegram's "Payment failed" sheet with that reason. */ export declare const buildPreCheckoutHandler: (catalog: ProductCatalog) => (ctx: PreCheckoutCtx) => Promise; export type SuccessfulPaymentHandlerOptions = { stores: PaymentsStores; cfg: BotPaymentsConfig; catalog: ProductCatalog; /** * Map of productKey โ†’ callbacks fired AFTER fulfillment succeeds. * Key `"*"` is the catch-all, run on every fulfillment in addition * to the productKey-specific list. * * Sync return signature; the plugin always answers Telegram itself. * Handlers are fire-and-forget โ€” async work inside is OK but the * plugin doesn't await them. */ onFulfilled: ReadonlyMap void>>; }; /** * Process a `successful_payment` event. Idempotent on chargeId. * * On first delivery: * 1. Decode payload, look up product * 2. Persist `ChargeRecord` + idempotency sentinel + user index * 3. Mutate `ctx.session.pay.*` via `applyCharge` * 4. Auto-cancel any LOWER vip rung (tier upgrade case) * 5. Send user confirmation + emit onFulfilled hooks * * On duplicate delivery: returns silently. */ export declare const buildSuccessfulPaymentHandler: (opts: SuccessfulPaymentHandlerOptions) => (ctx: MessageCtx) => Promise; export { SourcedError }; //# sourceMappingURL=handlers.d.ts.map