isPargarAccounting()) { return response()->json([ 'success' => false, 'data' => null, 'message' => 'Pargar integration is disabled', ], 503); } [$username, $password] = $this->pargarLoginCredentials($request); $expectedUser = $this->normalizeCredential(trim((string) $this->Config('pargar_username'))); $expectedPass = $this->normalizeCredential(trim((string) $this->Config('pargar_password'))); if ($username !== $expectedUser || $password !== $expectedPass || $expectedUser === '') { return response()->json([ 'success' => false, 'data' => null, 'message' => 'Invalid credentials!', ]); } $token = PargarAuth::issueToken($username); return response()->json([ 'success' => true, 'data' => ['token' => $token], 'message' => 'You are logged in successfully.', ]); } public function upsertCustomers(Request $request) { $payload = $this->pargarPayload($request); $customers = $payload['Customers'] ?? []; if (! is_array($customers) || $customers === []) { return response()->json([ 'success' => false, 'message' => 'Customers list is required', 'data' => null, ], 422); } $messages = []; foreach ($customers as $row) { if (! is_array($row)) { continue; } $accountId = trim((string) ($row['AccountId'] ?? '')); $fullName = trim((string) ($row['FullName'] ?? '')); $mobile = Customer::normalizeMobile((string) ($row['CellPhone'] ?? '')); if ($accountId === '' || $mobile === null) { $messages[] = 'ردیف نامعتبر: AccountId یا CellPhone خالی است'; continue; } $byAccount = Customer::whereNull('deleted_at') ->where('pargar_account_id', $accountId) ->first(); if ($byAccount && Customer::normalizeMobile($byAccount->mobile) !== $mobile) { $messages[] = "کد حساب {$accountId} قبلاً برای مشتری دیگری ثبت شده است"; continue; } $customer = Customer::findActiveByMobile($mobile); if ($customer) { $currentAccount = (string) ($customer->pargar_account_id ?? ''); if ($currentAccount === $accountId) { $messages[] = 'کد حساب مشتری وجود دارد'; continue; } if ($currentAccount !== '' && $currentAccount !== $accountId) { $messages[] = "شماره {$mobile} قبلاً به حساب پرگار دیگری متصل است"; continue; } $customer->pargar_account_id = $accountId; $this->applyPargarCustomerName($customer, $fullName); $customer->save(); $messages[] = "کد حسابداری مشتری {$fullName} ویرایش شد"; continue; } if ($byAccount) { if (Customer::isMobileTaken($mobile, (int) $byAccount->id)) { $messages[] = "شماره {$mobile} تکراری است"; continue; } if ($fullName !== '') { $this->applyPargarCustomerName($byAccount, $fullName); } $byAccount->mobile = $mobile; if (empty($byAccount->password)) { $byAccount->password = $mobile; } $byAccount->save(); $messages[] = "مشتری با کد حسابداری {$accountId} به‌روزرسانی شد"; continue; } try { $customer = new Customer(); $this->applyPargarCustomerName($customer, $fullName !== '' ? $fullName : 'مشتری'); $customer->mobile = $mobile; $customer->pargar_account_id = $accountId; $customer->active = true; $customer->type = 2; $customer->password = $mobile; $customer->referral_id = $this->uniqueReferralId(); $customer->save(); $messages[] = "مشتری با کد حسابداری {$accountId} ایجاد شد"; } catch (\Throwable $e) { $messages[] = "خطا برای {$mobile}: " . ( $e instanceof UnprocessableEntityHttpException ? $e->getMessage() : 'ثبت مشتری ناموفق بود' ); } } return response()->json([ 'success' => true, 'message' => implode(' | ', $messages), 'data' => null, ]); } public function listOrders(Request $request) { $documents = collect(); $orders = Order::query() ->where('sts', 2) ->whereNull('pargar_confirmed_at') ->limit(100) ->get(); foreach ($orders as $order) { $payload = $this->orderExportPayload($order); if ($payload === null) { continue; } $documents->push($payload); } $walletLogs = PargarSyncLog::query() ->whereIn('type', [PargarSyncService::TYPE_WALLET_CASH, PargarSyncService::TYPE_WALLET_GOLD]) ->where('sync_status', PargarSyncService::STATUS_PENDING) ->orderBy('id') ->limit(100) ->get(); foreach ($walletLogs as $log) { $payload = $this->walletExportPayload($log); if ($payload !== null) { $documents->push($payload); } } return response()->json([ 'state' => 'true', 'msg' => '', 'data' => [ 'documents' => $documents->values(), 'duplicated' => 'false', 'result' => 'OK', ], ]); } public function confirmOrders(Request $request) { $payload = $this->pargarPayload($request); $orderIds = $payload['OrderIds'] ?? []; if (! is_array($orderIds)) { return response()->json([ 'success' => false, 'message' => 'OrderIds must be an array', 'data' => null, ], 422); } $this->sync()->confirmOrders($orderIds); return response()->json([ 'success' => true, 'message' => 'تاییدیه اسناد با موفقیت دریافت شد', 'data' => null, ]); } public function storeDocuments(Request $request) { $data = $this->pargarPayload($request); $accountId = trim((string) ($data['accountId'] ?? '')); $transactions = $data['transactions'] ?? []; $customer = $accountId !== '' ? Customer::where('pargar_account_id', $accountId)->first() : null; PargarSyncLog::create([ 'customer_id' => $customer?->id, 'desc' => 'دریافت اسناد از پرگار', 'type' => PargarSyncService::TYPE_DOCUMENT, 'sync_status' => PargarSyncService::STATUS_SUCCESS, 'pargar_ref' => $accountId, 'data' => json_encode($data, JSON_UNESCAPED_UNICODE), ]); return response()->json([ 'success' => true, 'message' => 'اسناد مشتری با موفقیت دریافت شد', 'data' => null, ]); } public function storeMande(Request $request) { $data = $this->pargarPayload($request); $accountId = trim((string) ($data['AccountId'] ?? '')); $customer = Customer::where('pargar_account_id', $accountId)->first(); if (! $customer) { return response()->json([ 'success' => false, 'message' => 'مشتری یافت نشد', 'data' => null, ], 422); } $goldInv = isset($data['GoldInv']) ? (float) str_replace('/', '.', (string) $data['GoldInv']) : 0; $rialInv = isset($data['RialInv']) ? (float) str_replace('/', '.', (string) $data['RialInv']) : 0; // فقط مقایسه برای لاگ — اپ source of truth است؛ کیف را از مانده پرگار عوض نکن $ownerType = (int) ($customer->type ?? 2); $walletInterface = new WalletInterface($customer->id, $ownerType); $wallet = $walletInterface->wallet; $appGold = (float) ($wallet->gold ?? 0); $appCash = (float) ($wallet->cash ?? 0); $goldDiff = $goldInv - $appGold; $rialDiff = $rialInv - $appCash; $hasDiff = abs($goldDiff) >= 0.01 || abs($rialDiff) >= 1; $desc = $hasDiff ? 'اختلاف مانده پرگار با اپ — کیف اپ دست نخورده' : 'دریافت مانده از پرگار — بدون اختلاف'; PargarSyncLog::create([ 'customer_id' => $customer->id, 'desc' => $desc, 'type' => PargarSyncService::TYPE_MANDE, 'amount' => $goldInv, 'sync_status' => PargarSyncService::STATUS_SUCCESS, 'pargar_ref' => $accountId, '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([ 'success' => true, 'message' => 'مانده مشتری با موفقیت دریافت شد', 'data' => null, ]); } public function listNewCustomers(Request $request) { $customers = Customer::query() ->whereNull('deleted_at') ->where(function ($q) { $q->whereNull('pargar_account_id') ->orWhere('pargar_account_id', '') ->orWhere('pargar_account_id', '0') ->orWhere('pargar_account_id', '-1'); }) ->limit(100) ->get(['id', 'fname', 'lname', 'name', 'mobile']); if ($customers->isEmpty()) { return response()->json([ 'success' => true, 'message' => 'مشتری جدید وجود ندارد', 'data' => null, ]); } $rows = $customers->map(function (Customer $customer) { $fname = trim((string) ($customer->fname ?? '')); $lname = trim((string) ($customer->lname ?? '')); if ($fname === '' && $lname === '') { $nameParts = preg_split('/\s+/', trim((string) ($customer->name ?? '')), 2) ?: ['', '']; $fname = trim((string) ($nameParts[0] ?? '')); $lname = trim((string) ($nameParts[1] ?? '')); } return [ 'id' => (string) $customer->id, 'name' => $fname, 'family' => $lname, 'mobile' => (string) $customer->mobile, ]; }); return response()->json([ 'state' => 'true', 'msg' => '', 'customer' => $rows, ]); } public function confirmCustomers(Request $request) { $payload = $this->pargarPayload($request); $rows = $payload['customers'] ?? []; if (! is_array($rows)) { return response()->json([ 'success' => false, 'message' => 'customers must be an array', 'data' => null, ], 422); } foreach ($rows as $row) { if (! is_array($row)) { continue; } $customerId = $row['customer_id'] ?? $row['id_customer'] ?? null; $accountId = $row['accounting_id'] ?? null; if ($customerId === null || $accountId === null) { continue; } $normalizedAccountId = trim((string) $accountId); if ($normalizedAccountId === '' || in_array($normalizedAccountId, ['0', '-1'], true)) { continue; } $customer = Customer::find($customerId); if ($customer) { $customer->pargar_account_id = $normalizedAccountId; $customer->save(); $log = PargarSyncLog::query() ->where('customer_id', $customer->id) ->where('type', 'customer') ->where('sync_status', PargarSyncService::STATUS_PENDING) ->latest('id') ->first(); if ($log) { $this->sync()->markSuccess($log, $normalizedAccountId); } } } return response()->json([ 'success' => true, 'message' => 'تاییدیه ثبت مشتری با موفقیت دریافت شد', 'data' => null, ]); } /** * @return array{0: string, 1: string} */ private function pargarLoginCredentials(Request $request): array { $payload = $this->pargarPayload($request); return [ $this->normalizeCredential((string) ($payload['username'] ?? '')), $this->normalizeCredential((string) ($payload['password'] ?? '')), ]; } /** * @return array */ private function pargarPayload(Request $request): array { $payload = $request->all(); if ($payload !== []) { return $payload; } $decoded = json_decode((string) $request->getContent(), true); return is_array($decoded) ? $decoded : []; } private function normalizeCredential(string $value): string { $persian = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']; $arabic = ['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩']; $latin = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; return trim(str_replace(array_merge($persian, $arabic), array_merge($latin, $latin), $value)); } private function applyPargarCustomerName(Customer $customer, string $fullName): void { $fullName = trim($fullName); if ($fullName === '') { return; } [$fname, $lname] = $this->splitPargarFullName($fullName); $customer->fname = $fname; $customer->lname = $lname; // mutator مدل Customer مقدار name را از fname + lname می‌سازد $customer->name = trim("$fname $lname"); } /** * @return array{0: string, 1: string} */ private function splitPargarFullName(string $fullName): array { $parts = preg_split('/\s+/u', trim($fullName), 2) ?: ['', '']; return [ trim((string) ($parts[0] ?? '')), trim((string) ($parts[1] ?? '')), ]; } private function uniqueReferralId(): string { do { $referral = substr(str_shuffle('0123456789abcdefghijklmnopqrstuvwxyz'), 0, 4); } while (Customer::where('referral_id', $referral)->exists()); return $referral; } private function pargarItemName(Order $order): string { return $this->isCartOrder($order) ? 'سبد خرید' : 'آبشده'; } private function isCartOrder(Order $order): bool { if (Schema::hasTable('cart') && Schema::hasColumn('cart', 'order_id')) { return Cart::where('order_id', $order->id)->exists(); } return PargarSyncLog::where('order_id', $order->id) ->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 ? 'واریز ریالی' : 'برداشت ریالی', ]; } }