Files
appraisal_center_api/app/admin/controller/WechatMerchantController.php
2026-04-16 11:17:18 +08:00

337 lines
13 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace app\admin\controller;
use support\Request;
use app\common\model\WechatMerchant;
use Illuminate\Database\Capsule\Manager as DB;
use Webman\Http\UploadFile;
class WechatMerchantController
{
public function list(Request $request)
{
$page = (int)$request->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();
}
}