losthost/user-balance

There is no license information available for the latest version (0.0.6) of this package.

A simple lib to track users' balances

Installs: 9

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 0

Forks: 0

Open Issues: 0

pkg:composer/losthost/user-balance

0.0.6 2025-12-13 09:34 UTC

This package is auto-updated.

Last update: 2025-12-13 09:37:51 UTC


README

Библиотека для учета балансов пользователей с системой чекпоинтов для высокой производительности.

Особенности

  • Полная история всех операций с фильтрацией по датам и типам
  • Высокая производительность за счет системы чекпоинтов
  • Атомарные переводы между пользователями в одной транзакции БД
  • Типобезопасность через константы вместо строк
  • Поддержка больших чисел DECIMAL(36,18) для криптовалют
  • Гибкая история операций с пагинацией и фильтрацией

Установка

composer require losthost/userbalance

Быстрый старт

use losthost\UserBalance\DBBalanceTransaction;

// Автоматически создает таблицу при первом использовании

// Пополнение баланса
$tx = DBBalanceTransaction::transaction(
    'user_123', 
    100.50, 
    DBBalanceTransaction::TYPE_TOPUP, 
    'Пополнение'
);

// Списание
$tx = DBBalanceTransaction::transaction(
    'user_123', 
    -30, 
    DBBalanceTransaction::TYPE_USAGE, 
    'Оплата услуги'
);

// Перевод
$result = DBBalanceTransaction::transfer(
    'alice_id', 
    'bob_id', 
    50, 
    'Перевод'
);

// Баланс
$balance = DBBalanceTransaction::getBalance('user_123');

// История операций
$history = DBBalanceTransaction::getHistory(
    'user_123',
    new DateTime('-7 days'), // За последние 7 дней
    null,                    // До текущего момента
    [DBBalanceTransaction::TYPE_TOPUP, DBBalanceTransaction::TYPE_USAGE],
    100,                     // Лимит
    0                        // Оффсет
);

Основные методы

transaction() — Создание транзакции

DBBalanceTransaction::transaction(
    string $user_id,
    float $amount,
    string $type, // Используйте константы TYPE_*
    ?string $description = null
) : DBBalanceTransaction

transfer() — Атомарный перевод

DBBalanceTransaction::transfer(
    string $from_user,
    string $to_user, 
    float $amount, // > 0
    ?string $description = null
) : array ['out' => $tx_out, 'in' => $tx_in]

getBalance() — Получение баланса

DBBalanceTransaction::getBalance(
    string $user_id,
    ?string $datetime = null // Баланс на момент времени
) : float

getHistory() — История операций

DBBalanceTransaction::getHistory(
    string $user_id,
    ?DateTimeInterface $start = null,
    ?DateTimeInterface $end = null,
    ?array $types = null, // null = все кроме checkpoint
    int $limit = 100,
    int $offset = 0
) : array // Массив ассоциативных массивов

checkpoint() — Создание чекпоинтов

DBBalanceTransaction::checkpoint(
    int $min_transactions // 0 = для всех пользователей
) : void

initBalance() — Инициализация баланса

DBBalanceTransaction::initBalance(
    string $user_id,
    float $amount,
    string $description = 'Initial balance'
) : void // Бросает исключение если пользователь уже существует

Типы операций

DBBalanceTransaction::TYPE_TOPUP        // Пополнение
DBBalanceTransaction::TYPE_USAGE        // Списание  
DBBalanceTransaction::TYPE_TRANSFER_IN  // Получение перевода
DBBalanceTransaction::TYPE_TRANSFER_OUT // Отправка перевода
DBBalanceTransaction::TYPE_CHECKPOINT   // Чекпоинт (системный)

Важно: TYPE_TRANSFER_IN и TYPE_TRANSFER_OUT можно создавать только через метод transfer().

Чекпоинты для производительности

Библиотека использует чекпоинты для ускорения запросов баланса:

-- При запросе баланса считаются только транзакции после последнего чекпоинта
SELECT SUM(amount) FROM transactions 
WHERE user_id = 'user1' 
AND id >= (SELECT MAX(id) FROM transactions WHERE user_id = 'user1' AND type = 'checkpoint')

Рекомендуется запускать в кроне:

// Раз в час создавать чекпоинты для пользователей с ≥1000 транзакций
DBBalanceTransaction::checkpoint(1000);

Расширение функциональности

Отдельный учет бонусов

class DBBonus extends DBBalanceTransaction {
    // Автоматически создаст таблицу 'bonus' с той же структурой
    // Можно добавить свои поля в METADATA
}

// Использование
DBBonus::transaction('user1', 100, DBBonus::TYPE_TOPUP, 'Бонус');
$bonus_balance = DBBonus::getBalance('user1');

Кастомные типы операций

class MyBalance extends DBBalanceTransaction {
    const TYPE_REFERRAL = 'referral';
    const TYPE_PENALTY = 'penalty';
    
    const METADATA = [
        ...parent::METADATA,
        'type' => 'ENUM("topup", "usage", "transfer_in", "transfer_out", "checkpoint", "referral", "penalty")',
    ];
}

Структура базы данных

CREATE TABLE transactions (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id VARCHAR(50) NOT NULL,
    amount DECIMAL(36,18) NOT NULL,
    type ENUM('topup', 'usage', 'transfer_in', 'transfer_out', 'checkpoint'),
    description TEXT,
    created_at DATETIME NOT NULL,
    INDEX idx_user_id (user_id),
    INDEX idx_user_type (user_id, type)
);

Обработка ошибок

try {
    DBBalanceTransaction::transfer('user1', 'user2', -10);
} catch (InvalidArgumentException $e) {
    // "Transfer amount must be positive"
}

try {
    DBBalanceTransaction::transaction('user1', 100, 'invalid_type');
} catch (InvalidArgumentException $e) {
    // "Invalid transaction type"
}

try {
    DBBalanceTransaction::initBalance('existing_user', 100);
} catch (RuntimeException $e) {
    // "User already has transactions"
}

Тестирование

composer test

33 теста, 85 assertions покрывают:

  • Корректность расчетов баланса
  • Атомарность переводов
  • Работу чекпоинтов
  • Фильтрацию истории операций
  • Валидацию входных данных
  • Создание таблиц при их отсутствии
  • Проверку на работу во внешней транзакции

Производительность в продакшене

  1. Индексы: (user_id, type) для быстрого поиска чекпоинтов
  2. Чекпоинты: Запускайте checkpoint(1000) раз в час
  3. Архивация: Старые транзакции можно перемещать в архивную таблицу
  4. Мониторинг: Следите за количеством транзакций между чекпоинтами

Лицензия

MIT