Реалізація Invoicing (генерація рахунків) для SaaS-застосунку

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Послуги, які ми пропонуємо
Показано 1 з 1Усі 2062 послуг
Реалізація Invoicing (генерація рахунків) для SaaS-застосунку
Середній
~3-5 днів
Часті запитання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1360
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1251
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    957
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1188
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    929
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Розробка веб-сайту для компанії ФІКСПЕР
    947

Типова ситуація: B2B SaaS-продукт зростає на 20% на місяць, але виставлення рахунків досі відбувається вручну. Менеджери витрачають до 15 хвилин на кожен рахунок — копіюють дані, вставляють у Word, гублять суми, помиляються в ІПН. Клієнти скаржаться на затримки в 2-3 дні, бухгалтерія витрачає години на звірку. Автоматизація інвойсингу скорочує цей процес до 30 секунд — у 30 разів швидше, заощаджуючи до 10 000 доларів на рік для компанії з 500 рахунками щомісяця. Ми реалізуємо повний цикл: від налаштування Stripe до генерації кастомних PDF зі зберіганням у S3 та синхронізацією з 1С.

У SaaS-бізнесі з сотнями клієнтів та підписок у різних валютах ручне виставлення рахунків стає вузьким місцем. Кожна помилка в реквізитах або податках може коштувати тисяч доларів. Ми бачили проєкти, де 15% рахунків містили помилки — після автоматизації цей показник упав до 0.5%. «Автоматизація інвойсингу заощадила нам 40 годин на місяць і скоротила прострочення на 30%» — фінансовий директор одного з клієнтів. Правильне налаштування Stripe Invoicing та генерація власних PDF дозволяють не лише прискорити процес, але й підвищити довіру клієнтів: рахунки виглядають професійно та містять коректні дані.

Наша команда реалізувала інвойсинг для 30+ SaaS-проєктів: від стартапів до enterprise. Ми знаємо всі підводні камені: від tax ID до часових зон. Якщо ви зіткнулися зі зростанням обсягу рахунків — отримайте консультацію інженера.

Чому автоматизація інвойсингу критична для SaaS?

З кожним новим клієнтом зростає навантаження на бухгалтерію. Помилки в реквізитах, податках та валютах призводять до затримок оплат і втрати довіри. Автоматизований інвойсинг виключає людські помилки при формуванні рахунків, прискорює виставлення до секунд після оплати підписки, підтримує різні валюти (USD, EUR, RUB) та податкові режими (ПДВ, VAT, US sales tax), а також генерує PDF-рахунки з кастомним брендингом для кожного клієнта. Інтеграція з бухгалтерією дозволяє автоматично синхронізувати дані з 1С та іншими системами.

Проблеми, які вирішуємо

  1. Податкова неоднозначність — Stripe підтримує tax ID (ІПН для RU, VAT для EU), але вимагає правильної конфігурації. Ми налаштовуємо автоматичне визначення tax rate за країною клієнта.
  2. Кастомний брендинг — стандартні рахунки Stripe рідко влаштовують клієнтів. Ми створюємо власні PDF-шаблони через @react-pdf/renderer, повністю контролюючи дизайн та розташування полів.
  3. Зберігання та доставка — PDF-рахунки мають бути доступні з особистого кабінету та надсилатися на email. Використовуємо S3 з попередньо підписаними URL та SendGrid для відправки.
  4. Синхронізація з бухгалтерією — через webhook Stripe або власну генерацію передаємо дані в 1С та інші системи.

Як гарантувати точність податків в інвойсах?

Податкові правила відрізняються за країнами та штатами. Stripe дозволяє задавати tax rates глобально, але для складних сценаріїв (наприклад, EU Intra-community VAT) потрібна додаткова логіка. Ми використовуємо Stripe Tax API для автоматичного розрахунку податків на основі адреси клієнта та типу продукту. У випадку власних PDF, податки обчислюються на стороні сервера з використанням перевірених бібліотек. Тестування на реальних сценаріях (пісочниця Stripe) гарантує, що кожна транзакція пройде без блокувань.

Як ми реалізували інвойсинг для SaaS-стартапу?

Клієнт — американський SaaS для управління фрілансерами. Вимагалося виставляти рахунки в USD та EUR, з логотипом клієнта, термінами оплати 15/30 днів, і автоматично зберігати PDF у S3. Обрали гібрид: підписки через Stripe Invoicing, а генерацію PDF — через React PDF з власним компонентом.

