Перейти к содержанию

TransactionHandler — Background Processing

Обзор

TransactionHandler — фоновый сервис для обработки транзакций, синхронизации с ВИНК и расчёта балансов. Работает на базе Quartz.NET планировщика.

Архитектура

graph TB
    subgraph "TransactionHandler"
        PROGRAM[Program.cs<br/>Entry Point]
        STARTUP[Startup.cs<br/>DI Config]
        QUARTZ[Quartz.NET<br/>Scheduler]
        JOBS[Jobs<br/>5 min interval]
    end

    subgraph "Core Services"
        FPTS[FinancePartTransactionService<br/>Transaction Processing]
        TM[TransactionManager<br/>State Coordination]
        JST[JobStatusTracker<br/>Health Monitor]
    end

    subgraph "External"
        DB[(PostgreSQL)]
        VINKS[VINK APIs]
        MQ[RabbitMQ]
    end

    PROGRAM --> STARTUP --> QUARTZ --> JOBS --> FPTS --> DB
    FPTS --> VINKS
    JOBS --> TM
    JOBS --> JST
    FPTS --> MQ

Core Components

Program.cs

Точка входа с глобальной обработкой исключений:

// Global exception handling
AppDomain.CurrentDomain.UnhandledException += (sender, e) =>
{
    if (e.ExceptionObject is OutOfMemoryException ||
        e.ExceptionObject is InsufficientMemoryException)
    {
        // Log and restart
    }
};

// Jobs registered in Production only
q.AddJobsTransactionHandler();  // FinanceTransactionCalculateJob

TransactionManager

Координатор операций через флаги состояния:

Флаг Описание
IsSyncTransaction Синхронизация транзакций в процессе
IsRecalculateTransaction Перерасчёт транзакций
IsCalculateBalance Расчёт баланса
IsReconciliationOfBalances Сверка балансов
IsHourCheckBalance Ежечасная проверка баланса
IsHavingBalanceActionNeedProcess Есть действия с балансами

Проверки конкурентности

public bool CanCalculateBalance()
    => !IsReconciliationOfBalances && !IsHourCheckBalance;

public bool CanRecalculateTransaction()
    => !IsReconciliationOfBalances;

Жизненный цикл транзакции

stateDiagram-v2
    [*] --> Received: External VINK API
    Received --> Pending: FinanceTransaction.Create()
    Pending --> Processing: NeedCalculate = true
    Processing --> Validation: FinanceTransactionCalculateJob (5 min)
    Validation --> Parts: Card & Contract valid
    Validation --> Failed: No contract
    Parts --> Pricing: PartTransactions()
    Pricing --> Negative: Check balance
    Pricing --> Normal: Balance OK
    Negative --> Final: Negative pricing
    Normal --> Final: Normal pricing
    Final --> Done: NeedCalculate = false
    Failed --> [*]
    Done --> [*]

Типы частей транзакции

Тип Описание Поток
ВинкДистрибьютор VINK → Дистрибьютор Fuel Vendor → Distributor
ДистрибьюторПокупатель Дистрибьютор → Покупатель Distributor → Customer
ПокупательПокупатель Покупатель → Покупатель Resale → Another Company

Расчёт цены

graph TD
    FT[FinanceTransaction] --> DISCOUNT[Get Discount from Contract]
    DISCOUNT --> BALANCE{Balance Status}
    BALANCE -->|Negative| NEG[Negative Pricing]
    BALANCE -->|OK| CONDITION[Get Contract Condition]
    CONDITION --> TEMPLATE[Apply Pricing Template]
    TEMPLATE --> FINAL[Final Price = Base ± Discount/Markup]
    NEG --> FINAL

Опции отрицательного баланса

  1. Процент reduction: PercentDiscountForNegativeBalance
  2. Рубль surcharge: RubleForNegativeBalance
  3. Stella price (без скидки): PriceByStellaForNegativeBalance

Расписание задач

Основные задачи

Задача Интервал Назначение
FinanceTransactionCalculateJob 5 мин Расчёт транзакций
ApiJob (GasStations) 48 часов Синхронизация АЗС
ApiJob (Cards) 2 часа Синхронизация карт
ApiJob (Balance) 20 мин Синхронизация балансов
ApiJob (HistoryTransactions) 35 мин История транзакций
BillingJob 08:00, 20:00 Сверка балансов
CalculateBalanceActionsJob 30 сек Обработка действий

Специфичные задачи ВИНК

Tatneft

Задача Интервал Описание
SyncTransactionTatneftJob 1 мин Синхронизация транзакций
TatneftBlockJob 1 мин Блокировка карт
TatneftLimitJob 1 мин Лимиты
BalanceTatneftUpdateJob 10 мин Баланс
TatneftFormationReportByMounthJob Ежедневно 02:00 Месячные отчёты

Gazprom

Задача Интервал Описание
GazpromCreateReportsJob Ежечасно Ночные отчёты

Health Monitoring

Health Check Endpoint

GET /health

Response:

{
  "status": "Healthy",
  "checks": [
    {
      "name": "job_health",
      "status": "Healthy",
      "description": "FinanceTransactionCalculateJob running",
      "data": {
        "lastRun": "2026-01-05T01:45:00Z"
      }
    }
  ],
  "totalDuration": 15.5
}

JobStatusTracker

Мониторинг выполнения задач:

  • Последняя успешная executed
  • Детекция failures
  • Duration tracking

Отслеживание владельца карты

Система отслеживает изменения владельца карты во времени:

public int? GetCardOwnerIdAtDate(
    int cardId,
    DateTime localTransactionTime,
    int timeZoneOffsetHours)

Это позволяет корректно атрибутировать транзакции при передаче карт между владельцами.

Конфигурация

Environment

Окружение Jobs Enabled
Development ❌ Disabled
Test ❌ Disabled
Production ✅ Enabled

Connection String

{
  "ConnectionStrings": {
    "DefaultConnection": "Host=localhost;Database=fuelprocessing;..."
  }
}

Troubleshooting

Транзакции застряли с NeedCalculate = true

Причина: У карты нет CurrentContractBalanceId

Решение: Убедитесь в корректной настройке баланса контракта

Некорректное ценообразование

Причина: Отсутствуют шаблоны цен топлива

Решение: Проверьте FuelPriceForTemplate и RegionDirectContractWithVendorDiscount

Дрейф баланса

Причина: Необработанные записи BalanceAction

Решение: Проверьте выполнение CalculateBalanceActionsJob

Связанная документация