ufutx.love.util/app/Services/WechatPayService.php

255 lines
9.0 KiB
PHP
Raw Normal View History

2025-08-12 14:46:31 +08:00
<?php
namespace App\Services;
2026-04-24 11:04:56 +08:00
use Exception;
2025-08-12 14:46:31 +08:00
use Illuminate\Support\Facades\Log;
use WeChatPay\Builder;
use WeChatPay\BuilderChainable;
2026-04-24 11:04:56 +08:00
use WeChatPay\Crypto\AesGcm;
2025-08-12 14:46:31 +08:00
use WeChatPay\Crypto\Rsa;
class WechatPayService
{
/**
* 初始化一个APIv3客户端
* @return void
*/
private function newClient(): BuilderChainable
{
// 商户号
$merchantId = config("wechatpay.payment.mch_id");
// 从本地文件中加载「商户API私钥」「商户API私钥」会用来生成请求的签名
$merchantPrivateKeyFilePath = 'file://' . config("wechatpay.payment.cert_key_path");
$merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
// 「商户API证书」的「证书序列号」
$merchantCertificateSerial = config("wechatpay.payment.serial");
// 从本地文件中加载「微信支付平台证书」可由内置CLI工具下载到用来验证微信支付应答的签名
2025-08-13 11:02:48 +08:00
$platformCertificateFilePath = 'file://' . config("wechatpay.payment.platform_cert_path");
$onePlatformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);
2025-08-12 14:46:31 +08:00
// 「微信支付平台证书」的「平台证书序列号」
// 可以从「微信支付平台证书」文件解析,也可以在 商户平台 -> 账户中心 -> API安全 查询到
2025-08-13 11:02:48 +08:00
$platformCertificateSerial = config("wechatpay.payment.platform_cert_serial");
2025-08-12 14:46:31 +08:00
// // 从本地文件中加载「微信支付公钥」,用来验证微信支付应答的签名
2025-08-13 11:02:48 +08:00
// $platformPublicKeyFilePath = 'file://' . config("wechatpay.payment.public_key_path");
// $twoPlatformPublicKeyInstance = Rsa::from($platformPublicKeyFilePath, Rsa::KEY_TYPE_PUBLIC);
2025-08-12 14:46:31 +08:00
// // 「微信支付公钥」的「微信支付公钥ID」
// // 需要在 商户平台 -> 账户中心 -> API安全 查询
2025-08-13 11:02:48 +08:00
// $platformPublicKeyId = config("wechatpay.payment.public_key_id");
2025-08-12 14:46:31 +08:00
// 构造一个 APIv3 客户端实例
$instance = Builder::factory([
'mchid' => $merchantId,
'serial' => $merchantCertificateSerial,
'privateKey' => $merchantPrivateKeyInstance,
'certs' => [
2025-08-13 11:02:48 +08:00
$platformCertificateSerial => $onePlatformPublicKeyInstance,
// $platformPublicKeyId => $twoPlatformPublicKeyInstance,
2025-08-12 14:46:31 +08:00
],
2025-08-12 18:09:45 +08:00
"secret" => config("wechatpay.payment.api3_key")
2025-08-12 14:46:31 +08:00
]);
return $instance;
}
/**
2025-08-13 13:53:48 +08:00
* 商家转账
2025-08-12 14:46:31 +08:00
* @return void
*/
2026-04-24 11:04:56 +08:00
public function mchTransfer(string $trade_no, string $scene_id, string $openid, int $amount, string $remark, string $notify_url, array $transfer_scene_report_infos = []): array
2025-08-12 14:46:31 +08:00
{
// 发送请求
2025-08-12 15:04:12 +08:00
if (empty(count($transfer_scene_report_infos))) {
2025-08-12 15:09:43 +08:00
$transfer_scene_report_infos = [
[
'info_type' => '岗位类型', // 固定值
'info_content' => '商家/商户', // 示例值
],
[
'info_type' => '报酬说明', // 固定值
'info_content' => '商家/商户提现', // 示例值
],
];
2025-08-12 14:46:31 +08:00
}
2026-04-23 16:09:45 +08:00
// try {
$appid = config("wechat.official_account.default.app_id");
$data = [
"appid" => $appid,
"out_bill_no" => $trade_no,
"transfer_scene_id" => (string) $scene_id,
"openid" => $openid,
"transfer_amount" => $amount,
"transfer_remark" => $remark,
2026-04-24 11:04:56 +08:00
"notify_url" => $notify_url,
2026-04-23 16:09:45 +08:00
"transfer_scene_report_infos" => $transfer_scene_report_infos
];
Log::info("转账数据", $data);
$instance = $this->newClient();
$resp = $instance->chain('v3/fund-app/mch-transfer/transfer-bills')->post([
"json" => $data
]);
$res = json_decode($resp->getBody(), true);
return ["code" => 0, "err_msg" => "", "data" => $res];
// } catch (\Exception $e) {
// // 进行异常捕获并进行错误判断处理
// Log::info($e->getMessage());
// if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
// $r = $e->getResponse();
// $res = json_decode($r->getBody());
// return ["code" => 1, "err_msg" => $r->getBody(), "data" => null];
// }
2025-08-12 14:46:31 +08:00
2026-04-23 16:09:45 +08:00
// return ["code" => 1, "err_msg" => $e->getMessage(), "data" => null];
// }
2025-08-12 14:46:31 +08:00
}
2026-04-24 11:04:56 +08:00
/**
* 商家转账回调
*/
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
*/
2026-04-24 17:30:02 +08:00
private function verifySignature(array $headers, array $body): bool
2026-04-24 11:04:56 +08:00
{
// 检查必要头是否存在
if (
2026-04-24 17:32:25 +08:00
empty($headers['wechatpay-timestamp']) || empty($headers['wechatpay-nonce']) ||
empty($headers['wechatpay-signature']) || empty($headers['wechatpay-serial'])
2026-04-24 11:04:56 +08:00
) {
2026-04-24 17:33:51 +08:00
Log::error('请求头参数缺失');
2026-04-24 11:04:56 +08:00
return false;
}
// 检查序列号是否匹配(可选的额外校验)
// $expectedSerial = $this->getPlatformCertSerial();
// if ($headers['serial'] !== $expectedSerial) {
// return false;
// }
// 构造验签名串timestamp + \n + nonce + \n + body + \n
2026-04-24 17:32:25 +08:00
$signStr = $headers['wechatpay-timestamp'] . "\n"
. $headers['wechatpay-nonce'] . "\n"
2026-04-24 17:30:02 +08:00
. json_encode($body) . "\n";
2026-04-24 11:04:56 +08:00
// 加载微信支付平台公钥
$publicKey = openssl_pkey_get_public('file://' . config("wechatpay.payment.platform_cert_path"));
if (!$publicKey) {
Log::error('加载平台证书失败');
return false;
}
// Base64 解码签名
2026-04-24 17:32:25 +08:00
$signature = base64_decode($headers['wechatpay-signature']);
2026-04-24 11:04:56 +08:00
// 验证签名
$result = openssl_verify($signStr, $signature, $publicKey, OPENSSL_ALGO_SHA256);
2026-04-24 17:33:51 +08:00
Log::info("验签结果", ["res" => $result]);
2026-04-24 11:04:56 +08:00
return $result === 1;
}
/**
* AES-256-GCM 解密回调数据
*/
2026-04-24 17:30:02 +08:00
private function decryptNotifyData(array $data): ?array
2026-04-24 11:04:56 +08:00
{
2026-04-24 17:30:02 +08:00
// $data = json_decode($rawBody, true);
2026-04-24 11:04:56 +08:00
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);
}
2025-08-13 13:53:48 +08:00
/**
* 商家批量转账到零钱
*/
2025-08-13 13:58:48 +08:00
public function transferBatches(string $trade_no, string $trade_no2, string $openid, int $amount, string $remark): array
2025-08-13 13:53:48 +08:00
{
2025-08-13 14:00:53 +08:00
// try {
$appid = config("wechat.official_account.default.app_id");
$data = [
"appid" => $appid,
2025-08-13 14:02:24 +08:00
"out_batch_no" => $trade_no,
2025-08-13 14:00:53 +08:00
"batch_name" => $remark,
"batch_remark" => $remark,
"total_amount" => $amount,
"total_num" => 1,
"transfer_detail_list" => [
2025-08-13 14:05:56 +08:00
[
"out_detail_no" => $trade_no2,
"transfer_amount" => $amount,
"transfer_remark" => $remark,
"openid" => $openid,
]
2025-08-13 14:00:53 +08:00
]
];
Log::info("转账到零钱数据", $data);
$instance = $this->newClient();
$resp = $instance->chain('v3/transfer/batches')->post([
"json" => $data
]);
$res = json_decode($resp->getBody(), true);
dd($res);
// } catch (\Exception $e) {
// // 进行异常捕获并进行错误判断处理
// Log::info($e->getMessage());
// if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
// $r = $e->getResponse();
// $res = json_decode($r->getBody());
// return ["code" => 1, "err_msg" => $r->getBody()];
// }
// return ["code" => 1, "err_msg" => $e->getMessage()];
// }
2025-08-13 13:53:48 +08:00
}
2025-08-12 14:46:31 +08:00
/**
* 转账银行卡
* @return void
*/
public function bankTransfer()
{
}
}