Приклад налаштування клієнта в Stripe
// Настройка Customer с реквизитами для инвойсов
const customer = await stripe.customers.create({
  email: '[email protected]',
  name: 'Acme Corp',
  address: {
    line1: 'ул. Ленина, 1',
    city: 'Москва',
    country: 'RU',
    postal_code: '101000',
  },
  tax_ids: [{
    type: 'ru_inn',
    value: '7727563778',
  }],
  metadata: { tenantId },
});

// Кастомный брендинг через Stripe Dashboard:
// Settings → Branding → Logo, colors, footer text
// Обновление реквизитов клиентом
export async function updateBillingDetails(
  tenantId: string,
  data: BillingDetailsInput
): Promise<void> {
  const subscription = await db.subscription.findUnique({
    where: { tenantId }
  });

  await stripe.customers.update(subscription!.stripeCustomerId, {
    name: data.companyName,
    email: data.billingEmail,
    address: {
      line1: data.address,
      city: data.city,
      country: data.country,
      postal_code: data.postalCode,
    },
  });

  // Tax ID (ИНН для RU, VAT для EU)
  if (data.taxId) {
    // Сначала удаляем старые tax IDs
    const existingCustomer = await stripe.customers.retrieve(
      subscription!.stripeCustomerId,
      { expand: ['tax_ids'] }
    ) as Stripe.Customer;

    for (const taxId of (existingCustomer.tax_ids as Stripe.ApiList<Stripe.TaxId>).data) {
      await stripe.customers.deleteTaxId(subscription!.stripeCustomerId, taxId.id);
    }

    // Добавляем новый
    await stripe.customers.createTaxId(subscription!.stripeCustomerId, {
      type: data.taxIdType as Stripe.TaxIdCreateParams.Type,
      value: data.taxId,
    });
  }
}

Генерація PDF через React PDF з кешуванням у S3: перший запит генерує та зберігає, наступні — віддають готовий файл. Це знижує навантаження та прискорює віддачу.

// npm install @react-pdf/renderer
import { pdf } from '@react-pdf/renderer';
import { InvoicePDF } from '@/components/pdf/InvoicePDF';

export async function generateInvoicePDF(invoiceId: string): Promise<Buffer> {
  const invoice = await db.invoice.findUnique({
    where: { id: invoiceId },
    include: {
      tenant: { include: { branding: true } },
      lineItems: true,
    }
  });

  const pdfStream = await pdf(
    <InvoicePDF invoice={invoice!} />
  ).toBuffer();

  return pdfStream;
}

// Сохраняем в S3 и возвращаем URL
export async function getInvoicePdfUrl(invoiceId: string): Promise<string> {
  const key = `invoices/${invoiceId}.pdf`;

  // Проверяем, уже создан ли
  try {
    await s3.headObject({ Bucket: process.env.AWS_BUCKET!, Key: key }).promise();
    return `https://${process.env.AWS_BUCKET}.s3.amazonaws.com/${key}`;
  } catch {
    // Не найден — генерируем
  }

  const pdfBuffer = await generateInvoicePDF(invoiceId);
  await s3.putObject({
    Bucket: process.env.AWS_BUCKET!,
    Key: key,
    Body: pdfBuffer,
    ContentType: 'application/pdf',
    ContentDisposition: `attachment; filename="invoice-${invoiceId}.pdf"`,
  }).promise();

  return `https://${process.env.AWS_BUCKET}.s3.amazonaws.com/${key}`;
}

Компонент рахунку детально опрацьований: таблиця з описом, кількістю, ціною, сумою; блоки «Від» і «Кому»; статус оплати. Всі дані беруться з БД, що гарантує актуальність.

// components/pdf/InvoicePDF.tsx
import {
  Document, Page, Text, View, Image, StyleSheet
} from '@react-pdf/renderer';

const styles = StyleSheet.create({
  page: { padding: 40, fontSize: 11, fontFamily: 'Helvetica' },
  header: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 40 },
  title: { fontSize: 24, fontWeight: 'bold' },
  table: { marginTop: 20 },
  tableRow: { flexDirection: 'row', borderBottom: '1px solid #eee', padding: '8px 0' },
  tableHeader: { backgroundColor: '#f5f5f5', fontWeight: 'bold' },
});

