wechat = $wechat ?? new WechatPayComplaintService(); } public function configOptions(): array { return $this->configQuery() ->field('id,partner_id,mini_program_name,appid,mchid') ->order('id', 'asc') ->select() ->toArray(); } public function indexList(array $filters): array { $config = $this->resolveConfig((int) ($filters['config_id'] ?? 0)); [$startDate, $endDate] = $this->dateRange( (string) ($filters['start_date'] ?? ''), (string) ($filters['end_date'] ?? '') ); $page = max(1, (int) request()->input('page', 1)); $limit = min(50, max(1, (int) request()->input('limit', 20))); $result = $this->wechat->complaints($config, [ 'begin_date' => $startDate, 'end_date' => $endDate, 'limit' => $limit, 'offset' => ($page - 1) * $limit, ]); $rows = array_map(fn (array $row): array => $this->formatComplaint($row), $result['data'] ?? []); $this->appendRefundRemarks($rows); return [ 'data' => $rows, 'total' => (int) ($result['total_count'] ?? count($rows)), 'current_page' => $page, 'per_page' => $limit, 'config' => $this->publicConfig($config), ]; } public function detail(int $configId, string $complaintId): array { $complaintId = $this->complaintId($complaintId); $config = $this->resolveConfig($configId); $result = $this->wechat->detail($config, $complaintId); if (!empty($result['payer_phone'])) { $result['payer_phone'] = $this->wechat->decryptSensitive( (string) $result['payer_phone'], (string) $config['apiclient_key'] ); } return $this->formatComplaint($result); } public function history(int $configId, string $complaintId): array { $config = $this->resolveConfig($configId); $result = $this->wechat->history($config, $this->complaintId($complaintId)); return array_map(function (array $item): array { $item['complaint_media_list'] = array_values((array) ($item['complaint_media_list'] ?? [])); return $item; }, $result['data'] ?? []); } public function reply(array $data): array { $content = trim((string) ($data['content'] ?? '')); if ($content === '' || mb_strlen($content) > 200) { throw new ApiException('回复内容不能为空且不能超过 200 个字'); } $config = $this->resolveConfig((int) ($data['config_id'] ?? 0)); $this->wechat->reply($config, $this->complaintId((string) ($data['complaint_id'] ?? '')), $content); return []; } public function complete(array $data): array { $config = $this->resolveConfig((int) ($data['config_id'] ?? 0)); $this->wechat->complete($config, $this->complaintId((string) ($data['complaint_id'] ?? ''))); return []; } private function configQuery() { $query = Db::table('sa_wechat_mini_program')->whereNull('delete_time'); if (!$this->isSuperAdmin()) { $query->where('partner_id', $this->currentAdminId()); } return $query; } private function resolveConfig(int $configId): array { $query = $this->configQuery(); if ($configId > 0) { $query->where('id', $configId); } $config = $query->order('id', 'asc')->find(); if (!$config) { throw new ApiException($configId > 0 ? '微信小程序配置不存在或无权访问' : '请先绑定微信小程序并完善微信支付配置'); } return $config; } /** @return array{0:string,1:string} */ private function dateRange(string $startDate, string $endDate): array { if ($startDate === '' || $endDate === '') { throw new ApiException('请选择投诉查询日期范围'); } $start = DateTimeImmutable::createFromFormat('!Y-m-d', $startDate); $end = DateTimeImmutable::createFromFormat('!Y-m-d', $endDate); if (!$start || !$end || $start->format('Y-m-d') !== $startDate || $end->format('Y-m-d') !== $endDate) { throw new ApiException('投诉查询日期格式错误'); } if ($start > $end) { throw new ApiException('开始日期不能晚于结束日期'); } if ((int) $start->diff($end)->days > 30) { throw new ApiException('单次最多查询连续 31 天的投诉记录'); } return [$startDate, $endDate]; } private function complaintId(string $complaintId): string { $complaintId = trim($complaintId); if ($complaintId === '' || mb_strlen($complaintId) > 64) { throw new ApiException('投诉单号错误'); } return $complaintId; } private function formatComplaint(array $row): array { $orders = []; foreach ((array) ($row['complaint_order_info'] ?? []) as $order) { if (!is_array($order)) { continue; } if (isset($order['amount'])) { $order['amount'] = round(((float) $order['amount']) / 100, 2); } $orders[] = $order; } $row['complaint_order_info'] = $orders; $row['complaint_media_list'] = array_values((array) ($row['complaint_media_list'] ?? [])); $row['refund_remark'] = (string) ($row['refund_remark'] ?? ''); return $row; } private function appendRefundRemarks(array &$rows): void { $transactionIds = []; foreach ($rows as $row) { foreach ($row['complaint_order_info'] ?? [] as $order) { if (!empty($order['transaction_id'])) { $transactionIds[] = (string) $order['transaction_id']; } } } $transactionIds = array_values(array_unique($transactionIds)); if ($transactionIds === []) { return; } $orders = Db::table('sa_rental_order')->whereIn('transaction_id', $transactionIds) ->whereNull('delete_time')->field('transaction_id,refund_amount,remark')->select()->toArray(); $map = []; foreach ($orders as $order) { if ((float) ($order['refund_amount'] ?? 0) > 0 || trim((string) ($order['remark'] ?? '')) !== '') { $map[(string) $order['transaction_id']] = (string) (($order['remark'] ?? '') ?: '已退款'); } } foreach ($rows as &$row) { foreach ($row['complaint_order_info'] ?? [] as $order) { $transactionId = (string) ($order['transaction_id'] ?? ''); if (isset($map[$transactionId])) { $row['refund_remark'] = $map[$transactionId]; break; } } } unset($row); } private function publicConfig(array $config): array { return [ 'id' => (int) $config['id'], 'mini_program_name' => (string) $config['mini_program_name'], 'appid' => (string) $config['appid'], 'mchid' => (string) $config['mchid'], ]; } private function currentAdminId(): int { return (int) ($this->adminInfo['id'] ?? 0); } private function isSuperAdmin(): bool { return $this->currentAdminId() === 1 || (int) ($this->adminInfo['is_super'] ?? 0) === 1; } }