stack/apps/backend/src/lib/telegram.tsx
BilalG1 373fb48e7f
payment email templates (#1106)
<img width="553" height="471" alt="Screenshot 2026-01-14 at 12 16 36 PM"
src="https://github.com/user-attachments/assets/9f32473d-5294-4cf7-b527-0668fb04ae47"
/>
<img width="630" height="514" alt="Screenshot 2026-01-14 at 12 17 06 PM"
src="https://github.com/user-attachments/assets/b17f57f7-148d-4438-b337-df7516d1793e"
/>

<!--

Make sure you've read the CONTRIBUTING.md guidelines:
https://github.com/stack-auth/stack-auth/blob/dev/CONTRIBUTING.md

-->


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Expanded Stripe webhooks: handles invoice and one‑time/subscription
events, sends templated payment receipt and failure emails, posts
chargeback alerts to Telegram.
* Customer invoices API plus client and UI support for listing invoices;
backend stores invoice status, total, and hosted URL.

* **Tests**
* Added end‑to‑end tests for new webhook scenarios (receipts, failures,
chargebacks) and invoices API with email outbox checks.

* **Chores**
* Centralized Telegram helpers and improved formatting, validation, and
reliability.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Introduces end-to-end invoice visibility and payment notifications.
> 
> - **Emails:** Adds default `payment_receipt` and `payment_failed`
templates and sends them from Stripe webhooks for one-time and
subscription payments (skips non‑uncollectible failures); resolves
recipients for users/teams.
> - **Webhooks:** Expands handled events; upserts invoices on
`invoice.*`; stricter unknown-type handling; adds Telegram chargeback
alert; refactors init script Telegram sending.
> - **Data model:** Extends `SubscriptionInvoice` with `status`,
`amountTotal`, `hostedInvoiceUrl` and writes them via
`upsertStripeInvoice`.
> - **API/SDK/UI:** New paginated `GET
/payments/invoices/{customer_type}/{customer_id}`; client interface
(`listInvoices`, hooks) and template Payments panel render an invoices
table.
> - **Tests:** E2E for invoices access, webhook behaviors, and email
delivery.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
edc8fe5651. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
2026-01-20 18:45:01 -08:00

42 lines
1.3 KiB
TypeScript

import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env";
import { StackAssertionError, throwErr } from "@stackframe/stack-shared/dist/utils/errors";
const TELEGRAM_HOSTNAME = "api.telegram.org";
const TELEGRAM_ENDPOINT_PATH = "/sendMessage";
export type TelegramConfig = {
botToken: string,
chatId: string,
};
export function getTelegramConfig(chatChannel: "init-stack" | "chargebacks"): TelegramConfig | null {
const botToken = getEnvVariable("STACK_TELEGRAM_BOT_TOKEN", "");
const chatIdEnv = chatChannel === "init-stack" ? "STACK_TELEGRAM_CHAT_ID" : "STACK_TELEGRAM_CHAT_ID_CHARGEBACKS";
const chatId = getEnvVariable(chatIdEnv, "");
if (!botToken || !chatId) {
return null;
}
return { botToken, chatId };
}
export async function sendTelegramMessage(options: TelegramConfig & { message: string }): Promise<void> {
const response = await fetch(`https://${TELEGRAM_HOSTNAME}/bot${options.botToken}${TELEGRAM_ENDPOINT_PATH}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
chat_id: options.chatId,
text: options.message,
}),
});
if (!response.ok) {
const body = await response.text();
throw new StackAssertionError("Failed to send Telegram notification.", {
status: response.status,
body,
});
}
}