get('page', 1); $limit = (int)$request->get('limit', 15); if ($page < 1) $page = 1; if ($limit < 1) $limit = 15; $query = WechatMerchant::query(); if (($name = trim((string)$request->get('name', ''))) !== '') { $query->where('name', 'like', "%{$name}%"); } if (($mchId = trim((string)$request->get('mch_id', ''))) !== '') { $query->where('mch_id', 'like', "%{$mchId}%"); } if ($request->get('status') !== null && $request->get('status') !== '') { $query->where('status', (int)$request->get('status')); } $total = $query->count(); $list = $query->select([ 'id', 'name', 'mode', 'mch_id', 'app_id', 'sub_mch_id', 'sub_app_id', 'service_provider', 'serial_no', 'notify_url', 'is_default', 'status', 'remark', 'apiclient_cert_path', 'apiclient_key_path', 'created_at', ]) ->selectRaw("IF(api_v3_key IS NULL OR api_v3_key = '', 0, 1) as has_api_v3_key") ->selectRaw("IF((private_key_pem IS NULL OR private_key_pem = '') AND (apiclient_key_path IS NULL OR apiclient_key_path = ''), 0, 1) as has_private_key") ->selectRaw("IF(apiclient_cert_path IS NULL OR apiclient_cert_path = '', 0, 1) as has_apiclient_cert") ->orderByDesc('is_default') ->orderByDesc('id') ->offset(($page - 1) * $limit) ->limit($limit) ->get(); return jsonResponse([ 'total' => $total, 'list' => $list, ]); } public function create(Request $request) { $name = trim((string)$request->post('name', '')); $mode = trim((string)$request->post('mode', 'direct')); $mchId = trim((string)$request->post('mch_id', '')); $appId = trim((string)$request->post('app_id', '')); $subMchId = trim((string)$request->post('sub_mch_id', '')); $subAppId = trim((string)$request->post('sub_app_id', '')); $serviceProvider = trim((string)$request->post('service_provider', '')); $serialNo = trim((string)$request->post('serial_no', '')); $apiV3Key = trim((string)$request->post('api_v3_key', '')); $privateKeyPem = trim((string)$request->post('private_key_pem', '')); $notifyUrl = trim((string)$request->post('notify_url', '')); $remark = trim((string)$request->post('remark', '')); $status = (int)$request->post('status', 1); $isDefault = (int)$request->post('is_default', 0) ? 1 : 0; if ($name === '' || $mchId === '') { return jsonResponse(null, '名称和商户号必填', 400); } if (!in_array($mode, ['direct', 'service_provider', 'third_party'], true)) { return jsonResponse(null, '商户类型不合法', 400); } if ($mode === 'service_provider' && $subMchId === '') { return jsonResponse(null, '服务商模式必须填写子商户号', 400); } if ($this->existsConflict(0, $mode, $mchId, $subMchId, $appId, $subAppId)) { return jsonResponse(null, '该商户配置已存在', 400); } DB::beginTransaction(); try { if ($isDefault === 1) { WechatMerchant::where('is_default', 1)->update(['is_default' => 0]); } $row = WechatMerchant::create([ 'name' => $name, 'mode' => $mode, 'mch_id' => $mchId, 'app_id' => $appId ?: null, 'serial_no' => $serialNo ?: null, 'api_v3_key' => $apiV3Key ?: null, 'private_key_pem' => $privateKeyPem ?: null, 'notify_url' => $notifyUrl ?: null, 'sub_mch_id' => $subMchId ?: null, 'sub_app_id' => $subAppId ?: null, 'service_provider' => $serviceProvider ?: null, 'remark' => $remark ?: null, 'status' => $status ? 1 : 0, 'is_default' => $isDefault, ]); DB::commit(); return jsonResponse($row, '创建成功'); } catch (\Throwable $e) { DB::rollBack(); return jsonResponse(null, '创建失败: ' . $e->getMessage(), 500); } } public function update(Request $request) { $id = (int)$request->post('id'); $row = WechatMerchant::find($id); if (!$row) { return jsonResponse(null, '商户号不存在', 404); } $name = trim((string)$request->post('name', $row->name)); $mode = trim((string)$request->post('mode', $row->mode)); $mchId = trim((string)$request->post('mch_id', $row->mch_id)); $appId = trim((string)$request->post('app_id', $row->app_id ?? '')); $subMchId = trim((string)$request->post('sub_mch_id', $row->sub_mch_id ?? '')); $subAppId = trim((string)$request->post('sub_app_id', $row->sub_app_id ?? '')); $serviceProvider = trim((string)$request->post('service_provider', $row->service_provider ?? '')); $serialNo = trim((string)$request->post('serial_no', $row->serial_no ?? '')); $apiV3Key = trim((string)$request->post('api_v3_key', '')); $privateKeyPem = trim((string)$request->post('private_key_pem', '')); $notifyUrl = trim((string)$request->post('notify_url', $row->notify_url ?? '')); $remark = trim((string)$request->post('remark', $row->remark ?? '')); $status = (int)$request->post('status', $row->status); $isDefault = $request->post('is_default') !== null ? ((int)$request->post('is_default') ? 1 : 0) : (int)$row->is_default; if ($name === '' || $mchId === '') { return jsonResponse(null, '名称和商户号必填', 400); } if (!in_array($mode, ['direct', 'service_provider', 'third_party'], true)) { return jsonResponse(null, '商户类型不合法', 400); } if ($mode === 'service_provider' && $subMchId === '') { return jsonResponse(null, '服务商模式必须填写子商户号', 400); } if ($this->existsConflict($id, $mode, $mchId, $subMchId, $appId, $subAppId)) { return jsonResponse(null, '该商户配置已存在', 400); } DB::beginTransaction(); try { if ($isDefault === 1) { WechatMerchant::where('is_default', 1)->where('id', '<>', $id)->update(['is_default' => 0]); } $row->name = $name; $row->mode = $mode; $row->mch_id = $mchId; $row->app_id = $appId ?: null; $row->serial_no = $serialNo ?: null; if ($apiV3Key !== '') { $row->api_v3_key = $apiV3Key; } if ($privateKeyPem !== '') { $row->private_key_pem = $privateKeyPem; } $row->notify_url = $notifyUrl ?: null; $row->sub_mch_id = $subMchId ?: null; $row->sub_app_id = $subAppId ?: null; $row->service_provider = $serviceProvider ?: null; $row->remark = $remark ?: null; $row->status = $status ? 1 : 0; $row->is_default = $isDefault; $row->save(); DB::commit(); return jsonResponse(null, '更新成功'); } catch (\Throwable $e) { DB::rollBack(); return jsonResponse(null, '更新失败: ' . $e->getMessage(), 500); } } public function delete(Request $request) { $id = (int)$request->post('id'); $row = WechatMerchant::find($id); if (!$row) { return jsonResponse(null, '商户号不存在', 404); } if ((int)$row->is_default === 1) { return jsonResponse(null, '默认商户号不可删除', 400); } $row->delete(); return jsonResponse(null, '删除成功'); } public function uploadApiclientCert(Request $request) { return $this->uploadPem($request, 'apiclient_cert'); } public function uploadApiclientKey(Request $request) { return $this->uploadPem($request, 'apiclient_key'); } public function uploadApiV3Key(Request $request) { $id = (int)$request->post('id'); $row = WechatMerchant::find($id); if (!$row) { return jsonResponse(null, '商户号不存在', 404); } /** @var UploadFile|null $file */ $file = $request->file('file'); if (!$file || !$file->isValid()) { return jsonResponse(null, '未找到文件或文件无效', 400); } $size = $file->getSize(); if ($size === false || $size > 1024) { return jsonResponse(null, '文件过大', 400); } $content = file_get_contents($file->getPathname()); $content = is_string($content) ? $content : ''; $key = preg_replace('/\s+/', '', $content); $key = is_string($key) ? trim($key) : ''; if ($key === '' || strlen($key) !== 32) { return jsonResponse(null, 'APIv3 Key 格式不正确(应为32位)', 400); } $row->api_v3_key = $key; $row->save(); return jsonResponse([ 'id' => $row->id, 'has_api_v3_key' => 1, ], '上传成功'); } private function uploadPem(Request $request, string $type) { $id = (int)$request->post('id'); $row = WechatMerchant::find($id); if (!$row) { return jsonResponse(null, '商户号不存在', 404); } /** @var UploadFile|null $file */ $file = $request->file('file'); if (!$file || !$file->isValid()) { return jsonResponse(null, '未找到文件或文件无效', 400); } $ext = strtolower($file->getUploadExtension()); if ($ext !== 'pem') { return jsonResponse(null, '仅支持 pem 文件', 400); } $dir = runtime_path() . '/wechatpay/merchants/' . $row->id; if (!is_dir($dir)) { mkdir($dir, 0700, true); } $filename = $type === 'apiclient_cert' ? 'apiclient_cert.pem' : 'apiclient_key.pem'; $path = $dir . '/' . $filename; $file->move($path); @chmod($path, 0600); if ($type === 'apiclient_cert') { $row->apiclient_cert_path = $path; try { $certPem = file_get_contents($path); $x509 = openssl_x509_read($certPem); if ($x509) { $info = openssl_x509_parse($x509); $serialHex = $info['serialNumberHex'] ?? ''; if (is_string($serialHex) && $serialHex !== '') { $row->serial_no = $row->serial_no ?: strtoupper($serialHex); } } } catch (\Throwable $e) { } } else { $row->apiclient_key_path = $path; } $row->save(); return jsonResponse([ 'id' => $row->id, 'serial_no' => $row->serial_no, 'has_apiclient_cert' => $row->apiclient_cert_path ? 1 : 0, 'has_private_key' => ($row->private_key_pem || $row->apiclient_key_path) ? 1 : 0, ], '上传成功'); } private function existsConflict(int $id, string $mode, string $mchId, string $subMchId, string $appId, string $subAppId): bool { $query = WechatMerchant::query()->where('mode', $mode)->where('mch_id', $mchId); if ($mode === 'service_provider') { $query->where('sub_mch_id', $subMchId); } else { $query->where(function ($q) { $q->whereNull('sub_mch_id')->orWhere('sub_mch_id', ''); }); } if ($appId !== '') { $query->where('app_id', $appId); } else { $query->where(function ($q) { $q->whereNull('app_id')->orWhere('app_id', ''); }); } if ($subAppId !== '') { $query->where('sub_app_id', $subAppId); } else { $query->where(function ($q) { $q->whereNull('sub_app_id')->orWhere('sub_app_id', ''); }); } if ($id > 0) { $query->where('id', '<>', $id); } return $query->exists(); } }