hasNumericIdKey($response)) { return true; } $data = $response['data'] ?? null; if (! is_array($data)) { return false; } $row = array_is_list($data) ? ($data[0] ?? null) : $data; return is_array($row) && $this->hasNumericIdKey($row); } private function hasNumericIdKey(array $row): bool { foreach (['idFactor', 'idSanad', 'pkRadifSanad', 'idHesab', 'OK'] as $key) { if (! array_key_exists($key, $row)) { continue; } $value = $row[$key]; if ($value === null || $value === '' || $value === false) { continue; } if (is_numeric($value) || (is_string($value) && trim($value) !== '')) { return true; } } return false; } public function buildLog(mixed $response = null, ?string $extra = null): string { $parts = array_filter([ $extra, is_array($response) ? json_encode($response, JSON_UNESCAPED_UNICODE) : (is_string($response) ? $response : null), ]); return implode("\n", $parts); } public function markSuccess(Zarin $row, array $response, ?array $payload = null): void { $flat = $this->flattenResponse($response); $zarinId = $flat['OK'] ?? $flat['idFactor'] ?? $flat['idSanad'] ?? $flat['pkRadifSanad'] ?? $flat['idHesab'] ?? null; $shFactor = $flat['Sh_factor'] ?? $flat['pkRadifSanad'] ?? $flat['idRadifSanad'] ?? null; $update = [ 'sh_factor' => $shFactor !== null ? (string) $shFactor : null, 'zarin_id' => $zarinId !== null ? (string) $zarinId : null, 'factor_number' => isset($flat['Factor_Number']) ? (string) $flat['Factor_Number'] : (isset($flat['idFactor']) ? (string) $flat['idFactor'] : null), 'res' => json_encode($response, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), 'sync_status' => self::STATUS_SUCCESS, 'sync_log' => null, ]; if ($payload !== null) { $update['data'] = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); } if ($this->hasSyncColumns()) { Zarin::where('id', $row->id)->update($update); } else { unset($update['sync_status'], $update['sync_log']); Zarin::where('id', $row->id)->update($update); } } public function markFailed(Zarin $row, mixed $response = null, ?string $extra = null): void { $update = [ 'res' => is_array($response) ? json_encode($response, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) : (is_string($response) ? $response : null), ]; if ($this->hasSyncColumns()) { $update['sync_status'] = self::STATUS_FAILED; $update['sync_log'] = $this->buildLog($response, $extra); } Zarin::where('id', $row->id)->update($update); } public function isFailed(Zarin $row): bool { if ($this->hasSyncColumns()) { return ($row->sync_status ?? null) === self::STATUS_FAILED || (empty($row->zarin_id) && ($row->sync_status ?? null) !== self::STATUS_SUCCESS); } return empty($row->zarin_id); } public function registerOnCreate(Customer $customer): void { if (! empty($customer->moshtari_code) || ! $this->isSyncEnabled()) { return; } $customerId = (int) $customer->id; dispatch(function () use ($customerId) { $service = app(ZarinSyncService::class); $customer = Customer::find($customerId); if ($customer === null || ! empty($customer->moshtari_code) || ! $service->isSyncEnabled()) { return; } try { $result = $service->registerCustomer($customer); if (! ($result['ok'] ?? false)) { Log::warning('Zarin auto-register on customer create failed', [ 'customer_id' => $customerId, 'message' => $result['message'] ?? null, ]); } } catch (\Throwable $e) { Log::warning('Zarin auto-register on customer create threw', [ 'customer_id' => $customerId, 'error' => $e->getMessage(), ]); } })->afterResponse(); } public function registerCustomer(Customer $customer): array { $customer->refresh(); if (! empty($customer->moshtari_code)) { return [ 'ok' => true, 'message' => 'مشتری قبلاً در زرین ثبت شده است', 'moshtari_code' => $customer->moshtari_code, ]; } $controller = app(ZarinController::class); $result = $controller->createCustomerInZarin($customer->id); $customer->refresh(); // createCustomerInZarin در موفقیت idHesab برمی‌گرداند (عدد یا رشتهٔ عددی مثل "426") // و در شکست پیام فارسی/خطا — قبلاً is_string همهٔ موفقیت‌های رشته‌ای را خطا می‌گرفت. if (! empty($customer->moshtari_code) || is_numeric($result)) { $code = $customer->moshtari_code ?: $result; return [ 'ok' => true, 'message' => 'مشتری با موفقیت در زرین ثبت شد', 'moshtari_code' => $code, ]; } return [ 'ok' => false, 'message' => is_string($result) && $result !== '' ? $result : 'ثبت مشتری در زرین ناموفق بود', ]; } public function retry(Zarin $row): array { if (! $this->isFailed($row) && ! empty($row->zarin_id)) { return ['ok' => false, 'message' => 'این سند قبلاً در زرین ثبت شده است']; } $payload = json_decode((string) $row->data, true) ?: []; $handler = $payload['handler'] ?? $this->inferHandler($row); $controller = app(ZarinController::class); $result = match ($handler) { 'wallet' => $controller->wallet(array_merge($payload, ['_retry_zarin_id' => $row->id]), $row), 'walletGold' => $controller->walletGold(array_merge($payload, ['_retry_zarin_id' => $row->id]), $row), 'storeTrade' => $controller->storeTrade(array_merge($payload, ['_retry_zarin_id' => $row->id]), $row), 'goldReward' => $controller->goldReward(array_merge($payload, ['_retry_zarin_id' => $row->id]), $row), default => false, }; $row->refresh(); if ($result !== false && ! empty($row->zarin_id)) { return ['ok' => true, 'message' => 'سند با موفقیت در زرین ثبت شد', 'zarin' => $row]; } return [ 'ok' => false, 'message' => $row->sync_log ?: 'ارسال مجدد به زرین ناموفق بود', 'zarin' => $row, ]; } private function inferHandler(Zarin $row): string { if ($row->order_id) { return 'storeTrade'; } if ($row->type === 'ريال') { return 'wallet'; } $desc = (string) $row->desc; if (str_contains($desc, 'شارژ') || str_contains($desc, 'دشارژ') || str_contains($desc, 'برداشت از کیف') || str_contains($desc, 'شارژ کیف')) { return 'walletGold'; } // نوع تعدادی ذخیره‌شده روی ردیف sync $type = (string) $row->type; if (in_array($type, ['سکه', 'شمش', 'کارساخته'], true)) { return 'walletGold'; } return 'goldReward'; } public function isSyncEnabled(): bool { $config = app(ConfigService::class); return $config->isZarinAccounting() && trim($config->getString('zarin_api', '')) !== '' && trim($config->getString('zarin_token', '')) !== ''; } private function flattenResponse(array $response): array { $data = $response['data'] ?? null; if (is_array($data)) { $row = array_is_list($data) ? ($data[0] ?? []) : $data; if (is_array($row)) { return array_merge($response, $row); } } return $response; } private function hasSyncColumns(): bool { return Schema::hasTable('zarin') && Schema::hasColumn('zarin', 'sync_status'); } }