export function InvoicePDF({ invoice }: { invoice: Invoice }) {
  return (
    <Document>
      <Page size="A4" style={styles.page}>
        <View style={styles.header}>
          <View>
            {invoice.tenant.branding?.logoUrl && (
              <Image src={invoice.tenant.branding.logoUrl} style={{ height: 40 }} />
            )}
            <Text style={styles.title}>СЧЁТ-ФАКТУРА</Text>
            <Text>№ {invoice.number}</Text>
            <Text>от {invoice.createdAt.toLocaleDateString('ru-RU')}</Text>
          </View>
          <View style={{ alignItems: 'flex-end' }}>
            <Text style={{ fontSize: 18, color: '#6366f1' }}>
              {formatCurrency(invoice.total, invoice.currency)}
            </Text>
            <Text style={{ color: invoice.status === 'paid' ? '#22c55e' : '#f59e0b' }}>
              {invoice.status === 'paid' ? 'Оплачен' : 'Ожидает оплаты'}
            </Text>
          </View>
        </View>

        {/* Детали компании */}
        <View style={{ flexDirection: 'row', gap: 40, marginBottom: 30 }}>
          <View>
            <Text style={{ fontWeight: 'bold', marginBottom: 4 }}>От:</Text>
            <Text>{process.env.COMPANY_NAME}</Text>
            <Text>ИНН: {process.env.COMPANY_INN}</Text>
          </View>
          <View>
            <Text style={{ fontWeight: 'bold', marginBottom: 4 }}>Кому:</Text>
            <Text>{invoice.customerName}</Text>
            {invoice.taxId && <Text>ИНН: {invoice.taxId}</Text>}
          </View>
        </View>

        {/* Строки инвойса */}
        <View style={styles.table}>
          <View style={[styles.tableRow, styles.tableHeader]}>
            <Text style={{ flex: 3 }}>Описание</Text>
            <Text style={{ flex: 1, textAlign: 'right' }}>Кол-во</Text>
            <Text style={{ flex: 1, textAlign: 'right' }}>Цена</Text>
            <Text style={{ flex: 1, textAlign: 'right' }}>Сумма</Text>
          </View>
          {invoice.lineItems.map((item) => (
            <View key={item.id} style={styles.tableRow}>
              <Text style={{ flex: 3 }}>{item.description}</Text>
              <Text style={{ flex: 1, textAlign: 'right' }}>{item.quantity}</Text>
              <Text style={{ flex: 1, textAlign: 'right' }}>
                {formatCurrency(item.unitAmount, invoice.currency)}
              </Text>
              <Text style={{ flex: 1, textAlign: 'right' }}>
                {formatCurrency(item.amount, invoice.currency)}
              </Text>
            </View>
          ))}
        </View>

        {/* Итого */}
        <View style={{ alignItems: 'flex-end', marginTop: 20 }}>
          <Text style={{ fontSize: 14, fontWeight: 'bold' }}>
            Итого: {formatCurrency(invoice.total, invoice.currency)}
          </Text>
        </View>
      </Page>
    </Document>
  );
}

Як працює синхронізація через webhook?

Webhook invoice.finalized надсилає дані про новий рахунок. Ми upsert-имо запис у своїй БД і зберігаємо посилання на PDF від Stripe. Для власних рахунків — генеруємо PDF і завантажуємо в S3. Це дозволяє клієнту бачити рахунки в реальному часі.

case 'invoice.finalized': {
  const stripeInvoice = event.data.object as Stripe.Invoice;

  await db.invoice.upsert({
    where: { stripeInvoiceId: stripeInvoice.id },
    create: {
      stripeInvoiceId: stripeInvoice.id,
      tenantId: stripeInvoice.metadata.tenantId,
      number: stripeInvoice.number!,
      total: stripeInvoice.amount_due,
      currency: stripeInvoice.currency,
      status: 'open',
      pdfUrl: stripeInvoice.invoice_pdf,
      periodStart: new Date(stripeInvoice.period_start * 1000),
      periodEnd: new Date(stripeInvoice.period_end * 1000),
    },
    update: { status: 'open' }
  });
  break;
}

Що входить у роботу

Етап Деталі
Аналітика Аудит поточного процесу, узгодження формату рахунку, податкових вимог, валют
Проектування Архітектура: вибір між Stripe Invoicing та власною генерацією, схема webhook, модель даних
Реалізація Налаштування Stripe, розробка PDF-шаблону, webhook-хендлери, інтеграція з S3 та email
Тестування Перевірка всіх сценаріїв: створення, оновлення, скасування, повернення, різні валюти
Документація API-специфікація, інструкція для бухгалтера, керівництво з брендингу
Деплой Розгортання на продакшн, моніторинг помилок, налаштування алертів
Підтримка Гарантія 1 місяць (безкоштовне виправлення багів), опціонально — навчання команди

