2026-07-21 12:36:59 +03:30
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Services;
|
|
|
|
|
|
|
|
|
|
use App\Models\Customer;
|
|
|
|
|
use App\Models\PargarSyncLog;
|
2026-07-21 14:13:26 +03:30
|
|
|
use Illuminate\Support\Facades\Cache;
|
2026-07-21 12:36:59 +03:30
|
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
|
use Rooyesh\Wallet\interfaces\TransactionInterface;
|
|
|
|
|
use Rooyesh\Wallet\interfaces\WalletInterface;
|
|
|
|
|
|
|
|
|
|
/**
|
2026-07-21 14:13:26 +03:30
|
|
|
* اعمال اسناد/ماندهٔ ورودی پرگار روی کیف اپ.
|
|
|
|
|
*
|
2026-07-21 14:23:26 +03:30
|
|
|
* اضطراری: اعمال اسناد روی کیف بهطور کامل خاموش است تا از شارژ چندباره جلوگیری شود.
|
|
|
|
|
* دکمهٔ ادمین فقط مانده را (در صورت فعال بودن) یکبار تنظیم میکند.
|
2026-07-21 12:36:59 +03:30
|
|
|
*/
|
|
|
|
|
class PargarInboundWalletService
|
|
|
|
|
{
|
|
|
|
|
public const TX_TYPE_PARGAR = 22;
|
|
|
|
|
|
2026-07-21 14:23:26 +03:30
|
|
|
/** کلید کش برای قطع اضطراری هرگونه اعمال کیف از پرگار */
|
|
|
|
|
public const KILL_SWITCH_CACHE_KEY = 'pargar_wallet_apply_disabled';
|
2026-07-21 14:13:26 +03:30
|
|
|
|
2026-07-21 12:36:59 +03:30
|
|
|
public function __construct(
|
|
|
|
|
private readonly PargarSyncService $sync,
|
|
|
|
|
private readonly WalletBroadcastService $walletBroadcast
|
|
|
|
|
) {
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-21 14:23:26 +03:30
|
|
|
public static function isApplyFrozen(): bool
|
|
|
|
|
{
|
|
|
|
|
// پیشفرض: یخزده — مگر صریحاً false شده باشد
|
|
|
|
|
$cached = Cache::get(self::KILL_SWITCH_CACHE_KEY);
|
|
|
|
|
if ($cached === null) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (bool) $cached;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static function freezeApplies(bool $frozen = true): void
|
|
|
|
|
{
|
|
|
|
|
Cache::forever(self::KILL_SWITCH_CACHE_KEY, $frozen);
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-21 12:36:59 +03:30
|
|
|
/**
|
|
|
|
|
* @param array<string, mixed> $data
|
|
|
|
|
* @return array{applied: int, skipped: int, errors: list<string>, logs: list<int>}
|
|
|
|
|
*/
|
2026-07-21 14:13:26 +03:30
|
|
|
public function applyDocuments(Customer $customer, array $data, bool $broadcast = true): array
|
2026-07-21 12:36:59 +03:30
|
|
|
{
|
|
|
|
|
$transactions = is_array($data['transactions'] ?? null) ? $data['transactions'] : [];
|
|
|
|
|
|
2026-07-21 14:23:26 +03:30
|
|
|
Log::warning('Pargar applyDocuments blocked (frozen)', [
|
|
|
|
|
'customer_id' => $customer->id,
|
|
|
|
|
'tx_count' => count($transactions),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
return [
|
2026-07-21 12:36:59 +03:30
|
|
|
'applied' => 0,
|
2026-07-21 14:23:26 +03:30
|
|
|
'skipped' => count($transactions),
|
|
|
|
|
'errors' => ['اعمال اسناد پرگار روی کیف موقتاً غیرفعال است'],
|
2026-07-21 12:36:59 +03:30
|
|
|
'logs' => [],
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-21 12:49:08 +03:30
|
|
|
/**
|
2026-07-21 14:13:26 +03:30
|
|
|
* @return array{
|
|
|
|
|
* documents: array{applied: int, skipped: int, errors: list<string>, logs: list<int>},
|
|
|
|
|
* mande: array{applied: bool, gold_delta?: float, rial_delta?: float, message: string}
|
|
|
|
|
* }
|
2026-07-21 12:49:08 +03:30
|
|
|
*/
|
2026-07-21 14:13:26 +03:30
|
|
|
public function syncWalletFromPargar(Customer $customer, bool $forceMande = false): array
|
2026-07-21 12:49:08 +03:30
|
|
|
{
|
2026-07-21 14:23:26 +03:30
|
|
|
if (self::isApplyFrozen() && ! $forceMande) {
|
|
|
|
|
return [
|
|
|
|
|
'documents' => ['applied' => 0, 'skipped' => 0, 'errors' => [], 'logs' => []],
|
|
|
|
|
'mande' => [
|
|
|
|
|
'applied' => false,
|
|
|
|
|
'message' => 'اعمال کیف از پرگار یخزده است — ابتدا با دستور pargar:wallet-freeze off آزاد کنید',
|
|
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-21 14:13:26 +03:30
|
|
|
$lock = Cache::lock('pargar-wallet-sync:'.$customer->id, 90);
|
|
|
|
|
if (! $lock->get()) {
|
|
|
|
|
return [
|
|
|
|
|
'documents' => ['applied' => 0, 'skipped' => 0, 'errors' => ['همگامسازی در حال اجراست'], 'logs' => []],
|
|
|
|
|
'mande' => ['applied' => false, 'message' => 'همگامسازی دیگری برای این مشتری در حال اجراست — لطفاً صبر کنید'],
|
|
|
|
|
];
|
2026-07-21 12:49:08 +03:30
|
|
|
}
|
|
|
|
|
|
2026-07-21 14:13:26 +03:30
|
|
|
try {
|
|
|
|
|
return [
|
|
|
|
|
'documents' => ['applied' => 0, 'skipped' => 0, 'errors' => [], 'logs' => []],
|
|
|
|
|
'mande' => $this->applyLatestMandeDifference($customer, force: $forceMande),
|
|
|
|
|
];
|
|
|
|
|
} finally {
|
|
|
|
|
$lock->release();
|
|
|
|
|
}
|
2026-07-21 12:49:08 +03:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @return array{applied: bool, gold_delta?: float, rial_delta?: float, message: string}
|
|
|
|
|
*/
|
2026-07-21 14:13:26 +03:30
|
|
|
public function applyLatestMandeDifference(Customer $customer, bool $force = false): array
|
2026-07-21 12:49:08 +03:30
|
|
|
{
|
2026-07-21 14:23:26 +03:30
|
|
|
if (self::isApplyFrozen() && ! $force) {
|
|
|
|
|
return [
|
|
|
|
|
'applied' => false,
|
|
|
|
|
'message' => 'اعمال کیف از پرگار یخزده است',
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-21 13:25:18 +03:30
|
|
|
if ($this->hasPendingOutboundWalletSync((int) $customer->id)) {
|
|
|
|
|
return [
|
|
|
|
|
'applied' => false,
|
|
|
|
|
'message' => 'سند ارسالی از پنل به پرگار هنوز تأیید نشده — برای جلوگیری از برگشت شارژ، ماندهٔ پرگار اعمال نشد',
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-21 12:49:08 +03:30
|
|
|
$log = PargarSyncLog::query()
|
|
|
|
|
->where('customer_id', $customer->id)
|
|
|
|
|
->where('type', PargarSyncService::TYPE_MANDE)
|
|
|
|
|
->orderByDesc('id')
|
|
|
|
|
->first();
|
|
|
|
|
|
|
|
|
|
if ($log === null) {
|
|
|
|
|
return ['applied' => false, 'message' => 'لاگ ماندهٔ پرگار برای این مشتری یافت نشد'];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$data = json_decode((string) $log->data, true) ?: [];
|
2026-07-21 14:13:26 +03:30
|
|
|
if (! $force && ! empty($data['wallet_applied_from_mande'])) {
|
2026-07-21 12:49:08 +03:30
|
|
|
return ['applied' => false, 'message' => 'ماندهٔ پرگار قبلاً روی کیف اعمال شده است'];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$goldInv = $this->parseNumber($data['GoldInv'] ?? 0);
|
|
|
|
|
$rialInv = $this->parseNumber($data['RialInv'] ?? 0);
|
|
|
|
|
|
|
|
|
|
$ownerType = (int) ($customer->type ?? 2);
|
|
|
|
|
$walletInterface = new WalletInterface((int) $customer->id, $ownerType);
|
|
|
|
|
$wallet = $walletInterface->wallet;
|
|
|
|
|
|
|
|
|
|
$goldDiff = $goldInv - (float) ($wallet->gold ?? 0);
|
|
|
|
|
$rialDiff = $rialInv - (float) ($wallet->cash ?? 0);
|
|
|
|
|
|
|
|
|
|
if (abs($goldDiff) < 0.01 && abs($rialDiff) < 1) {
|
|
|
|
|
$data['wallet_applied_from_mande'] = true;
|
|
|
|
|
$log->data = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
|
|
|
|
$log->save();
|
|
|
|
|
|
|
|
|
|
return ['applied' => false, 'message' => 'اختلافی بین ماندهٔ پرگار و کیف اپ نیست'];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$input = [
|
|
|
|
|
'title' => 'تطبیق ماندهٔ پرگار',
|
2026-07-21 14:13:26 +03:30
|
|
|
'note' => 'mande-log:'.$log->id,
|
2026-07-21 12:49:08 +03:30
|
|
|
'expire_at' => now(),
|
|
|
|
|
'active' => true,
|
|
|
|
|
];
|
|
|
|
|
if (abs($goldDiff) >= 0.01) {
|
|
|
|
|
$input['gold'] = abs($goldDiff);
|
|
|
|
|
}
|
|
|
|
|
if (abs($rialDiff) >= 1) {
|
|
|
|
|
$input['cash'] = abs($rialDiff);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
new TransactionInterface(
|
|
|
|
|
$walletInterface,
|
|
|
|
|
$input,
|
|
|
|
|
self::TX_TYPE_PARGAR,
|
|
|
|
|
$goldDiff > 0,
|
|
|
|
|
$rialDiff > 0
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$data['wallet_applied_from_mande'] = true;
|
|
|
|
|
$data['applied_gold_delta'] = $goldDiff;
|
|
|
|
|
$data['applied_rial_delta'] = $rialDiff;
|
2026-07-21 14:13:26 +03:30
|
|
|
$data['applied_at'] = now()->toDateTimeString();
|
2026-07-21 12:49:08 +03:30
|
|
|
$log->data = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
|
|
|
|
$log->desc = 'اعمال ماندهٔ پرگار روی کیف اپ';
|
|
|
|
|
$log->save();
|
|
|
|
|
|
|
|
|
|
$this->walletBroadcast->broadcastForCustomer((int) $customer->id, $ownerType);
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
'applied' => true,
|
|
|
|
|
'gold_delta' => $goldDiff,
|
|
|
|
|
'rial_delta' => $rialDiff,
|
|
|
|
|
'message' => 'ماندهٔ پرگار روی کیف اعمال شد',
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-21 13:25:18 +03:30
|
|
|
private function hasPendingOutboundWalletSync(int $customerId): bool
|
|
|
|
|
{
|
|
|
|
|
return PargarSyncLog::query()
|
|
|
|
|
->where('customer_id', $customerId)
|
|
|
|
|
->whereIn('type', [PargarSyncService::TYPE_WALLET_CASH, PargarSyncService::TYPE_WALLET_GOLD])
|
|
|
|
|
->where('sync_status', PargarSyncService::STATUS_PENDING)
|
|
|
|
|
->exists();
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-21 12:36:59 +03:30
|
|
|
private function parseNumber(mixed $value): float
|
|
|
|
|
{
|
|
|
|
|
if ($value === null || $value === '') {
|
|
|
|
|
return 0.0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (float) str_replace('/', '.', (string) $value);
|
|
|
|
|
}
|
|
|
|
|
}
|