This commit is contained in:
Hankin 2026-04-24 11:04:56 +08:00
parent 4e24c65aa4
commit 8f71d6b80f
4 changed files with 120 additions and 5 deletions

View File

@ -7,6 +7,7 @@ use App\Http\Response\ResponseJson;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Log;
use TypeError;
class WechatPayController extends Controller
@ -19,7 +20,8 @@ class WechatPayController extends Controller
"scene_id" => "string",
"openid" => "required|string",
"amount" => "required|integer",
"remark" => "required|string"
"remark" => "required|string",
"notify_url" => "required|url"
];
$input = $request->all();
$validator = Validator::make($input, $rules, $messages = [
@ -39,8 +41,23 @@ class WechatPayController extends Controller
$openid = $request->openid;
$amount = $request->amount;
$remark = $request->remark;
$res = WechatPayService::mchTransfer($trade_no, $scene_id, $openid, $amount, $remark, []);
$notify_url = $request->notify_url;
$res = WechatPayService::mchTransfer($trade_no, $scene_id, $openid, $amount, $remark, $notify_url, []);
return $this->success("ok", $res);
}
function mchTransferCallback(Request $request)
{
$headers = [
'wechatpay-timestamp' => $_SERVER['HTTP_WECHATPAY_TIMESTAMP'] ?? '',
'wechatpay-nonce' => $_SERVER['HTTP_WECHATPAY_NONCE'] ?? '',
'wechatpay-signature' => $_SERVER['HTTP_WECHATPAY_SIGNATURE'] ?? '',
'wechatpay-serial' => $_SERVER['HTTP_WECHATPAY_SERIAL'] ?? '',
];
$body = file_get_contents('php://input');
$res = WechatPayService::mchTransferCallback($headers, $body);
Log::info("解密数据", ["data" => $res]);
return $this->success("ok", $res);
}
}

View File

@ -40,8 +40,13 @@ class RouteServiceProvider extends ServiceProvider
->prefix("util/api")
->group(base_path('routes/util/api.php'));
$utilPrefix = "api/wechatpay";
if (config("app.env") == "local") {
$utilPrefix = "util/api/wechatpay";
}
Route::middleware('api')
->prefix("api/wechatpay")
->prefix($utilPrefix)
->group(base_path('routes/pay/wechat.php'));
});
}

View File

@ -1,10 +1,11 @@
<?php
namespace App\Services;
use Illuminate\Auth\Events\Login;
use Exception;
use Illuminate\Support\Facades\Log;
use WeChatPay\Builder;
use WeChatPay\BuilderChainable;
use WeChatPay\Crypto\AesGcm;
use WeChatPay\Crypto\Rsa;
class WechatPayService
@ -59,7 +60,7 @@ class WechatPayService
* 商家转账
* @return void
*/
public function mchTransfer(string $trade_no, string $scene_id, string $openid, int $amount, string $remark, array $transfer_scene_report_infos = []): array
public function mchTransfer(string $trade_no, string $scene_id, string $openid, int $amount, string $remark, string $notify_url, array $transfer_scene_report_infos = []): array
{
// 发送请求
if (empty(count($transfer_scene_report_infos))) {
@ -83,6 +84,7 @@ class WechatPayService
"openid" => $openid,
"transfer_amount" => $amount,
"transfer_remark" => $remark,
"notify_url" => $notify_url,
"transfer_scene_report_infos" => $transfer_scene_report_infos
];
Log::info("转账数据", $data);
@ -105,6 +107,96 @@ class WechatPayService
// }
}
/**
* 商家转账回调
*/
public function mchTransferCallback($headers, $rawBody)
{
if (!$this->verifySignature($headers, $rawBody)) {
Log::error('微信回调验签失败', ['headers' => $headers]);
return null; // 返回 500 让微信重试
}
// 4. 解密回调数据
$decryptData = $this->decryptNotifyData($rawBody);
if (!$decryptData) {
Log::error('数据解密失败');
return null;
}
return $decryptData;
}
/**
* 验证签名RSA-SHA256
*/
private function verifySignature(array $headers, string $body): bool
{
// 检查必要头是否存在
if (
empty($headers['timestamp']) || empty($headers['nonce']) ||
empty($headers['signature']) || empty($headers['serial'])
) {
return false;
}
// 检查序列号是否匹配(可选的额外校验)
// $expectedSerial = $this->getPlatformCertSerial();
// if ($headers['serial'] !== $expectedSerial) {
// return false;
// }
// 构造验签名串timestamp + \n + nonce + \n + body + \n
$signStr = $headers['timestamp'] . "\n"
. $headers['nonce'] . "\n"
. $body . "\n";
// 加载微信支付平台公钥
$publicKey = openssl_pkey_get_public('file://' . config("wechatpay.payment.platform_cert_path"));
if (!$publicKey) {
Log::error('加载平台证书失败');
return false;
}
// Base64 解码签名
$signature = base64_decode($headers['signature']);
// 验证签名
$result = openssl_verify($signStr, $signature, $publicKey, OPENSSL_ALGO_SHA256);
return $result === 1;
}
/**
* AES-256-GCM 解密回调数据
*/
private function decryptNotifyData(string $rawBody): ?array
{
$data = json_decode($rawBody, true);
if (!isset($data['resource'])) {
return null;
}
$resource = $data['resource'];
$ciphertext = base64_decode($resource['ciphertext']);
$nonce = $resource['nonce'];
$associatedData = $resource['associated_data'] ?? '';
// PHP 7.1+ 原生支持 AES-256-GCM
$decrypted = AesGcm::decrypt(
$ciphertext, // Base64解码后的密文包含tag
config("wechatpay.payment.api3_key"),
$nonce,
$associatedData
);
if ($decrypted === false) {
return null;
}
return json_decode($decrypted, true);
}
/**
* 商家批量转账到零钱
*/

View File

@ -4,3 +4,4 @@ use App\Http\Controllers\WechatPayController;
//发起转账
Route::post('saas/mch/transfer', [WechatPayController::class, "mchTransfer"])->middleware('merchant_user');
Route::post('saas/mch/transfer/callback', [WechatPayController::class, "mchTransferCallback"]);