billing system
Документация по биллинговой системе
Обзор
Система выставления счетов в Gratheon позволяет пользователям подписываться на различные ценовые уровни (Бесплатный, Начальный, Профессиональный) и управлять своими подписками. Система реализована в трёх репозиториях:
- web-app: интерфейс пользователя для управления выставлением счетов.
- user-cycle: интеграция backend GraphQL API и Stripe.
- веб-сайт: общедоступная страница с ценами.
Архитектура
Компоненты системы
graph TB
subgraph "Frontend (web-app)"
UI[Account/Billing Page]
PricingPlans[PricingPlans Component]
BillingConfig[billing.ts Config]
end
subgraph "Backend (user-cycle)"
GraphQL[GraphQL Resolvers]
StripeAPI[Stripe Integration]
UserDB[(User Database)]
end
subgraph "External"
Stripe[Stripe Checkout]
StripeWebhook[Stripe Webhooks]
end
UI --> PricingPlans
PricingPlans --> BillingConfig
PricingPlans -->|createCheckoutSession| GraphQL
GraphQL --> StripeAPI
StripeAPI --> Stripe
Stripe -->|Payment Success| StripeWebhook
StripeWebhook --> GraphQL
GraphQL --> UserDB
Ценовые уровни
Бесплатно (любитель)
- Цена: Бесплатно навсегда.
- Особенности:
- До 3 ульев
- максимум 10 рамок на улей
- Обнаружение рабочих пчел
- Обнаружение королевы
- Публичное совместное использование улья
- Дневник лечения
- Ограничения:
- Низкоприоритетная обработка ИИ
- Сохранение изображения 1 год
Стартер
- Ежемесячно: 22 евро/месяц.
- Ежегодно: 12 евро в месяц (144 евро в год) - Сэкономьте 45 %
- Особенности:
- До 20 ульев
- 30 рамок на улей
- Анализ клеток
- Счет Варроа (нижняя доска)
- Планировщик размещения ульев
- Инспекционное управление
- ИИ-помощник пчеловода
- Ограничения:
- 1 учетная запись пользователя
- Сохранение изображения 2 года
Профессионал
- Ежемесячно: 55 евро/месяц.
- Ежегодно: 33 евро в месяц (396 евро в год) - Сэкономьте 40 %
- Особенности:
- До 150 ульев
- Неограниченное количество кадров
- Хранение телеметрии
- Анализ данных таймсерий
- Сравнение колоний
- Неограниченное количество проверок
- До 20 учетных записей пользователей
- Ограничения:
- Разрешение телеметрии 10 минут
- Сохранение изображения 3 года
- Статус: В разработке.
Гибкий (аддон)
- Цена: 100 евро единоразово (1000 жетонов, срок действия 1 год)
- Цель: функции инфраструктуры с оплатой по факту использования.
- Особенности:
- Обработка и хранение видео
- Ограничения скорости телеметрии IoT
- СМС оповещения
- Интеграция вебхуков
- Дополнительная емкость за пределами уровня
- Статус: В разработке (не отображается при выборе платежа web-app).
Предприятие
- Цена: индивидуальная цена.
- Особенности:
- Пользовательские интеграции
- Локальное развертывание
- Приоритетная поддержка 24/7
- Повышенная безопасность
- Неограниченный масштаб
- Контакт: Enterprise@gratheon.com
Детали реализации
Интерфейс (web-app)
Конфигурация (src/config/billing.ts)
Определяет все уровни выставления счетов с помощью:
- Названия и цвета уровней
- Ежемесячные/годовые цены.
- Проценты экономии
- Идентификаторы Stripe price
export const BILLING_TIERS = {
free: { name: 'Free', color: '#f0f0f0', textColor: '#666' },
starter: {
name: 'Starter',
color: '#FFD900',
monthly: { price: 22, currency: 'EUR', stripePrice: 'price_starter_monthly' },
yearly: {
price: 12,
pricePerYear: 144,
currency: 'EUR',
savings: '45%',
stripePrice: 'price_starter_yearly'
}
},
// ... more tiers
}
Компоненты
src/page/accountEdit/billing/index.tsx
- Оболочка главной платежной страницы.
- Показывает статус подписки
- Отображает сроки годности
- Кнопка отмены подписки
- Встраивает компонент PricingPlans.
src/page/accountEdit/billing/pricingPlans.tsx
- Макет из 3 столбцов: Бесплатно, Начальный, Профессиональный.
- Месячное/годовое переключение на уровень
- Интеграция кассы Stripe
- Индикация текущего плана
- Обработка ошибок
src/page/accountEdit/billing/pricingPlans.css
- Адаптивный макет сетки
- Цвета, специфичные для уровня
- Эффекты при наведении
- Мобильный адаптивный
Серверная часть (user-cycle)
GraphQL Схема (schema.graphql)
type User {
billingPlan: String
hasSubscription: Boolean
isSubscriptionExpired: Boolean
date_expiration: DateTime
}
type BillingHistoryEvent {
id: Int!
userId: Int!
eventType: String!
billingPlan: String
amount: Float
currency: String
details: String
createdAt: String!
}
type Query {
billingHistory: [BillingHistoryEvent]
}
type Mutation {
createCheckoutSession(plan: String, cycle: String): URL
cancelSubscription: CancelSubscriptionResult
}
История платежей
Функция истории выставления счетов отслеживает все события, связанные с выставлением счетов для пользователя:
Типы событий:
registration— учетная запись пользователя создана.purchase– приобретена уровневая подписка.cancellation— подписка отменена пользователемsystem_downgrade– автоматический переход на уровень бесплатного пользования (например, сбой платежа)upgrade– План обновленdowngrade- План понижен
Модель (src/models/billingHistory.ts):
export const billingHistoryModel = {
async getByUserId(userId: number): Promise<BillingHistoryEvent[]> {
// Fetch all events for user, ordered by createdAt DESC
},
async addRegistration(userId: number, plan: string) {
// Track when user registers
},
async addPurchase(userId: number, plan: string, amount: number, currency: string) {
// Track tier purchase
},
async addCancellation(userId: number, previousPlan: string) {
// Track cancellation
},
async addSystemDowngrade(userId: number, reason: string) {
// Track auto-downgrade to free tier
}
}
Таблица базы данных (billing_history):
id: первичный ключ с автоинкрементомuser_id: внешний ключ к таблице учетных записей.event_type: ENUM('регистрация', 'покупка', 'отмена', 'system_downgrade', 'обновление', 'понижение версии')billing_plan: ENUM('бесплатный', 'стартовый', 'профессиональный', 'аддон', 'корпоративный')amount: десятичное число (сумма платежа)currency: VARCHAR (например, «EUR»)details: ТЕКСТ (строка JSON с дополнительной информацией)created_at: временная метка
Отображение пользовательского интерфейса:
История платежей отображается в виде временной шкалы на странице web-app /account:
- Дата регистрации
- Уровневые покупки с суммами
- Отмены
- Изменения в системе (например, автоматическое понижение рейтинга при сбое платежа)
Это заменяет необходимость в явных предупреждениях об истечении срока действия — пользователи могут видеть полный график выставления счетов.
Резолверы (src/resolvers.ts)
createCheckoutSession
- Проверяет аутентификацию пользователя
- Сопоставляет план/цикл с идентификатором цены Stripe.
- Создает сеанс оформления заказа Stripe
- Оформление возврата URL
- Поддерживает режимы: «подписка» (стартер, профессионал) или «оплата» (аддон)
createCheckoutSession: async (parent, { plan, cycle }, ctx) => {
if (!ctx.uid) return err(error_code.AUTHENTICATION_REQUIRED);
const user = await userModel.getById(ctx.uid);
let priceId = getPriceIdForPlan(plan, cycle);
let mode = plan === 'addon' ? 'payment' : 'subscription';
const session = await stripe.checkout.sessions.create({
customer_email: user.email,
mode: mode,
line_items: [{ price: priceId, quantity: 1 }],
success_url: `${appUrl}/account/success`,
cancel_url: `${appUrl}/account/cancel`,
metadata: { plan, cycle }
});
return session.url;
}
cancelSubscription
- Отменяет подписку на Stripe.
- Обновление базы данных пользователей.
- Возвращает обновленный объект пользователя
Конфигурация (src/config/config.default.ts)
stripe: {
secret: 'sk_test_...',
webhook_secret: 'whsec_...',
plans: {
starter: {
monthly: 'price_starter_monthly_placeholder',
yearly: 'price_starter_yearly_placeholder'
},
professional: {
monthly: 'price_professional_monthly_placeholder',
yearly: 'price_professional_yearly_placeholder'
},
addon: {
oneTime: 'price_addon_onetime_placeholder'
}
}
}
Веб-сайт (публичные цены)
src/components/CustomPricingPage.js
- Публичная страница цен
- Показывает все уровни: любитель, начинающий, гибкий, профессиональный, корпоративный.
- Подробные списки функций
- Аддон-калькулятор (в разработке)
- Ссылки на регистрацию/продажу
Пользовательский процесс
Процесс покупки подписки
sequenceDiagram
actor User
participant WebApp
participant UserCycle
participant Stripe
participant Database
User->>WebApp: Navigate to /account
WebApp->>UserCycle: Query current user & plan
UserCycle->>Database: Fetch user data
Database-->>UserCycle: User data
UserCycle-->>WebApp: User with billingPlan
User->>WebApp: Click "Choose Yearly" on Starter
WebApp->>UserCycle: createCheckoutSession(plan: "starter", cycle: "yearly")
UserCycle->>Stripe: Create checkout session
Stripe-->>UserCycle: Session URL
UserCycle-->>WebApp: Checkout URL
WebApp->>Stripe: Redirect to checkout
User->>Stripe: Complete payment
Stripe->>UserCycle: Webhook: payment_success
UserCycle->>Database: Update subscription
Stripe-->>User: Redirect to success_url
User->>WebApp: /account/success
WebApp->>WebApp: Show success message
Порядок отмены подписки
sequenceDiagram
actor User
participant WebApp
participant UserCycle
participant Stripe
participant Database
User->>WebApp: Click "Cancel subscription"
WebApp->>UserCycle: cancelSubscription mutation
UserCycle->>Database: Fetch user & stripe_subscription
Database-->>UserCycle: Subscription ID
UserCycle->>Stripe: Cancel subscription
Stripe-->>UserCycle: Confirmation
UserCycle->>Database: Set stripe_subscription = null
Database-->>UserCycle: Updated
UserCycle-->>WebApp: Updated user
WebApp->>WebApp: Update UI to show cancellation
Схема базы данных
таблица счетов (в user-cycle MySQL)
id: первичный ключ.email: адрес электронной почты пользователя.stripe_subscription: идентификатор подписки Stripe (обнуляемый).billing_plan: ENUM('бесплатно', 'стартовый', 'профессиональный', 'аддон', 'корпоративный') - По умолчанию: "бесплатно"date_expiration: дата истечения срока действия подписки.date_added: дата создания учетной записи.
таблица billing_history (в user-cycle MySQL)
id: первичный ключ с автоинкрементом.user_id: внешний ключ к таблице учетных записей.event_type: ENUM('регистрация', 'покупка', 'отмена', 'system_downgrade', 'обновление', 'понижение версии')billing_plan: ENUM('бесплатный', 'стартовый', 'профессиональный', 'аддон', 'корпоративный')amount: DECIMAL(10,2) - Сумма платежа (обнуляемая)currency: VARCHAR(3) – например, «EUR» (обнуляемое)details: TEXT – строка JSON с дополнительной информацией (обнуляемая)created_at: TIMESTAMP - По умолчанию: CURRENT_TIMESTAMP
Файлы миграции:
migrations/023-create-billing-history.sql— Создает таблицу и заполняет данные учетной записи.migrations/024-update-billing-plan-enum.sql— обновляет значения перечисления со старых («базовый», «профессиональный») на новые («начинающий», «профессиональный»).
Интеграция полос
Идентификаторы цен (Производство)
Должно быть настроено в user-cycle/src/config/config.default.ts:
price_starter_monthly: стартовый ежемесячный план.price_starter_yearly: стартовый годовой планprice_professional_monthly: профессиональный ежемесячный план.price_professional_yearly: профессиональный годовой планprice_addon_onetime: Гибкая единоразовая оплата аддона.
Вебхуки
Настройте в панели инструментов Stripe:
checkout.session.completed: обработка успешных платежей.
–customer.subscription.deleted: обработка отменыcustomer.subscription.updated: обработка изменений плана.
Конечная точка вебхука: https://app.gratheon.com/webhooks/stripe
Переменные среды
user-cycle
STRIPE_SECRET_KEY: секретный ключ Stripe API.STRIPE_WEBHOOK_SECRET: секрет подписи веб-перехватчика Stripe.JWT_KEY: ключ подписи токена сеанса.MYSQL_HOST,MYSQL_USER,MYSQL_PASSWORD,MYSQL_DATABASE: конфигурация базы данных.
web-app
VITE_API_URL: конечная точка GraphQL API (указывает на user-cycle)
Вопросы безопасности
- Аутентификация: для всех изменений выставления счетов требуется действительный токен JWT в контексте.
- Защита CSRF: мутации GraphQL используют POST с правильным CORS.
- Проверка вебхука: подписи вебхуков Stripe должны быть проверены.
- Ценовая целостность: цены определяются на стороне сервера, а не на стороне клиента.
- Безопасность сеанса: срок действия сеанса оформления заказа истекает через 24 часа.
Тестирование
Контрольный список ручного тестирования
- Уровень бесплатного пользования отображается правильно.
- Стартовые ежемесячные/ежегодные работы по отбору
- Профессиональные ежемесячные/ежегодные отборочные работы
- При оформлении заказа Stripe корректно перенаправляется
- Страница успеха отображается после оплаты.
- Страница отмены отображается, если пользователь отказывается от оформления заказа.
- Отмена подписки работает
- Просроченная подписка показывает предупреждение
- Значок текущего плана отображается правильно.
Тестовые карты с полосками
- Успех:
4242 4242 4242 4242 - Отклонение:
4000 0000 0000 0002 - 3D Secure:
4000 0027 6000 3184
Известные проблемы и будущие улучшения
Завершенные функции
- ✅ Отслеживание истории платежей (события регистрации, покупки, отмены)
- ✅ Автоматический переход на уровень бесплатного пользования при сбое платежа
- ✅ Никаких жестких ограничений по платному доступу — пользователи сохраняют доступ к приложению.
- ✅ Обновленная структура цен (годовая скидка 45 % для Starter, 40 % для Professional)
- ✅ Чистый пользовательский интерфейс выбора уровня с индикатором текущего плана.
- ✅ Миграция перечисления базы данных (с «базового»/«профессионального» на «начинающего»/«профессионального»)
Текущая работа
- 🚧 Пользовательский интерфейс временной шкалы истории платежей в web-app.
- 🚧 Удалить гибкий уровень из выбора web-app (оставить только на веб-сайте)
- 🚧 Контроль доступа на уровне функций (замена жесткого платного доступа)
- 🚧 Улучшение визуального дизайна выбора уровня.
Планируемые функции
- Порядок обновления/понижения подписки
- Отслеживание использования гибкого дополнения.
- История счетов на странице учетной записи
- Управление способами оплаты
- Мультивалютная поддержка
- Корпоративные индивидуальные контракты
- Пропорциональное выставление счетов за изменения плана.
- Напоминания о продлении подписки
Стратегия блокировки на уровне функций
В будущей реализации функции будут проверяться индивидуально:
// Example feature check
const { hasAccess, requiredTier } = useFeatureAccess('cell-analysis')
if (!hasAccess) {
return (
<UpgradePrompt
feature="Cell Analysis"
requiredTier={requiredTier}
description="Analyze honeycomb cells and track resources"
/>
)
}
Преимущества:
- Пользователи обнаруживают функции естественным образом
- Контекстные подсказки по обновлению показывают ценность.
- Лучший UX, чем блокировка всего приложения.
- Четкое сопоставление функций и уровней.
Примечания по развертыванию
web-app
- Сборка:
npm run build - Среда: установите для
VITE_API_URLпроизводственный user-cycle URL. - Разверните статические файлы на CDN/хостинг.
user-cycle
- Установите ключи производственного Stripe в конфигурации.
- Настройте веб-хук Stripe URL.
- Разверните сервис GraphQL.
- Проверьте миграцию базы данных
сайт
- Обновите страницу цен, если уровни изменились.
- Восстановите Докузавра:
npm run build. - Разверните статический сайт
Поддержка и устранение неполадок
Распространенные проблемы
"Не удалось создать сеанс оформления заказа"
- Проверьте правильность ключей Stripe API.
- Убедитесь, что идентификаторы цен существуют на панели инструментов Stripe.
- Проверьте журналы сервера на наличие ошибок API.
"Срок действия подписки истек"
- Пользователю необходимо продлить подписку
- Проверьте
date_expirationв базе данных. - Проверьте статус подписки Stripe.
Вебхук не срабатывает
- Проверьте вебхук URL на панели управления Stripe.
- Проверьте правильность секретного кода веб-перехватчика.
- Проверка журналов веб-перехватчиков Stripe.
Контакт
- Технические проблемы: создайте проблему в соответствующем репозитории.
- Поддержка по выставлению счетов: support@gratheon.com
- Корпоративные продажи: Enterprise@gratheon.com