Типові складнощі та їх усунення

Помилка Наслідок Рішення
Неправильний tax ID Нерозрахунок податків Перевірка формату через Stripe validation
Відсутність кешування PDF Високе навантаження Збереження в S3 з попередньо підписаними URL
Ігнорування idempotency Дублювання рахунків Використання idempotency_key
Невраховані часові зони Невідповідність дат Встановлення часової зони клієнта

Терміни орієнтовно

  • Базове налаштування Stripe Invoicing — від 3 до 5 днів (без кастомних PDF).
  • Власна генерація PDF з S3 та webhook — від 7 до 10 днів.
  • Повний цикл з інтеграцією 1С — від 10 до 14 днів.

Вартість розраховується індивідуально після аудиту — напишіть нам для консультації.

Наш досвід — 5+ років у SaaS-розробці, понад 30 успішних проєктів з інвойсингом. Гарантуємо коректне налаштування та підтримку. Зв'яжіться з нами, щоб обговорити ваш проєкт та отримати безкоштовну консультацію.

Розробка SaaS-платформ

Ми знаємо цей біль напам'ять. Запускаєш MVP з авторизацією та підпискою, а через півроку впираєшся в архітектурні рішення, які не можна відкотити без переписування половини коду. Multi-tenancy, білінг, аудит логів, feature flags — кожен блок вимагає попереднього проектування. Ціна помилки при масштабуванні сягає десятків людино-місяців, а вартість рефакторингу архітектури після запуску — 500 000–1 000 000 грн.

За 8 років роботи над SaaS-продуктами ми перевірили на практиці, які рішення працюють, а які перетворюють підтримку на пекло. Нижче — архітектурні підходи, які використовуємо самі та рекомендуємо клієнтам.

Як забезпечити масштабованість SaaS-платформи?

Як ми будуємо multi-tenancy: ізоляція без оверхеду

Перше, що вирішуємо — схема розділення даних. Shared schema (tenant_id на кожній таблиці) — наш стандартний вибір для більшості проектів. Всі орендарі в одній базі, міграції застосовуються разом, операційна складність мінімальна. В Laravel реалізуємо через Global Scope:

protected static function booted(): void
{
    static::addGlobalScope('tenant', function (Builder $builder) {
        $builder->where('tenant_id', TenantContext::current()->id);
    });
}

Глобальний скоп — лише перший рівень захисту. Обов'язково додаємо Row-Level Security в PostgreSQL — вона спрацює, якщо додаток пропустить WHERE tenant_id = ?:

ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON orders
    USING (tenant_id = current_setting('app.tenant_id')::uuid);

Для enterprise-клієнтів, яким потрібна фізична ізоляція, виділяємо окрему базу. Такий гібридний підхід (shared + dedicated) використовується в 80% зрілих SaaS: базовий продукт на shared schema, преміум — на окремій інстанції. Ми впроваджуємо його з першого спринту, щоб не переписувати логіку пізніше.

Модель multi-tenancy описана у відкритих джерелах — рекомендуємо ознайомитися для розуміння компромісів.

Чому білінг — найнедооціненіший блок

Upgrade посеред розрахункового періоду, downgrade з відкладеним набранням чинності, прострочений trial, failed payment з grace period — Stripe Billing закриває 90% сценаріїв з коробки. Обов'язково обробляємо вебхуки (customer.subscription.updated, invoice.payment_failed) з ідемпотентним ключем — без нього retry на клієнті призведе до подвійного списання.

Для локальних ринків — ЮKassa або Tinkoff recurring. API менш зручні, але покривають вимоги законодавства.

Порівняння: використання Stripe замість самописного білінгу скорочує час розробки підписної логіки у 2,5 рази, а кількість багів — на 80% (дані з наших проектів). Це економить від 200 000 грн на етапі MVP.

Onboarding: як не втратити користувача до aha-moment

Технічно onboarding — це wizard з persistent станом, який не можна випадково пропустити. Таблиця onboarding_steps з чек-листом, middleware редиректить на незавершений крок. Після завершення — флаг в user settings, middleware вимикається.

Критичний нюанс: показуйте прогрес реального продукту, не абстрактні кроки. «Створіть перший звіт» замість «Завершіть крок 3 з 5». Ми використовуємо drip-кампанії через Customer.io або власну чергу з відкладеними jobs — якщо користувач виконав ключову дію, наступний лист не надсилається.

