Universal Billing Engine
Project description
Universal Billable Module (universal Billing Engine)
Status: Active Date: 2026-01-19 Tags: [monetization, billing, api, generic, ninja, n8n, referral, trial, quota, feature-based]
Business Processes
The module is designed as an isolated rights and payments accounting system (Billing Engine). It does not contain the application logic of a specific product (e.g., report generation), delegating this to external orchestrators (n8n).
1. Onboarding and Identity:
- External Identification: Orchestrators (like messaging bots or web apps) or specialized modules (like
bot) handle user identification/creation. - Trial Accruals: The "first report for free" logic is implemented through the activation of products with the
trialfeature. The system checks trial usage history via theTrialHistorymodel using abstract identity hashes (protecting against reuse across different providers). - Quota Balance: Before offering services, the user's quota balance is checked by feature name.
2. Order Life Cycle (Order Flow):
- Initiation: An
Orderis created via the API or service layer before sending an invoice to the client, passing a list of products withskuorid,quantity, andprice. Application IDs (e.g.,report_id) are stored inmetadata. The order is created withPENDINGstatus. - Invoice Creation: After order creation, the
order_idis included in the external invoicepayloadalong with application metadata (e.g.,report_id,sku). This ensures the order exists in the database before payment. - Payment: Payment gateway processing occurs in n8n or external systems. The invoice payload contains the
order_idfrom the database. - Confirmation: After successful payment, the existing order is confirmed via
POST /api/v1/billing/orders/{order_id}/confirmwithpayment_idandpayment_method. The module atomically transitions the order topaidstatus, setspaid_at, and createsUserProductrecords for each order item, calculatingexpires_atfor period-based products. - Idempotency: Reprocessing a payment with the same
payment_iddoes not create duplicateUserProductrecords. The order is identified byorder_idfrom the invoice payload, not created anew.
3. Referral Program (Generic Referrals):
- Links: The module stores
referrer -> refereechains through theReferralmodel. - Bonuses: Bonus accrual logic is managed via the API or service layer, triggering the creation of free orders or direct quota accruals.
- Protection: Verification through
TrialHistoryprevents the reuse of bonuses by identity hashes (e.g., hashed external ID).
4. Consumption Control (Quota Management):
- Selector-based approach (SKU-first, feature-fallback): For backward compatibility with external integrations that may pass a product identifier, the API parameter
featureis treated as a selector:- First, the engine tries to match it as
Product.sku(case-insensitive). - If no active product is found by SKU, it falls back to matching it as a feature name via
Product.metadata.features(e.g.,['resume_lift', 'vacancy_response']).
- First, the engine tries to match it as
- Verification:
check_quota(user, feature)methods return availability, a message, product name, and remaining balance. - Consumption:
consume_quota(user, feature, ...)methods atomically consume quota, creating aProductUsagerecord and supporting idempotency viaidempotency_key. - Product Types:
quantity:used_quantityis decremented; when the limit is reached, the product is deactivated.period:expires_atis checked; consumption does not affect the counter.unlimited: Always available; consumption is for audit only.
Architecture
The module is "detachable" (standalone-ready) and interacts with the environment through abstract interfaces.
System Components
- Core Engine (billable):
- Works with an abstract user model via
settings.AUTH_USER_MODEL. - Has no direct imports from application modules (
yir,vacancies). - Provides a REST API for n8n and a Python API (service layer) for workers.
- Uses JSONB
metadatato store application IDs instead of ForeignKeys.
- Works with an abstract user model via
- Orchestrator (n8n):
- "Glue Logic" layer. Knows about external platforms, payment gateways, and application IDs.
- Maps business events (like
/startin bot) to the identification layer (bot) and then to the Billing Generic API. - Manages user scenarios and states through
UserSession.
- Consumer (yir/vacancies):
- Consumes services through the service layer without knowing about the logic of their acquisition or cost.
- Calls quota check and consumption methods by feature.
Service Layer
QuotaService — central service for working with quotas:
- Feature availability check methods.
- Atomic consumption with idempotency.
- Trial product activation.
- Support for synchronous and asynchronous operations.
UserProductService — management of active user products:
- Retrieving active and available products with feature filtering.
- Usage capability check.
- Balance summary retrieval.
- Usage record creation.
OrderService — order management:
- Creation of orders with multiple products.
- Payment processing with idempotency.
- Automatic
UserProductcreation upon payment. - Product deactivation upon refund/cancellation.
ProductService — working with the product catalog:
- Retrieving products by features.
- Filtering trial products.
- Product activity management.
Technical Implementation
Current State (Analysis)
What is implemented:
- ✅ Data models:
Product,Order,OrderItem,UserProduct,ProductUsage,TrialHistory,Referral. - ✅ SKU support in the
Productmodel. - ✅ JSONB
metadatafor extensibility. - ✅ Product types:
period,quantity,unlimited. - ✅ Service layer with atomic operations.
- ✅ REST API via Django Ninja with Swagger documentation.
- ✅ Django Signals for event-driven local integration.
- ✅ Full unit and integration test coverage.
Universal Data Models
Примечание: Все таблицы и индексы биллинга используют префикс billable_ (например, billable_products, billable_orders, billable_order_items, и т.д.).
Product — product catalog:
- Таблица:
billable_products sku(CharField, unique): Unique identifier for integration.name,description: Name and description.product_type(TextChoices):PERIOD,QUANTITY,UNLIMITED.price,currency: Price and currency.period_days(nullable): Validity period for period-based products.quantity(nullable): Number of units for quantity-based products.is_active: Activity flag.metadata(JSONField): Additional parameters, includingfeatures(list of features).
Order — user orders:
- Таблица:
billable_orders user(ForeignKey): Link to the user.total_amount,currency: Amount and currency.status(TextChoices):PENDING,PAID,CANCELLED,REFUNDED.payment_method,payment_id: Payment data.created_at,paid_at: Timestamps.
OrderItem — order items:
- Таблица:
billable_order_items order(ForeignKey): Link to the order.product(ForeignKey): Link to the product.quantity,price: Quantity and price per unit.total_quantity,period_days: Copies of product parameters at the time of purchase.
UserProduct — active user rights:
- Таблица:
billable_user_products user,product,order_item: Links.purchased_at,expires_at: Timestamps.is_active: Activity flag.total_quantity,used_quantity: For quantity-based products.period_start,period_end: For period-based products.- Methods:
can_use(),is_expired(),get_remaining_quantity(),get_days_left().
ProductUsage — usage history:
- Таблица:
billable_product_usages user,user_product: Links.action_type,action_id: Action type and identifier.used_at: Timestamp.metadata(JSONField): Additional data (stores app IDs likereport_id).
TrialHistory — tracking of trial usage:
- Таблица:
billable_trial_history identity_type(CharField): Type of identifier (e.g., 'external_id', 'hh').identity_hash(CharField, indexed): SHA-256 hash of the identifier for privacy.trial_plan_name: Name of the trial plan used.used_at,created_at: Timestamps.- Methods:
has_used_trial(identities: dict),has_used_trial_async(identities: dict).
Referral — referral program:
- Таблица:
billable_referrals referrer,referee(ForeignKey): User links.bonus_granted: Bonus accrual flag.created_at: Timestamp.
Generic API (Django Ninja)
Main Endpoints:
POST /api/v1/billing/grants— grant products by SKU (e.g., 'express') or default trial products.GET /api/v1/billing/balance— get current user quotas by feature.POST /api/v1/billing/orders— create an order with arbitrary metadata.POST /api/v1/billing/orders/{order_id}/confirm— confirm payment and activate rights. Returns full order data including item SKUs.POST /api/v1/billing/quota/consume— consume quota by feature.POST /api/v1/billing/referrals— establish a referral link.
Detailed Endpoint: Order Confirmation
POST /api/v1/billing/orders/{order_id}/confirm
Request Body:
{
"payment_id": "string (e.g. external payment_charge_id)",
"payment_method": "string (default: provider_payments)",
"status": "paid"
}
Response (200 OK):
{
"success": true,
"message": "Order paid and products activated",
"data": {
"id": 123,
"user_id": 456,
"status": "paid",
"total_amount": "500.00",
"currency": "RUB",
"payment_method": "provider_payments",
"payment_id": "charge_...",
"created_at": "2026-01-19T10:00:00Z",
"paid_at": "2026-01-19T10:05:00Z",
"items": [
{
"id": 1,
"product_id": 10,
"product_name": "Premium Report",
"sku": "yir_premium",
"quantity": 1,
"price": "500.00",
"total_quantity": 1,
"period_days": null
}
],
"metadata": {
"report_id": 789
}
}
}
Note: User profile creation/syncing is moved to the bot module.
Security
1. Authentication
- Bearer Token Authentication: Tokens are passed in the
Authorization: Bearer <token>header. Token is checked againstsettings.BILLING_API_TOKEN.
2. Protective measures
- Atomic Operations: All quota changes use
transaction.atomic()andselect_for_update()(where supported). - Idempotency: Using
idempotency_key(viaaction_id) in quota consumption and payment processing. - Validation: All input data is validated via Pydantic/Ninja schemas.
Refactoring Log
Refactoring monetization to billable (COMPLETED)
- Renamed folder
monetization->billable. - Renamed AppConfig
MonetizationConfig->BillableConfig. - Updated all imports and references in
settings.py,urls.py, and within the module. - Translated all comments, docstrings, and
README.mdto English. - Updated all migrations to use the new app name and English verbose names.
- Created
pyproject.tomlto make the module a standalone installable package. - All tests updated and passing.
Stage 5: User Identification Refactoring (COMPLETED)
- Extracted Logic: Moved user registration and identification from
billableto the newbotmodule. - New Endpoint: Created
POST /api/v1/bot/identifyas the main entry point for n8n/external scenarios. - Trial Eligibility: Trial check logic is now part of the
identifyflow but uses the abstractTrialHistoryfrombillable.
Stage 6: n8n Workflow Migration (IN PROGRESS)
Goal: Replace direct SQL queries and old API calls in n8n. Tasks:
- Update "Year in Review - Onboarding" workflow.
- Replace user registration SQL with
POST /api/v1/bot/identify. - Replace trial grant SQL with
POST /api/v1/billing/grants. - Invoice Creation Flow: Orders are now created in the database before sending invoices to the client. The
order_idis included in the invoicepayload, and payment confirmation uses the existing order viaPOST /api/v1/billing/orders/{order_id}/confirminstead of creating a new order. - Test E2E workflow with the new API.
Principles of Detachability
- No Hardlinks: No ForeignKeys to other apps. App IDs are stored in
metadata. - Settings Based: Configuration via
settings.py. - Event Driven: Generates Django Signals for decoupled integration.
- Feature Based: Products define capabilities via
metadata.features. - Idempotency: Built-in protection against double-spending.
- Packageable: Included
pyproject.tomlforpip installsupport.
Смотрите также:
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file billable-0.1.0.tar.gz.
File metadata
- Download URL: billable-0.1.0.tar.gz
- Upload date:
- Size: 33.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5647a39bcb1514795c4e27da6d1ad76acff791bde30b88fda6c8a25bca2669fa
|
|
| MD5 |
d22e7c51641bf42b259f930f64ab4a12
|
|
| BLAKE2b-256 |
4423420d02d879b38b26241424316f0826a5ed0b479b2b34ddce61873f89d2fe
|
File details
Details for the file billable-0.1.0-py3-none-any.whl.
File metadata
- Download URL: billable-0.1.0-py3-none-any.whl
- Upload date:
- Size: 34.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
964eddb73bb69559b31b06dca6f44273c537d2e15b707fd833d7a26eb89ccf4f
|
|
| MD5 |
5c20a9478edc5d556c6600e7eb7deaaa
|
|
| BLAKE2b-256 |
b92a8e19459ccb4ee7543c205ee960c90c49b7cd95ad1f80a6211a88b3c9c9bb
|