337 lines
13 KiB
PHP
337 lines
13 KiB
PHP
<?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();
|
||
}
|
||
}
|