Feature flags та управління доступом

SaaS з тарифами вимагає гранулярного контролю. Не робіть if ($user->plan === 'pro') по всьому коду — через місяць він стане непідтримуваним. Натомість:

  • Backend: Gate + Policy з перевіркою через таблицю features, пов'язану з планами.
  • Frontend: контекст з флагами, що завантажується при ініціалізації додатку.
  • Open-source інструменти: Unleash або Growthbook — UI для A/B-тестів та rollout.

Як захистити API від агресивних клієнтів

Rate limiting — must-have для публічного API. Один клієнт може покласти всіх інших. В Laravel використовуємо Redis з sliding window counter:

Тариф Ліміт Заголовки у відповіді
Free 100 req/h X-RateLimit-Limit: 100
Pro 1 000 req/h X-RateLimit-Limit: 1000
Enterprise 10 000 req/h X-RateLimit-Limit: 10000

Кожна відповідь містить X-RateLimit-Remaining та X-RateLimit-Reset — клієнти розраховують на ці заголовки.

Аудит-логи та моніторинг: що, хто і коли

Без аудит-логу неможливо дізнатися, хто видалив проект або коли змінилися налаштування білінгу. Таблиця audit_logs з індексами по (tenant_id, created_at) та (subject_type, subject_id). В Laravel — Observer'и на ключових моделях.

Приклад реалізації Observer для Model
class OrderObserver
{
    public function created(Order $order): void
    {
        AuditLog::create([
            'tenant_id' => $order->tenant_id,
            'user_id' => auth()->id(),
            'action' => 'created',
            'subject_type' => Order::class,
            'subject_id' => $order->id,
        ]);
    }
}

Моніторинг: Sentry для exception tracking, Grafana + Prometheus для метрик. Алерти на error rate > 5% та response time p95 > 2s.

Як організувати безпеку та аудит у SaaS?

Безпеку будуємо на трьох рівнях: транспорт (HTTPS + HSTS), доступ (OAuth 2.0 / OpenID Connect з обов'язковим JWT refresh), дані (шифрування чутливих полів на рівні додатку). Аудит логів доповнюємо retention політикою — логи зберігаються 90 днів для free-тарифу і 365 для enterprise. Контроль доступу реалізуємо через RBAC з таблицею roles та permissions, інтегровану з Gate.

Помилка в налаштуванні CORS або відсутність CSRF-токенів на публічному API — найчастіша вразливість у SaaS, яку ми виправляємо на аудиті.

Досвід нашої команди та гарантії

Над SaaS-платформами працюють інженери з 8+ річним досвідом, за плечима — 50+ проектів, від стартапів до enterprise з мільйонними навантаженнями. Ми даємо гарантію на архітектурні рішення: якщо обраний підхід не масштабується — перепроектуємо за свій рахунок.

Що ви отримуєте

  • Документація архітектури: схеми, ERD, sequence diagrams.
  • Налаштування CI/CD (GitHub Actions / GitLab CI).
  • Доступи до репозиторію, стейджингу та продакшену.
  • Навчання команди: 2–3 сесії з код-рев'ю та runbook.
  • Post-launch підтримка 1 місяць.
  • Гарантія на архітектуру: безкоштовний рефакторинг, якщо рішення не проходить за навантаженням.

Процес роботи

  1. Discovery (1–2 тижні) — аудит поточної архітектури, скоуп MVP, пріоритети фіч.
  2. Проектування (1 тиждень) — вибір стеку, схема multi-tenancy, план білінгу.
  3. Розробка (4–12 тижнів) — спринти по 2 тижні, демо після кожного.
  4. Тестування (1 тиждень) — навантажувальні тести під target навантаження, security audit.
  5. Деплой та навчання (1 тиждень) — rollout, налаштування моніторингу, передача документації.

Орієнтири за термінами

Етап Термін
MVP (core features + auth + billing) 12–16 тижнів
Повноцінний продукт з admin panel 20–28 тижнів
Enterprise SaaS з multi-tenancy + audit 28–40 тижнів

Розробка SaaS-платформи — складний, але керований процес, якщо від самого початку закласти правильну архітектуру. Якщо ви плануєте запуск або масштабування продукту — замовте консультацію, щоб уникнути типових помилок. Вартість проекту розраховується індивідуально, зв'яжіться з нами — оцінимо за 2 дні. Замовте розробку під ключ: від проектування до деплою з гарантією архітектури. Перша година консультації — безкоштовно.