From 8f71d6b80fae908bb7dc8d81d3b870540346b5b6 Mon Sep 17 00:00:00 2001 From: Hankin Date: Fri, 24 Apr 2026 11:04:56 +0800 Subject: [PATCH] transfer --- app/Http/Controllers/WechatPayController.php | 21 ++++- app/Providers/RouteServiceProvider.php | 7 +- app/Services/WechatPayService.php | 96 +++++++++++++++++++- routes/pay/wechat.php | 1 + 4 files changed, 120 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/WechatPayController.php b/app/Http/Controllers/WechatPayController.php index d69b451..a359202 100644 --- a/app/Http/Controllers/WechatPayController.php +++ b/app/Http/Controllers/WechatPayController.php @@ -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); + } } diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 86adcb1..41b217d 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -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')); }); } diff --git a/app/Services/WechatPayService.php b/app/Services/WechatPayService.php index 4f324ef..b780fb1 100644 --- a/app/Services/WechatPayService.php +++ b/app/Services/WechatPayService.php @@ -1,10 +1,11 @@ $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); + } + /** * 商家批量转账到零钱 */ diff --git a/routes/pay/wechat.php b/routes/pay/wechat.php index e9777e0..e1ec653 100644 --- a/routes/pay/wechat.php +++ b/routes/pay/wechat.php @@ -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"]);