From a809a843b8380930c5251b8372687b4387eb612c Mon Sep 17 00:00:00 2001 From: adonis Date: Mon, 20 Jul 2026 15:52:42 +0330 Subject: [PATCH] fix pargar bug --- .../Controllers/Admin/WalletController.php | 64 +++++ .../Controllers/Customer/CartController.php | 5 +- .../Pargar/PargarInboundController.php | 243 +++++++++++++--- .../Single/SingleOrderController.php | 25 +- .../Controllers/pay/paypingController.php | 30 ++ app/Services/PargarSyncService.php | 261 +++++++++++++++++- database/sql/deploy_settings_const.sql | 20 +- 7 files changed, 586 insertions(+), 62 deletions(-) diff --git a/app/Http/Controllers/Admin/WalletController.php b/app/Http/Controllers/Admin/WalletController.php index 39e2f75..b71b9b4 100644 --- a/app/Http/Controllers/Admin/WalletController.php +++ b/app/Http/Controllers/Admin/WalletController.php @@ -25,6 +25,7 @@ use App\Models\TransactionsAdmin; use App\Services\AssetSummaryService; use App\Services\BalanceReportService; use App\Services\ConfigService; +use App\Services\PargarSyncService; use App\Services\WalletBalanceService; use App\Services\WalletBroadcastService; use Hekmatinasser\Verta\Verta; @@ -83,6 +84,20 @@ class WalletController extends Controller 'active' => true ], 3, false, true, $gold_block); + try { + app(PargarSyncService::class)->queueWalletDocument( + $customer, + 'cash', + (float) $data['value'], + [ + 'withdraw_id' => $Withdraw->id, + 'source' => 'admin_charge2', + ] + ); + } catch (\Throwable $e) { + report($e); + } + $message = 'کیف پول شما به مبلغ ' . self::formatSmsRialAmount($data['value']) . " ریال در تاریخ " . verta()->format('Y/n/j H:i:s') . " شارژ شد " . "\n" . $this->Config('name_fa'); $this->Send($customer->mobile, $message); @@ -140,6 +155,21 @@ class WalletController extends Controller } } + try { + app(PargarSyncService::class)->queueWalletDocument( + $customer, + 'cash', + (float) $data['value'], + [ + 'withdraw_id' => $Withdraw->id, + 'note' => $data['note'] ?? null, + 'source' => 'admin_cash', + ] + ); + } catch (\Throwable $e) { + report($e); + } + //send sms to users and customer $status = $data['value'] > 0 ?" شارژ شد ": " کسر شد "; if (self::isSmsNotifEnabled('charge_notif')) { @@ -212,6 +242,23 @@ class WalletController extends Controller report($e); } } + + try { + app(PargarSyncService::class)->queueWalletDocument( + $customer, + 'gold', + (float) $data['value'], + [ + 'withdraw_id' => $Withdraw->id, + 'note' => $data['note'] ?? null, + 'product_id' => $representative?->id, + 'source' => 'admin_gold', + ] + ); + } catch (\Throwable $e) { + report($e); + } + //send sms to users and customer $status = $data['value'] > 0 ?" شارژ شد ": " کسر شد "; $weightUnit = self::tradeWeightUnit(); @@ -361,6 +408,23 @@ class WalletController extends Controller } } + try { + // فقط محصول متصل به کیف اصلی → سند طلای پرگار + app(PargarSyncService::class)->queueWalletDocument( + $customer, + 'gold', + (float) $data['value'], + [ + 'withdraw_id' => $Withdraw->id, + 'note' => $data['note'] ?? null, + 'product_id' => $productId, + 'source' => 'admin_product', + ] + ); + } catch (\Throwable $e) { + report($e); + } + $status = $deposit ? ' شارژ شد ' : ' کسر شد '; $formatted = self::formatSmsGoldWeight($data['value']); diff --git a/app/Http/Controllers/Customer/CartController.php b/app/Http/Controllers/Customer/CartController.php index a697df4..eddf3b8 100644 --- a/app/Http/Controllers/Customer/CartController.php +++ b/app/Http/Controllers/Customer/CartController.php @@ -386,7 +386,10 @@ class CartController extends Controller if ($config->isPargarAccounting()) { try { - app(PargarSyncService::class)->recordTrade($order, true); + app(PargarSyncService::class)->recordTrade($order, true, [ + 'payment_kind' => PargarSyncService::PAYMENT_CASH, + 'product_id' => null, + ]); } catch (\Throwable $e) { report($e); } diff --git a/app/Http/Controllers/Pargar/PargarInboundController.php b/app/Http/Controllers/Pargar/PargarInboundController.php index b2b9881..910946d 100644 --- a/app/Http/Controllers/Pargar/PargarInboundController.php +++ b/app/Http/Controllers/Pargar/PargarInboundController.php @@ -8,13 +8,11 @@ use App\Models\Cart; use App\Models\Customer; use App\Models\Order; use App\Models\PargarSyncLog; -use App\Models\Product; use App\Services\ConfigService; use App\Services\PargarSyncService; use Illuminate\Http\Request; use Illuminate\Support\Facades\Schema; use Morilog\Jalali\Jalalian; -use Rooyesh\Wallet\interfaces\TransactionInterface; use Rooyesh\Wallet\interfaces\WalletInterface; use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException; @@ -164,44 +162,41 @@ class PargarInboundController extends Controller public function listOrders(Request $request) { + $documents = collect(); + $orders = Order::query() ->where('sts', 2) ->whereNull('pargar_confirmed_at') ->limit(100) ->get(); - $documents = $orders->map(function (Order $order) { - $customer = Customer::find((int) ($order->customer_id ?? 0)); + foreach ($orders as $order) { + $payload = $this->orderExportPayload($order); + if ($payload === null) { + continue; + } + $documents->push($payload); + } - // direction اپ: 1=خرید مشتری، 2=فروش مشتری - // OrderType پرگار از دید فروشگاه است: خرید مشتری = فروش فروشگاه (-1)، فروش مشتری = خرید فروشگاه (1) - $isCustomerBuy = (int) ($order->direction ?? 1) === 1; - $customerDirection = (int) ($order->direction ?? 0); + $walletLogs = PargarSyncLog::query() + ->whereIn('type', [PargarSyncService::TYPE_WALLET_CASH, PargarSyncService::TYPE_WALLET_GOLD]) + ->where('sync_status', PargarSyncService::STATUS_PENDING) + ->orderBy('id') + ->limit(100) + ->get(); - return [ - 'OrderId' => (string) $order->id, - 'OrderType' => $isCustomerBuy ? -1 : 1, - 'PriceType' => 1, - 'CustomerAccountId' => (int) ($customer->pargar_account_id ?? 0), - 'ItemId' => 11, - 'ItemName' => $this->pargarItemName($order), - 'OrderDate' => $order->pay_at - ? Jalalian::fromDateTime($order->pay_at)->format('Y/m/d-H:i:s') - : Jalalian::fromDateTime($order->created_at)->format('Y/m/d-H:i:s'), - 'Fee' => (float) ($order->price ?? 0), - 'Count' => 1, - 'Weight' => (float) ($order->amount ?? 0), - 'Price' => (float) ($order->total_price ?? 0), - 'Carat' => 750, - 'Description' => self::accountingShopTradeDesc($customerDirection, $this->isCartOrder($order)), - ]; - })->values(); + foreach ($walletLogs as $log) { + $payload = $this->walletExportPayload($log); + if ($payload !== null) { + $documents->push($payload); + } + } return response()->json([ 'state' => 'true', 'msg' => '', 'data' => [ - 'documents' => $documents, + 'documents' => $documents->values(), 'duplicated' => 'false', 'result' => 'OK', ], @@ -242,7 +237,7 @@ class PargarInboundController extends Controller PargarSyncLog::create([ 'customer_id' => $customer?->id, 'desc' => 'دریافت اسناد از پرگار', - 'type' => 'document', + 'type' => PargarSyncService::TYPE_DOCUMENT, 'sync_status' => PargarSyncService::STATUS_SUCCESS, 'pargar_ref' => $accountId, 'data' => json_encode($data, JSON_UNESCAPED_UNICODE), @@ -272,33 +267,35 @@ class PargarInboundController extends Controller $goldInv = isset($data['GoldInv']) ? (float) str_replace('/', '.', (string) $data['GoldInv']) : 0; $rialInv = isset($data['RialInv']) ? (float) str_replace('/', '.', (string) $data['RialInv']) : 0; - // خواندن و نوشتن روی همان کیف اصلی مشتری — نه first() تصادفی + // فقط مقایسه برای لاگ — اپ source of truth است؛ کیف را از مانده پرگار عوض نکن $ownerType = (int) ($customer->type ?? 2); $walletInterface = new WalletInterface($customer->id, $ownerType); $wallet = $walletInterface->wallet; - $goldDiff = $goldInv - (float) ($wallet->gold ?? 0); - $rialDiff = $rialInv - (float) ($wallet->cash ?? 0); + $appGold = (float) ($wallet->gold ?? 0); + $appCash = (float) ($wallet->cash ?? 0); + $goldDiff = $goldInv - $appGold; + $rialDiff = $rialInv - $appCash; + $hasDiff = abs($goldDiff) >= 0.01 || abs($rialDiff) >= 1; - if (abs($goldDiff) >= 0.01 || abs($rialDiff) >= 1) { - new TransactionInterface($walletInterface, [ - 'cash' => $rialDiff, - 'gold' => $goldDiff, - 'title' => 'تعدیل حسابداری پرگار', - 'expire_at' => now(), - 'active' => true, - 'start_at' => now(), - ], 22, $goldDiff > 0, $rialDiff > 0); - } + $desc = $hasDiff + ? 'اختلاف مانده پرگار با اپ — کیف اپ دست نخورده' + : 'دریافت مانده از پرگار — بدون اختلاف'; PargarSyncLog::create([ 'customer_id' => $customer->id, - 'desc' => 'دریافت مانده از پرگار', - 'type' => 'mande', + 'desc' => $desc, + 'type' => PargarSyncService::TYPE_MANDE, 'amount' => $goldInv, 'sync_status' => PargarSyncService::STATUS_SUCCESS, 'pargar_ref' => $accountId, - 'data' => json_encode($data, JSON_UNESCAPED_UNICODE), + 'data' => json_encode(array_merge($data, [ + 'app_gold' => $appGold, + 'app_cash' => $appCash, + 'gold_diff' => $goldDiff, + 'rial_diff' => $rialDiff, + 'wallet_adjusted' => false, + ]), JSON_UNESCAPED_UNICODE), ]); return response()->json([ @@ -495,4 +492,162 @@ class PargarInboundController extends Controller ->whereIn('desc', ['سبد خرید', 'فروش سبد به مشتری']) ->exists(); } + + /** + * @return array|null + */ + private function orderExportPayload(Order $order): ?array + { + $sync = $this->sync(); + $log = PargarSyncLog::where('order_id', $order->id)->latest('id')->first(); + $payload = $log ? (json_decode((string) $log->data, true) ?: []) : []; + + $paymentKind = (string) ($payload['payment_kind'] ?? PargarSyncService::PAYMENT_CASH); + if ($paymentKind === PargarSyncService::PAYMENT_OTHER_PRODUCT) { + return null; + } + + if ($log && is_string($log->pargar_ref) && str_starts_with($log->pargar_ref, 'skip:')) { + return null; + } + + $customer = Customer::find((int) ($order->customer_id ?? 0)); + $isCustomerBuy = (int) ($order->direction ?? 1) === 1; + $customerDirection = (int) ($order->direction ?? 0); + $fromCart = (bool) ($payload['from_cart'] ?? $this->isCartOrder($order)); + + $product = $sync->resolveOrderProduct($order, $payload); + $number = (float) ($payload['number'] ?? 0); + $amount = (float) ($order->amount ?? 0); + $totalPrice = (float) ($order->total_price ?? $order->payment_amount ?? 0); + $unitPrice = (float) ($order->price ?? 0); + + $isCountBased = $product + ? self::isCountBasedProduct(self::productUnitPayload($product)) + : false; + + $count = 1; + $weight = $amount; + if ($isCountBased && $number > 0) { + $count = max(1, (int) round($number)); + $weight = $amount; + } elseif ($number > 0 && abs($number - $amount) > 0.0001) { + $count = max(1, (int) round($number)); + } + + $itemId = $sync->pargarItemIdGold(); + $itemName = $this->pargarItemName($order); + if ($product && trim((string) $product->title) !== '') { + $itemName = trim((string) $product->title); + } + + $tradedProductId = (int) ($payload['product_id'] ?? $order->product_id ?? 0); + $tradesMainGold = $tradedProductId <= 0 + || app(ConfigService::class)->isMainWalletProduct($tradedProductId); + + // اثر مانده از روش پرداخت؛ وزن طلا فقط وقتی کالای معامله یا پرداخت به کیف اصلی مربوط است + if ($paymentKind === PargarSyncService::PAYMENT_CASH) { + if ($tradesMainGold) { + // خرید/فروش آبشده کیف‌اصلی با نقدی: وزن + مبلغ + $itemId = $sync->pargarItemIdGold(); + } else { + // محصول غیر اصلی با پرداخت نقدی: فقط ریال + $rialItemId = $sync->pargarItemIdRial(); + $itemId = $rialItemId > 0 ? $rialItemId : $sync->pargarItemIdGold(); + $weight = 0; + $count = 1; + } + } elseif ($paymentKind === PargarSyncService::PAYMENT_MAIN_GOLD) { + $itemId = $sync->pargarItemIdGold(); + $itemName = $sync->mainWalletGoldItemName(); + $paymentGold = (float) ($payload['payment_gold'] ?? $amount); + $weight = abs($paymentGold); + $totalPrice = 0; + } + + return [ + 'OrderId' => (string) $order->id, + 'OrderType' => $isCustomerBuy ? -1 : 1, + 'PriceType' => 1, + 'CustomerAccountId' => (int) ($customer->pargar_account_id ?? 0), + 'ItemId' => $itemId, + 'ItemName' => $itemName, + 'OrderDate' => $order->pay_at + ? Jalalian::fromDateTime($order->pay_at)->format('Y/m/d-H:i:s') + : Jalalian::fromDateTime($order->created_at)->format('Y/m/d-H:i:s'), + 'Fee' => $unitPrice, + 'Count' => $count, + 'Weight' => $weight, + 'Price' => $totalPrice, + 'Carat' => 750, + 'Description' => self::accountingShopTradeDesc($customerDirection, $fromCart), + ]; + } + + /** + * @return array|null + */ + private function walletExportPayload(PargarSyncLog $log): ?array + { + $sync = $this->sync(); + $payload = json_decode((string) $log->data, true) ?: []; + $kind = (string) ($payload['kind'] ?? ($log->type === PargarSyncService::TYPE_WALLET_GOLD ? 'gold' : 'cash')); + $signed = (float) ($payload['signed_amount'] ?? $log->amount ?? 0); + if (abs($signed) < ($kind === 'cash' ? 1 : 0.01)) { + $signed = (float) ($log->amount ?? 0); + // اگر علامت نبود، از desc حدس بزن — پیش‌فرض واریز مثبت + if (! isset($payload['signed_amount']) && is_string($log->desc) && str_contains($log->desc, 'برداشت')) { + $signed = -abs($signed); + } elseif (! isset($payload['signed_amount'])) { + $signed = abs($signed); + } + } + + $customer = Customer::find((int) ($log->customer_id ?? 0)); + $withdrawId = (int) ($log->withdraw_id ?? 0); + $orderId = $withdrawId > 0 ? 'W-' . $withdrawId : 'WLOG-' . $log->id; + + $isDeposit = $signed >= 0; + // OrderType از دید فروشگاه: واریز به مشتری = فروشگاه بدهکار؟ قرارداد قبلی خرید مشتری = -1 + // واریز ریالی مشتری ≈ افزایش بدهی فروشگاه به مشتری از نظر حساب → مشابه خرید نیست؛ + // برای واریز: OrderType=1 (ورود وجه به حساب مشتری در پرگار بسته به نرم‌افزار) + // هم‌راستا با آذین: اسناد کیف را با OrderType بر اساس علامت می‌فرستیم + $orderType = $isDeposit ? 1 : -1; + + if ($kind === 'gold') { + return [ + 'OrderId' => $orderId, + 'OrderType' => $orderType, + 'PriceType' => 1, + 'CustomerAccountId' => (int) ($customer->pargar_account_id ?? 0), + 'ItemId' => $sync->pargarItemIdGold(), + 'ItemName' => $sync->mainWalletGoldItemName(), + 'OrderDate' => Jalalian::fromDateTime($log->date ?? $log->created_at)->format('Y/m/d-H:i:s'), + 'Fee' => 0, + 'Count' => 1, + 'Weight' => abs($signed), + 'Price' => 0, + 'Carat' => 750, + 'Description' => $isDeposit ? 'شارژ طلای کیف اصلی' : 'برداشت طلای کیف اصلی', + ]; + } + + $rialItemId = $sync->pargarItemIdRial(); + + return [ + 'OrderId' => $orderId, + 'OrderType' => $orderType, + 'PriceType' => 1, + 'CustomerAccountId' => (int) ($customer->pargar_account_id ?? 0), + 'ItemId' => $rialItemId > 0 ? $rialItemId : $sync->pargarItemIdGold(), + 'ItemName' => $isDeposit ? 'واریز ریالی' : 'برداشت ریالی', + 'OrderDate' => Jalalian::fromDateTime($log->date ?? $log->created_at)->format('Y/m/d-H:i:s'), + 'Fee' => 0, + 'Count' => 1, + 'Weight' => 0, + 'Price' => abs($signed), + 'Carat' => 750, + 'Description' => $isDeposit ? 'واریز ریالی' : 'برداشت ریالی', + ]; + } } diff --git a/app/Http/Controllers/Single/SingleOrderController.php b/app/Http/Controllers/Single/SingleOrderController.php index ce426cb..f6384ce 100644 --- a/app/Http/Controllers/Single/SingleOrderController.php +++ b/app/Http/Controllers/Single/SingleOrderController.php @@ -208,7 +208,30 @@ class SingleOrderController extends Controller if ($config->isPargarAccounting()) { try { - app(\App\Services\PargarSyncService::class)->recordTrade($data['order'] ?? null); + $paymentProductId = (int) ($data['payment_product_id'] ?? 0); + $paymentKind = app(\App\Services\PargarSyncService::class) + ->inferPaymentKindFromContext([ + 'payment_product_id' => $paymentProductId, + ]); + // خرید/فروش عادی از کیف ریالی (بدون payment_product_id) = نقدی + if ($paymentProductId <= 0) { + $paymentKind = \App\Services\PargarSyncService::PAYMENT_CASH; + } + $paymentGold = null; + if ($paymentKind === \App\Services\PargarSyncService::PAYMENT_MAIN_GOLD) { + $paymentGold = (float) ($data['total_amount'] ?? $data['amount'] ?? 0); + } + app(\App\Services\PargarSyncService::class)->recordTrade( + $data['order'] ?? null, + false, + [ + 'payment_kind' => $paymentKind, + 'payment_product_id' => $paymentProductId > 0 ? $paymentProductId : null, + 'product_id' => (int) ($data['product_id'] ?? 0) ?: null, + 'number' => isset($data['number']) ? (float) $data['number'] : null, + 'payment_gold' => $paymentGold, + ] + ); } catch (\Throwable $e) { report($e); } diff --git a/app/Http/Controllers/pay/paypingController.php b/app/Http/Controllers/pay/paypingController.php index 76c3104..11e8d6c 100644 --- a/app/Http/Controllers/pay/paypingController.php +++ b/app/Http/Controllers/pay/paypingController.php @@ -182,6 +182,21 @@ class paypingController extends Controller 'active' => true, ], 3, false, true); + try { + app(\App\Services\PargarSyncService::class)->queueWalletDocument( + $User, + 'cash', + (float) ($GTransaction['amount'] * 10), + [ + 'withdraw_id' => $Withdraw->id, + 'note' => 'واریز درگاه', + 'source' => 'gateway', + ] + ); + } catch (\Throwable $e) { + report($e); + } + $message = 'کاربر ' . $Withdraw->customer->name . ' با شماره موبایل ' . $Withdraw->customer->mobile . ' حساب خود را به مقدار ' . number_format($Withdraw->cash) . ' ریال شارژ کرد. با تشکر، ' . $this->Config('name_fa'); @@ -238,6 +253,21 @@ class paypingController extends Controller 'active' => true, ], 3, false, true); + try { + app(\App\Services\PargarSyncService::class)->queueWalletDocument( + $User, + 'cash', + (float) ($decodedResponse['data']['amount'] * 10), + [ + 'withdraw_id' => (int) $Withdraw->id, + 'note' => 'واریز درگاه', + 'source' => 'gateway', + ] + ); + } catch (\Throwable $e) { + report($e); + } + if ($this->cfg()->getBool('payping_fin_sync_enabled', (bool) config('payment.payping_fin_sync_enabled', false))) { $finUrl = $this->cfg()->getString('payping_fin_url', 'https://limangold.ir/kernel/admin/fin'); $finToken = $this->cfg()->getString('payping_fin_token', ''); diff --git a/app/Services/PargarSyncService.php b/app/Services/PargarSyncService.php index dbedc5b..048c5ea 100644 --- a/app/Services/PargarSyncService.php +++ b/app/Services/PargarSyncService.php @@ -5,6 +5,7 @@ namespace App\Services; use App\Models\Customer; use App\Models\Order; use App\Models\PargarSyncLog; +use App\Models\Product; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Schema; @@ -14,6 +15,17 @@ class PargarSyncService public const STATUS_SUCCESS = 'success'; public const STATUS_FAILED = 'failed'; + public const TYPE_CUSTOMER = 'customer'; + public const TYPE_ORDER = 'order'; + public const TYPE_MANDE = 'mande'; + public const TYPE_DOCUMENT = 'document'; + public const TYPE_WALLET_CASH = 'wallet_cash'; + public const TYPE_WALLET_GOLD = 'wallet_gold'; + + public const PAYMENT_CASH = 'cash'; + public const PAYMENT_MAIN_GOLD = 'main_gold'; + public const PAYMENT_OTHER_PRODUCT = 'other_product'; + public function buildLog(mixed $response = null, ?string $extra = null): string { $parts = array_filter([ @@ -93,7 +105,7 @@ class PargarSyncService { $existing = PargarSyncLog::query() ->where('customer_id', $customer->id) - ->where('type', 'customer') + ->where('type', self::TYPE_CUSTOMER) ->where('sync_status', self::STATUS_PENDING) ->latest('id') ->first(); @@ -105,7 +117,7 @@ class PargarSyncService return PargarSyncLog::create([ 'customer_id' => $customer->id, 'desc' => 'در انتظار دریافت توسط پرگار', - 'type' => 'customer', + 'type' => self::TYPE_CUSTOMER, 'sync_status' => self::STATUS_PENDING, 'data' => json_encode(['handler' => 'exportCustomer'], JSON_UNESCAPED_UNICODE), ]); @@ -132,7 +144,84 @@ class PargarSyncService ]; } - public function recordTrade(?Order $order, bool $fromCart = false): ?PargarSyncLog + /** + * صف سند کیف برای سینک مانده پرگار با اپ. + * + * @param 'cash'|'gold' $kind + * @param array{withdraw_id?:int|null,note?:string|null,product_id?:int|null,source?:string|null} $meta + */ + public function queueWalletDocument( + Customer $customer, + string $kind, + float $signedAmount, + array $meta = [] + ): ?PargarSyncLog { + if (! $this->isSyncEnabled() || ! $this->hasSyncTable()) { + return null; + } + + if (abs($signedAmount) < ($kind === 'cash' ? 1 : 0.01)) { + return null; + } + + if ($kind === 'gold') { + $productId = (int) ($meta['product_id'] ?? 0); + if ($productId > 0 && ! app(ConfigService::class)->isMainWalletProduct($productId)) { + return null; + } + } elseif ($kind !== 'cash') { + return null; + } + + $withdrawId = isset($meta['withdraw_id']) ? (int) $meta['withdraw_id'] : null; + $type = $kind === 'cash' ? self::TYPE_WALLET_CASH : self::TYPE_WALLET_GOLD; + + if ($withdrawId) { + $existing = PargarSyncLog::query() + ->where('withdraw_id', $withdrawId) + ->where('type', $type) + ->latest('id') + ->first(); + if ($existing) { + return $existing; + } + } + + $isDeposit = $signedAmount > 0; + if ($kind === 'cash') { + $desc = $isDeposit ? 'واریز ریالی — ارسال به پرگار' : 'برداشت ریالی — ارسال به پرگار'; + } else { + $desc = $isDeposit ? 'شارژ طلای کیف اصلی — ارسال به پرگار' : 'برداشت طلای کیف اصلی — ارسال به پرگار'; + } + + return PargarSyncLog::create([ + 'customer_id' => $customer->id, + 'amount' => abs($signedAmount), + 'desc' => $desc, + 'withdraw_id' => $withdrawId, + 'date' => now(), + 'type' => $type, + 'sync_status' => self::STATUS_PENDING, + 'data' => json_encode([ + 'handler' => 'exportWallet', + 'kind' => $kind, + 'signed_amount' => $signedAmount, + 'note' => $meta['note'] ?? null, + 'product_id' => $meta['product_id'] ?? null, + 'source' => $meta['source'] ?? null, + ], JSON_UNESCAPED_UNICODE), + ]); + } + + /** + * @param array{ + * payment_kind?: string, + * payment_product_id?: int|null, + * product_id?: int|null, + * number?: float|null + * } $context + */ + public function recordTrade(?Order $order, bool $fromCart = false, array $context = []): ?PargarSyncLog { if (! $order || ! $order->id || ! $this->isSyncEnabled()) { return null; @@ -146,29 +235,123 @@ class PargarSyncService $direction = (int) ($order->direction ?? 0); $desc = \App\Http\Controllers\Controller::accountingShopTradeDesc($direction, $fromCart); + $paymentKind = $context['payment_kind'] + ?? $this->inferPaymentKindFromContext($context); + + $payload = [ + 'handler' => 'exportOrder', + 'direction' => $direction, + 'price' => $order->price, + 'total_price' => $order->total_price, + 'payment_kind' => $paymentKind, + 'payment_product_id' => $context['payment_product_id'] ?? null, + 'product_id' => $context['product_id'] ?? $order->product_id ?? null, + 'number' => $context['number'] ?? null, + 'payment_gold' => $context['payment_gold'] ?? null, + 'from_cart' => $fromCart, + ]; + + // تهاتر بین دو محصول غیر اصلی — روی ریال/طلای پرگار اثر ندارد + if ($paymentKind === self::PAYMENT_OTHER_PRODUCT) { + $order->pargar_confirmed_at = now(); + $order->save(); + + return PargarSyncLog::create([ + 'customer_id' => $order->customer_id, + 'amount' => $order->amount, + 'desc' => $desc . ' (تهاتر محصول — بدون سند ریال/طلای پرگار)', + 'order_id' => $order->id, + 'date' => $order->pay_at ?? $order->created_at, + 'type' => self::TYPE_ORDER, + 'sync_status' => self::STATUS_SUCCESS, + 'pargar_ref' => 'skip:barter', + 'data' => json_encode($payload, JSON_UNESCAPED_UNICODE), + ]); + } + return PargarSyncLog::create([ 'customer_id' => $order->customer_id, 'amount' => $order->amount, 'desc' => $desc, 'order_id' => $order->id, 'date' => $order->pay_at ?? $order->created_at, - 'type' => 'order', + 'type' => self::TYPE_ORDER, 'sync_status' => self::STATUS_PENDING, - 'data' => json_encode([ - 'handler' => 'exportOrder', - 'direction' => $direction, - 'price' => $order->price, - 'total_price' => $order->total_price, - ], JSON_UNESCAPED_UNICODE), + 'data' => json_encode($payload, JSON_UNESCAPED_UNICODE), ]); } + /** + * @param array{payment_kind?: string, payment_product_id?: int|null} $context + */ + public function inferPaymentKindFromContext(array $context): string + { + if (! empty($context['payment_kind'])) { + return (string) $context['payment_kind']; + } + + $paymentProductId = (int) ($context['payment_product_id'] ?? 0); + if ($paymentProductId <= 0) { + return self::PAYMENT_CASH; + } + + return app(ConfigService::class)->isMainWalletProduct($paymentProductId) + ? self::PAYMENT_MAIN_GOLD + : self::PAYMENT_OTHER_PRODUCT; + } + public function confirmOrders(array $orderIds): array { $confirmed = 0; foreach ($orderIds as $orderId) { - $order = Order::find($orderId); + $id = trim((string) $orderId); + if ($id === '') { + continue; + } + + if (str_starts_with(strtoupper($id), 'W-')) { + $withdrawId = (int) substr($id, 2); + if ($withdrawId <= 0) { + continue; + } + + $log = PargarSyncLog::query() + ->where('withdraw_id', $withdrawId) + ->whereIn('type', [self::TYPE_WALLET_CASH, self::TYPE_WALLET_GOLD]) + ->where('sync_status', self::STATUS_PENDING) + ->latest('id') + ->first(); + + if ($log) { + $this->markSuccess($log, $id, ['OrderId' => $id]); + $confirmed++; + } + + continue; + } + + if (str_starts_with(strtoupper($id), 'WLOG-')) { + $logId = (int) substr($id, 5); + if ($logId <= 0) { + continue; + } + + $log = PargarSyncLog::query() + ->where('id', $logId) + ->whereIn('type', [self::TYPE_WALLET_CASH, self::TYPE_WALLET_GOLD]) + ->where('sync_status', self::STATUS_PENDING) + ->first(); + + if ($log) { + $this->markSuccess($log, $id, ['OrderId' => $id]); + $confirmed++; + } + + continue; + } + + $order = Order::find($id); if (! $order) { continue; } @@ -227,6 +410,21 @@ class PargarSyncService ]; } + if ($handler === 'exportWallet') { + PargarSyncLog::where('id', $log->id)->update([ + 'sync_status' => self::STATUS_PENDING, + 'sync_log' => null, + 'pargar_ref' => null, + ]); + $log->refresh(); + + return [ + 'ok' => true, + 'message' => 'سند کیف دوباره در صف ارسال به پرگار قرار گرفت', + 'pargar' => $log, + ]; + } + return [ 'ok' => false, 'message' => $log->sync_log ?: 'ارسال مجدد به پرگار ناموفق بود', @@ -240,11 +438,15 @@ class PargarSyncService return 'exportOrder'; } - if ($log->type === 'customer') { + if ($log->type === self::TYPE_CUSTOMER) { return 'exportCustomer'; } - if ($log->type === 'mande') { + if (in_array($log->type, [self::TYPE_WALLET_CASH, self::TYPE_WALLET_GOLD], true)) { + return 'exportWallet'; + } + + if ($log->type === self::TYPE_MANDE) { return 'importMande'; } @@ -264,4 +466,37 @@ class PargarSyncService { return Schema::hasTable('pargar_sync_logs'); } + + public function pargarItemIdRial(): int + { + $raw = trim(app(ConfigService::class)->getString('pargar_item_id_rial', '0')); + + return max(0, (int) $raw); + } + + public function pargarItemIdGold(): int + { + $raw = trim(app(ConfigService::class)->getString('pargar_item_id_gold', '11')); + $id = (int) $raw; + + return $id > 0 ? $id : 11; + } + + public function mainWalletGoldItemName(): string + { + $product = app(ConfigService::class)->mainWalletRepresentativeProduct(); + $title = trim((string) ($product?->title ?? '')); + + return $title !== '' ? $title : 'آبشده'; + } + + public function resolveOrderProduct(?Order $order, ?array $payload = null): ?Product + { + $productId = (int) ($payload['product_id'] ?? $order?->product_id ?? 0); + if ($productId <= 0) { + return null; + } + + return Product::find($productId); + } } diff --git a/database/sql/deploy_settings_const.sql b/database/sql/deploy_settings_const.sql index 9290470..2fa8388 100644 --- a/database/sql/deploy_settings_const.sql +++ b/database/sql/deploy_settings_const.sql @@ -490,6 +490,16 @@ INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `de SELECT 'pargar_api_token', '', 'توکن ثابت API پرگار', 0, 1, NULL WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'pargar_api_token' LIMIT 1); +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`) +SELECT 'pargar_item_id_rial', '0', 'کد کالای ریال پرگار', 0, 1, + 'کد کالا برای اسناد واریز/برداشت ریالی در پرگار. صفر = استفاده از کد طلا با وزن صفر.', 'number' +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'pargar_item_id_rial' LIMIT 1); + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`) +SELECT 'pargar_item_id_gold', '11', 'کد کالای طلا پرگار', 0, 1, + 'کد کالای طلا (کیف اصلی / آبشده) در پرگار برای اسناد وزن‌دار.', 'number' +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'pargar_item_id_gold' LIMIT 1); + INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) SELECT 'telegram_bot_token', '', 'توکن بات تلگرام', 0, 1, NULL WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'telegram_bot_token' LIMIT 1); @@ -915,7 +925,8 @@ UPDATE `settings` SET `type` = 'number' WHERE `key` IN ( 'fee_service_charge_percentage', 'fee_free', 'service_charge_percentage', 'montzar_product_id', 'gift_for_referrer', 'gift_for_referral', 'payping_fin_customer_id', 'tahesab_port', 'referral_limit', - 'deposit_sms_support_number' + 'deposit_sms_support_number', + 'pargar_item_id_rial', 'pargar_item_id_gold' ); -- text (بقیه — URL، توکن، کلید API، ساعت، ...) @@ -928,6 +939,7 @@ UPDATE `settings` SET `type` = 'text' WHERE `key` IN ( 'payping_start_url', 'payping_verify_url', 'payping_gateway_items', 'payping_fin_url', 'payping_fin_token', 'tahesab_host', 'tahesab_token', 'tahesab_db', 'pargar_username', 'pargar_password', 'pargar_api_token', + 'pargar_item_id_rial', 'pargar_item_id_gold', 'node_client_id', 'node_shared_secret', 'sse_path', 'tahesab_bank_account', 'telegram_bot_token', 'bale_bot_token', 'support_messenger_id', @@ -1624,7 +1636,8 @@ UPDATE `settings` SET `type` = 'number' WHERE `key` IN ( 'fee_service_charge_percentage', 'fee_free', 'service_charge_percentage', 'montzar_product_id', 'gift_for_referrer', 'gift_for_referral', 'payping_fin_customer_id', 'tahesab_port', 'referral_limit', - 'deposit_sms_support_number' + 'deposit_sms_support_number', + 'pargar_item_id_rial', 'pargar_item_id_gold' ); -- --------------------------------------------------------------------------- @@ -1643,7 +1656,8 @@ UPDATE `settings` SET `can_edit` = 0, `is_active` = 0 WHERE `key` IN ('send_to_t UPDATE `settings` SET `can_edit` = 0 WHERE `key` IN ( 'accounting_provider', 'tahesab_host', 'tahesab_port', 'tahesab_token', 'tahesab_db', 'tahesab_bank_account', - 'pargar_username', 'pargar_password', 'pargar_api_token' + 'pargar_username', 'pargar_password', 'pargar_api_token', + 'pargar_item_id_rial', 'pargar_item_id_gold' ); UPDATE `settings` SET `title_fa` = 'سیستم حسابداری', `type` = 'select', `description` = 'none,pargar,tahesab' WHERE `key` = 'accounting_provider';