Как мы создали облачный сервис выставления счетов с помощью искусственного интеллекта в SeaNotes, используя Stripe, Resend и Gradient
Введение
Любое SaaS-приложение неполно без функции выставления счетов и инвойсирования. Хотя Stripe упрощает выставление счетов, инвойсирование с вашим собственным брендингом по-прежнему является задачей. Мы разработали открытый набор для старта, который поможет вам запустить ваше собственное SaaS за считанные минуты, и он включает в себя функцию инвойсирования.
SeaNotes — это open-source SaaS стартовый комплект, который предоставляет вам готовую к производству основу для быстрого создания реальных приложений. В своей основе это полнофункциональное приложение для заметок, которое поставляется с предустановленными сервисами, необходимыми большинству SaaS продуктов: аутентификация с помощью NextAuth.js, выставление счетов с помощью Stripe, транзакционные электронные письма через Resend, загрузка файлов с DigitalOcean Spaces, база данных PostgreSQL на DigitalOcean, функции искусственного интеллекта на платформе DigitalOcean Gradient, и однощелевое развертывание на платформе DigitalOcean App.
Используя этот стартовый комплект, вы можете либо строить на его основе, либо создавать свои собственные приложения, используя его части. В этой статье вы научитесь строить систему выставления счетов с поддержкой ИИ, адаптированную под потребности вашего бренда, используя платформу DigitalOcean Gradient, Resend и Stripe.
Ключевые выводы
После прочтения этой статьи вы сможете:
- Поймите, как вы можете использовать платформу Gradient от DigitalOcean для создания сервиса выставления счетов с помощью функции серверного извлечения.
- Поймите, как вы можете расширить стартовый набор SeaNotes, чтобы создать функции с поддержкой ИИ, используя существующие интеграции (Stripe и Resend в этом примере).
Вот демонстрация услуги выставления счетов:
Предварительные требования
- Учетная запись DigitalOcean
- Рабочее знание Next.js
- Существующая функция выставления счетов (в этом примере мы будем использовать стартовый комплект SeaNotes, который уже имеет интеграцию с Stripe)
Как работает сервис выставления счетов
Сервис выставления счетов разработан для создания счетов с вашей собственной брендинговой идентичностью. Он использует следующие четыре уровня в организованной архитектуре микросервисов:
Когда кто-то нажимает кнопку «Отправить счет по электронной почте», вот что происходит:
- Слой интеграции выставления счетов: выполняет запрос к Stripe, чтобы получить детали подписки пользователя, информацию о плане и данные по выставлению счетов.
- Слой ИИ-сервиса: Принимает данные по выставлению счетов и отправляет их на платформу Gradient от DigitalOcean, которая возвращает HTML-счет.
- Слой генерации PDF: Принимает HTML-выставленный счет и использует Puppeteer для его преобразования в высококачественный PDF-документ. (Puppeteer — это библиотека Node.js, которая управляет безголовым Chrome, и мы используем ее для генерации PDF, поскольку она отображает веб-страницы точно так же, как это делает Chrome, что обеспечивает точные, стилизованные и идеальные по пикселям PDF-документы из HTML и CSS)
- Слой доставки электронной почты: Берет PDF-буфер и отправляет его в качестве вложения к электронному письму, используя API Resend.
Создание генератора счетов
Как показано на диаграмме выше, генератор счетов-фактур состоит из четырех основных компонентов:
- ИИ сервис
- Служба выставления счетов
- Генерация PDF
- Служба доставки электронной почты
Слои выставления счетов и доставки электронной почты уже существуют в стартовом наборе. Основным компонентом для генератора счетов является слой ИИ, и на следующем этапе мы увидим, как его построить.
Шаг 1 — Создание счета с безсерверной инференцией
Для создания счета мы будем использовать функцию безсерверного вывода на платформе DigitalOcean Gradient.
Почему безсерверное инференсирование?
Используя функцию безсерверного инференса DigitalOcean для этой демонстрации, мы получаем прямой доступ к API моделей от OpenAI, Anthropic и Llama. Это также позволяет нам использовать модели с открытым исходным кодом, не занимаясь инфраструктурой самостоятельно, и упрощает замену LLM через один API и систему выставления счетов, вместо того чтобы управлять несколькими ключами. Поскольку он использует безстатейный подход, он легко интегрируется с нашей логикой приложения, давая нам полный контроль над подсказками, при этом устраняя накладные расходы на масштабирование и управление доступом к моделям у различных поставщиков. Это дает нам полный контроль над тем, как мы соединяем AI модели с нашим приложением, и помогает нам сосредоточиться только на логике приложения AI и производительности модели.
Вот как мы настраиваем конечную точку из панели управления DigitalOcean:
- Войдите в панель управления DigitalOcean
-
Перейдите к платформе агента в левой боковой панели и нажмите на безсерверный вывод.
- Затем нажмите на Создать ключ доступа к модели, дайте ему имя и нажмите на
Создать. - Он даст вам ключ конечной точки, вставьте его в ваш
.envфайл подDO_INFERENCE_API_KEY=ваш-ключ-для-инференса
Шаг 2 — Обработка счета
Когда вы нажмете на кнопку Счет-фактура по электронной почте:
- Запрос попадает в серверлесс-сервис inference.
- Загружает модель llama3-8b-instruct
- Запускает модель с запросом счета
- Создает для вас счет фактуру
- Отправляет результат обратно в виде структурированного JSON
Код ниже выполняет вышеописанный процесс:
export class InvoiceService implements ConfigurableService { async generateInvoice(invoiceData: InvoiceData): Promise<GeneratedInvoice> { const prompt = this.buildInvoicePrompt(invoiceData); const response = await this.client.chat.completions.create({ model: 'llama3-8b-instruct', messages: [ { role: "system", content: "You are a professional invoice generator. Create beautiful, professional invoices in HTML format." }, { role: "user", content: prompt } ], max_tokens: 2000, temperature: 0.1 }); return this.parseInvoiceResponse(response); } }
Вот как invoiceData заполняется данными из Stripe:
// 1. Fetch plan details from Stripe API const billingService = await createBillingService(); const plans = await billingService.getProducts(); // Calls Stripe API // 2. Find the user's specific plan const selectedPlan = plans.find(plan => plan.priceId === process.env.STRIPE_PRO_PRICE_ID ); // 3. Assemble invoice data from Stripe + database const invoiceData = prepareInvoiceData(userDetails, selectedPlan, subscription.id);
Метод getProducts() вызывает API Stripe для получения:
- Детали продукта (название, описание, особенности)
- Информация о цене (сумма в сотых, период начисления)
- Права на функциональные возможности из продуктовых функций Stripe
Затем prepareInvoiceData() объединяет эти данные Stripe с информацией о пользователе из базы данных, чтобы создать полный объект invoiceData, который отправляется на серверный конечный пункт.
Вы можете найти весь код в файле `vincoiceservice.ts`.
Что происходит дальше, так это то, что конечная точка возвращает JSON-ответ с:
- HTML: Профессиональная разметка счета с стилизацией и брендингом компании
- Текст: Обычная текстовая версия для резервных копий электронной почты и доступности
- Тема: Тема электронной почты для автоматической доставки
Вот пример того, как выглядит JSON-ответ:
{ "html": "<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Invoice - INV-20241201-0001</title><style>* { box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif; margin: 0; padding: 10px; background-color: #f5f5f5; line-height: 1.6; } .container { max-width: 600px; margin: 0 auto; background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); overflow: hidden; } .header { background: #0061EB; color: white; padding: 30px 20px; text-align: center; } .header h1 { margin: 0 0 10px 0; font-size: 28px; font-weight: 600; } .header h2 { margin: 0; font-size: 20px; font-weight: 400; opacity: 0.9; } .content { padding: 30px 20px; } .invoice-details { display: flex; flex-direction: column; gap: 20px; margin-bottom: 30px; } @media (min-width: 600px) { .invoice-details { flex-direction: row; justify-content: space-between; } } .customer-info, .invoice-info { flex: 1; } .invoice-info { text-align: left; } @media (min-width: 600px) { .invoice-info { text-align: right; } } .customer-info h3, .invoice-info h3 { margin: 0 0 10px 0; font-size: 16px; color: #333; } .customer-info p, .invoice-info p { margin: 0; font-size: 14px; color: #666; } .item { border-bottom: 1px solid #eee; padding: 20px 0; } .item h3 { margin: 0 0 10px 0; font-size: 18px; color: #333; } .item p { margin: 0 0 15px 0; color: #666; } .total { font-size: 18px; font-weight: bold; margin-top: 20px; padding-top: 20px; border-top: 2px solid #0061EB; color: #333; } .features { margin-top: 15px; } .features strong { display: block; margin-bottom: 8px; color: #333; } .features li { margin-bottom: 4px; } .support-section { margin-top: 30px; text-align: center; padding: 20px; background-color: #f8f9fa; border-radius: 8px; } .support-section p { margin: 0 0 15px 0; color: #666; font-size: 14px; } .contact-button { display: inline-block; background: #0061EB; color: white !important; text-decoration: none; padding: 12px 24px; border-radius: 6px; font-weight: 500; font-size: 14px; transition: background-color 0.2s; min-width: 140px; text-align: center; border: none; cursor: pointer; } .contact-button:hover { background: #0051c3; } .contact-button:active { background: #004094; } .footer { margin-top: 20px; text-align: center; color: #666; font-size: 12px; } .footer p { margin: 5px 0; } @media (max-width: 480px) { body { padding: 5px; } .header { padding: 20px 15px; } .header h1 { font-size: 24px; } .header h2 { font-size: 18px; } .content { padding: 20px 15px; } .contact-button { display: block !important; width: 100% !important; text-align: center !important; margin-top: 10px !important; box-sizing: border-box !important; } .support-section { padding: 15px !important; } }</style></head><body><div class="container"><div class="header"><h1>SeaNotes</h1><h2>Invoice</h2></div><div class="content"><div class="invoice-details"><div class="customer-info"><h3>Bill To:</h3><p><strong>John Doe</strong><br>john@example.com</p></div><div class="invoice-info"><h3>Invoice Details:</h3><p><strong>Invoice #:</strong> INV-20241201-0001<br><strong>Date:</strong> 8/25/2025<br><strong>Subscription ID:</strong> sub_123</p></div></div><div class="item"><h3>Pro Plan</h3><p>Advanced features for power users</p><div class="features"><strong>Features included:</strong><ul><li>Unlimited notes</li><li>Real-time sync</li><li>Priority support</li></ul></div><div class="total"><strong>Total: $12</strong><br><small>Billed monthly</div></div><div class="support-section"><p>Thank you for your subscription!</p><p>If you have any questions about this invoice, please contact our support team.</p><a href="mailto:support@seanotes.com" class="contact-button">Contact Support</a></div><div class="footer"><p>SeaNotes</p><p>This is an automatically generated invoice.</p></div></div></div></body></html>", "text": "INVOICE - INV-20241201-0001nnSeaNotesnInvoice Date: 8/25/2025nnBill To:nJohn Doenjohn@example.comnnSubscription ID: sub_123nnITEM:nPro PlannAdvanced features for power usersnnFeatures included:n- Unlimited notesn- Real-time syncn- Priority supportnnTOTAL: $12nBilled monthlynnThank you for your subscription!nIf you have any questions, please contact our support team at support@seanotes.com", "subject": "Invoice #INV-20241201-0001 - Pro Plan Subscription" }
Этот структурированный ответ затем передается службе генерации PDF для создания окончательного документа счета, который пользователи могут получить по электронной почте.
Шаг 3 — Понимание того, как генерируется PDF
Сервис PDF преобразует генерируемый ИИ HTML-накладную в профессиональный PDF-документ с помощью:
- Запуск Puppeteer с безголовым Chrome.
- Оборачивание сгенерированного ИИ HTML из предыдущего шага в индивидуальный стиль. Вы можете настроить эту часть в соответствии с вашим собственным брендингом и рекомендациями.
- Отображение страницы с правильным форматированием и в конечном итоге создание качественного PDF-вывода
Вот как работает реализация:
export class PDFService { async generateInvoicePDF(html: string): Promise<Buffer> { const browser = await this.getBrowser(); const page = await browser.newPage(); // Inject professional styling and responsive design const completeHTML = this.injectInvoiceStyles(html); await page.setContent(completeHTML, { waitUntil: ['networkidle0', 'domcontentloaded'], timeout: 30000 }); const pdfBuffer = await page.pdf({ format: 'A4', margin: { top: '20mm', right: '20mm', bottom: '20mm', left: '20mm' }, printBackground: true, deviceScaleFactor: 2 }); return Buffer.from(pdfBuffer); } }
Вы можете найти весь код на GitHub.
Этот PDF-буфер затем передается в службу повторной отправки электронной почты для доставки пользователям.
Шаг 4 — Доставка электронной почты с существующей интеграцией Resend
Заключительная часть заключается в том, чтобы сделать этот счет доступным по электронной почте, для чего мы будем использовать существующую интеграцию Resend, которая уже есть в комплекте для начинающих.
Код для интеграции электронной почты с Resend можно найти в файле `Generate-invoice/route.tsx`. Вот что делает сервис:
- Использует существующую интеграцию API повторной отправки
- Создает шаблоны электронной почты на базе React
- Прикрепляет PDF-буфер в виде multipart/mixed MIME и отправляет электронные письма.
Код ниже отвечает за отправку счета, сгенерированного ИИ, в формате PDF по электронной почте:
// Send invoice via email with PDF attachment const emailService = await createEmailService(); if (emailService.isEmailEnabled()) { // Prepare email attachments const attachments = []; if (pdfBuffer && pdfFilename) { attachments.push({ filename: pdfFilename, content: pdfBuffer, contentType: 'application/pdf' }); } await emailService.sendReactEmail( userDetails.email, generatedInvoice.subject, <InvoiceEmail invoiceHtml={generatedInvoice.html} customerName={userDetails.name} planName={selectedPlan.name} amount={selectedPlan.amount} invoiceNumber={invoiceData.invoiceNumber} fromEmail={serverConfig.Resend.fromEmail || 'support@seanotes.com'} />, attachments ); }
И вот как вы можете просто сделать ваше приложение с поддержкой ИИ.
Что дальше?
Эту статью можно рассматривать как:
- Учебное пособие о том, как создать собственный сервис выставления счетов.
- Учебник о том, как вы можете расширить существующий стартовый комплект для создания любых функций на базе ИИ. Поскольку мы используем безсерверное инференсирование, вам не нужно беспокоиться о создании нового агента, просто напишите свои подсказки и позвольте моделям выполнять работу за вас.
- Простой способ настроить сервис выставления счетов для вашего приложения. Для этого все, что вам нужно сделать, это следовать шагам, указанным в документации, и вы готовы начать.
Используя стартовый комплект в качестве основы, вы можете перейти от простых задач вывода к полноценным ИИ-агентам, которые обрабатывают более сложные, контекстно осведомленные рабочие процессы. Например:
- Агент службы поддержки, который отвечает на запросы, извлекая контекст из вашей базы знаний.
- Продавец-консультант, который составляет персонализированные email-рассылки, используя данные из предыдущей CRM.
- Контент-агент, который генерирует резюме или черновики блогов, при этом учитывая тон и рекомендации вашего бренда.
- Ассистент по данным, который анализирует загруженные файлы, хранит векторные представления в OpenSearch и отвечает на вопросы пользователей о них.
С комплектом для начинающих вы можете начать с малого, например, с генерации счетов, и развиваться в любые из этих случаев использования по мере достижения зрелости вашего продукта.
Вот некоторые ресурсы, связанные с комплектом для начинающих, которые могут помочь вам узнать больше:
- Живая демонстрация нашего первого стартового набора, SeaNotes
- Ссылка на код SeaNotes
- Блог объявления SeaNotes






Добавить комментарий