官方SDK安装(PHP版)
# API V2 V3 对比
# https://pay.weixin.qq.com/doc/global/v3/zh/4012354999
composer require wechatpay/wechatpay
支付和回调
class WechatPayV3
{
// 支付
public static function pay($body, $sn, $money, $openid, $notify_url)
{
$mchid = '你的商户号';
$appId = '你的小程序appid';
$apiV3Key = '你的APIv3密钥';
$serial_no = '你的商户证书序列号';
$merchant_category_code = '你的商户行业编码';
$mchid = '964383722';
$appid = 'wx6278f6d625cb66be';
$merchant_category_code = '7339';
/////////////////////////////
// 微信商户平台
/////////////////////////////
// 证书和密钥相关设置教程 https://pay.weixin.qq.com/doc/global/v3/zh/4012354144
/////////////////////////////
// APIv3 密钥
/////////////////////////////
// https://pay.weixin.qq.com/index.php/core/v/cert/api_cert
// 该密钥用于加密APIv3的“下载平台证书”和“支付回调通知”中的消息
$apiV3Key = 'dxtdj8x4mu2hhp2s6izms6x956d2iu24';
/////////////////////////////
// 商户证书序列号
/////////////////////////////
// API certificate (CA issued), 点击 View 后可以查看 Serial Number
// https://pay.weixin.qq.com/index.php/core/v/cert/api_cert
// 也可以根据商户证书公钥生成
// openssl x509 -in /www/wwwroot/wechat.app.com/apiclient_cert.pem -noout -serial
$serial_no = '7F0234A979F1CA9AC88219D5025FC6F7F26240DA';
/////////////////////////////
// API证书
/////////////////////////////
// https://pay.weixin.qq.com/index.php/core/v/cert/api_cert
// API证书用于识别和定义您的身份;部分安全级别较高的API会要求使用证书来识别您的身份,以避免身份被盗用造成的损失。 帮助链接 https://kf.qq.com/product/wechatpaymentmerchant.html#hid=2774
// 商户证书公钥(apiclient_cert.pem)
$apiclient_cert = ROOT_PATH .'/apiclient_cert.pem';
// 商户证书私钥(apiclient_key.pem)
$apiclient_key = ROOT_PATH .'/apiclient_key.pem';
/////////////////////////////
// 微信支付平台证书
/////////////////////////////
// 根据“商户证书私钥”和“APIv3 密钥”生成时候会显示,记录下来
// cd /www/wwwroot/wechat.app.com
// composer exec CertificateDownloader.php -- -k ${apiV3key} -m ${mchId} -f ${mchPrivateKeyFilePath} -s ${mchSerialNo} -o ${outputFilePath}
// composer exec CertificateDownloader.php -- -k dxtdj8x4mu2hhp2s6izms6x956d2iu24 -m 964383722 -f /www/wwwroot/wechat.app.com/apiclient_key.pem -s 7F0234A979F1CA9AC88219D5025FC6F7F26240DA -o ./
$wechatpay_cert_path = ROOT_PATH .'/wechatpay_28D0D5D220A7D5401CA681B0DD5C494277788652.pem';
/////////////////////////////
// 平台证书序列号
/////////////////////////////
// 根据上步骤的“微信支付平台证书”生成
// 或者使用程序生成
$wechatpay_cert_path_content = file_get_contents($wechatpay_cert_path);
$cert = openssl_x509_parse($wechatpay_cert_path_content);
$platform_serial_no = strtoupper(dechex($cert['serialNumber']));
$platform_serial_no = "28D0D5D220A7D5401CA681B0DD5C494277788652";
// 创建支付对象
$apiclient_key_content = file_get_contents($apiclient_key);
// 创建支付对象
$instance = Builder::factory([
'mchid' => $mchid,
'serial' => $serial_no,
'privateKey' => $apiclient_key_content,
'certs' => [ $platform_serial_no => file_get_contents($wechatpay_cert_path) ],
'base_uri' => 'https://apihk.mch.weixin.qq.com',
]);
// 下单接口参数
// 直连模式
// https://pay.weixin.qq.com/doc/global/v3/zh/4013014150
// 国内支付
// $result = $instance->v3->pay->transactions->jsapi->post([])
$result = $instance->v3->global->transactions->jsapi->post([
'json' => [
'mchid' => $mchid,
'appid' => $appid,
'description' => $body,
'out_trade_no' => $sn,
'notify_url' => $notify_url,
'trade_type' => 'JSAPI',
'merchant_category_code' => $merchant_category_code,
'amount' => [
'total' => $money * 100, // 单位:分
'currency' => 'CNY', // CNY
],
'payer' => [
'openid' => $openid,
],
],
]);
$resp = $result->getBody()->getContents();
/**
// 成功示例
{"prepay_id": "wx201411101639507cbf6ffd8b0779950874"}
// 错误示例
{
"code": "INVALID_REQUEST",
"message": "Parameter format verification error",
"detail": {
"field": "#/properties/payer",
"value": "1346177081915535577",
"issue": "与ALLOF schema不符",
"location": "body"
}
}
*/
$resp = json_decode($resp, true);
// 小程序端需要的参数
$timeStamp = (string)time();
$nonceStr = bin2hex(random_bytes(16));
$package = "prepay_id=" . $resp['prepay_id'];
// 生成签名
$message = $appid . "\n" . $timeStamp . "\n" . $nonceStr . "\n" . $package . "\n";
$sign = base64_encode(
openssl_sign($message, $signature, $apiclient_key_content, 'sha256WithRSAEncryption') ? $signature : ''
);
return ([
'timeStamp' => $timeStamp,
'nonceStr' => $nonceStr,
'package' => $package,
'signType' => 'RSA',
'paySign' => $sign,
]);
}
// 余额查询
public function balance()
{
try {
$instance = WxappV3::getPayInstance();
// 商家充值退款余额查询
// https://pay.weixin.qq.com/doc/global/v3/zh/4013068956
$response = $instance->chain('v3/global/refund/recharge-balance')->get(['debug' => false]);
// $response => object(GuzzleHttp/Psr7/Response)
// {"currency":"HKD","remaining_amount":0}
$resp = $response->getBody()->getContents();
$jsonArr = json_decode($resp, true);
// 检查json_decode是否成功
if (json_last_error() === JSON_ERROR_NONE && is_array($jsonArr)) {
$remaining_amount = $jsonArr['remaining_amount'] ?? 0;
$remaining_amount = sprintf("%.2f", $remaining_amount);
$currency = $jsonArr['currency'] ?? '';
$balance = $remaining_amount . $currency;
return json([
'code' => 0,
'msg' => 'success',
'time' => time(),
'data' => $jsonArr,
]);
//$this->success('success', $jsonArr);
}
$this->error(json_last_error());
} catch (Exception $e) {
//throw new Exception('Balance Exception: ' . $e->getMessage());
$msg = $e->getMessage();
preg_match('/(\{.*\})/', $msg, $matches);
if (isset($matches[1])) {
$jsonArr = json_decode($matches[1], true);
// 检查json_decode是否成功
if (json_last_error() === JSON_ERROR_NONE && is_array($jsonArr)) {
$msg = isset($jsonArr['message']) && $jsonArr['message'] ? $jsonArr['message'] : $msg;
}
}
$this->error($msg);
}
}
// 回调
public function notify(){
$apiV3Key = 'dxtdj8x4mu2hhp2s6izms6x956d2iu24';
// 1. 获取微信POST的原始数据
$body = file_get_contents('php://input');
$data = json_decode($body, true);
file_put_contents(ROOT_PATH . 'runtime/wx_notify.log', date('[Y-m-d H:i:s]')."\n".print_r($data, true)."\n", FILE_APPEND);
Log::record([
'type' => 'notify',
'date' => date('Y-m-d H:i:s'),
'data' => $data,
], 'info');
// 交易回调数据解密样本
$data2 = [
'id' => 'a9598c5b-c828-5a51-b86a-c5680ab413c2',
'create_time' => '2025-06-27T18:01:58+08:00',
'resource_type' => 'encrypt-resource',
'event_type' => 'TRANSACTION.SUCCESS',
'summary' => '支付成功',
'resource' => [
'original_type' => 'transaction',
'algorithm' => 'AEAD_AES_256_GCM',
'ciphertext' => 'PLpQ63fioczBwi6z6tMcqmpEr/rNH9krzoWGdiDM84Y1uQNtKjZlLjhxLxLlTNYQouVvYZN/OSXcTU59ZL6qiu0sPqWEkoYdUnEAMnDN0DddwAwNppV3J24IGMB7Kuy958pPa1/u3QUAoSkesrxfHrOowncqUyPiSQgRx89u4PfJAhcZ/z/RR7hH/rMi73goad6HMZhZhPGluM3Aas3MwWZCvxo+h0tzKb/5spKnGLLXdp/xoJKvsZv6P3rbWiwIkExF0b8tRN7F/1MLaH0peOk8nJkMUIPE7X92m03m8SNXVCiXrsRxV8IOEg9sw7H5V30Us7akPjI3D4C6LWDttTk9I6tV0Jt4aZBEP6zZgbvHnpO2L/oa1cE/6WPkppb3RmuMSCVbrmVYVw2I01htG7modpurI94yQIl3OnK/Mnd7WVb9YnepczaWs4Gw6EXDmi/6L0/IA0/f6W1QLlpDtF0fg8C1yCIUowWwMZdc71lVRN2DaELuzqOcw+bzXwt/rO5tky3W48zVxA6Wbgx2JOhRRvg2vKUjR+IilNhB8jo0muzBKlKIGM+bX+7D+GZPotL7Y9XTcMkp26rhZv+R/y4eXd7dXSBrcIKB0XkfrQqio4sXMQ==',
'associated_data' => 'transaction',
'nonce' => 'ezbtGNqQCZXC',
]
];
if (!isset($data['resource'])) {
$this->wx_pay_fail('No resource found');
}
// 2. 解密resource字段
$associated_data = $data['resource']['associated_data'] ?? '';
$nonce = $data['resource']['nonce'];
$ciphertext = $data['resource']['ciphertext'];
// 解密前,先 base64 解码
$ciphertext_dec = base64_decode($ciphertext);
// 最后 16 字节是 tag,前面是密文
$ciphertext_real = substr($ciphertext_dec, 0, -16);
$authTag = substr($ciphertext_dec, -16);
/*
{
"id": "4200002695202506278115459465",
"appid": "wx6278f6d625cb66be",
"mchid": "964383722",
"out_trade_no": "202506271801470087",
"payer": {
"openid": "o_-6x7c_efnEnnJQAqWIHOTAj1e0"
},
"amount": {
"total": 10,
"currency": "CNY",
"payer_total": 10,
"payer_currency": "CNY",
"exchange_rate": {
"type": "SETTLEMENT_RATE",
"rate": 91827010
}
},
"trade_type": "JSAPI",
"trade_state": "SUCCESS",
"trade_state_desc": "支付成功",
"bank_type": "OTHERS",
"success_time": "2025-06-27T18:01:58+08:00"
}
* */
$plaintext = openssl_decrypt(
$ciphertext_real, // 密文
'aes-256-gcm', // 算法
$apiV3Key, // 密钥
OPENSSL_RAW_DATA, // 选项
$nonce, // 随机串
$authTag, // tag
$associated_data // 附加数据
);
// 明文一般是 JSON,需要再 json_decode
$info_data = json_decode($plaintext, true);
// 用APIv3密钥解密
// $info_body = AesGcm::decrypt($ciphertext, $apiV3Key, $associated_data, $nonce);
// $info_data = json_decode($info_body, true);
// 3. 检查订单支付状态
if ($info_data['trade_state'] == 'SUCCESS') {
file_put_contents(ROOT_PATH . 'runtime/wx_notify.log', date('[Y-m-d H:i:s]')." trade_state==SUCCESS\n", FILE_APPEND);
// $info_data['out_trade_no'] 商户订单号
// $info_data['id'] 微信支付单号
// $info_data['amount']['total'] 实付金额,单位分
$order =$this->model->where('order_no',$info_data['out_trade_no'])->find();
if (!$order || $order->state == '2') {
$this->wx_pay_success(); // wx
}
if ($order['delivery_method'] == 2) {
// 生成唯一的6位随机数字
do {
$pickUpCode = mt_rand(100000, 999999); // 生成6位随机数
$exists = $this->model->where('pick_up_code', $pickUpCode)->count(); // 检查数据库中是否已存在该 pick_up_code
} while ($exists > 0); // 如果该 pick_up_code 已经存在,重新生成
$order->pick_up_code = $pickUpCode;
}
Db::startTrans();
try {
$order->pay_time = time();
$order->state = '2';
$order->paid = '2';
$order->trade_no = $info_data['id'];
$order->save();
Db::commit();
} catch (\think\Exception $e) {
Db::rollback();
$this->wx_pay_fail(); // wx
}
// 4. 返回微信指定成功JSON
$this->wx_pay_success(); // wx
exit;
} else {
$this->wx_pay_fail(); // wx
}
}
// 退款
// https://pay.weixin.qq.com/doc/global/v3/zh/4012354571
public static function refund($number, $refundNumber, $refundFee, $refundDesc)
{
$mchid = '964383722';
$appid = 'wx6278f6d625cb66be';
try {
$instance = self::getPayInstance();
$result = $instance->v3->global->refunds->post([
'json' => [
'mchid' => $mchid,
'appid' => $appid,
'out_trade_no' => $number,
'out_refund_no' => $refundNumber,
'reason' => $refundDesc,
'notify_url' => request()->domain().'/api/refund_notify', // 退款结果回调
'amount' => [
'refund' => intval($refundFee * 100), // 退款金额,分
'total' => intval($refundFee * 100), // 订单总金额,分
'currency' => 'CNY' // 货币代码,人民币: CNY,港币: HKD,美元: USD 等
]
]
]);
$resp = $result->getBody()->getContents();
Log::record([
'type' => 'refund_response',
'date' => date('Y-m-d H:i:s'),
'body' => $resp,
], 'info');
/**
// 正常示例
{
"id": "50200603742025070135848778847",
"out_refund_no": "202507011525530888",
"create_time": "2025-07-01T15:24:44+08:00",
"amount": {
"refund": 10,
"currency": "CNY",
"payer_refund": 10,
"payer_currency": "CNY",
"exchange_rate": {
"type": "SETTLEMENT_RATE",
"rate": 91827010
},
"settlement_refund": 10,
"settlement_currency": "HKD"
}
}
// 异常示例
{
"code": "INVALID_REQUEST",
"message": "Parameter format verification error",
"detail": {
"field": "#/properties/payer",
"value": "1346177081915535577",
"issue": "与ALLOF schema不符",
"location": "body"
}
}
*/
$resp = json_decode($resp, true);
return $resp;
} catch (GuzzleException $e) {
throw new Exception('Refund GuzzleException: ' . $e->getMessage());
}
}
public function wx_pay_success($msg = 'OK'){
file_put_contents(ROOT_PATH . 'runtime/wx_notify.log', date('[Y-m-d H:i:s]')." ".$msg."\n", FILE_APPEND);
// 验签通过,处理订单逻辑
$response = [
'code' => 'SUCCESS',
'message' => $msg
];
http_response_code(200);
echo json_encode($response);
exit;
}
public function wx_pay_fail($msg = 'FAIL'){
file_put_contents(ROOT_PATH . 'runtime/wx_notify.log', date('[Y-m-d H:i:s]')." ".$msg."\n", FILE_APPEND);
// 支付失败,记录日志或其他处理
$response = [
'code' => 'SYSTEM_ERROR',
'message' => $msg
];
http_response_code(400);
echo json_encode($response);
exit;
}
}
# tail -f /www/wwwroot/wechat.app.com/runtime/log/202506/28.log -n500 -f
# 支付回调
[2025-06-28 16:01:34]
Array
(
[id] => 62abc233-59a9-5e43-92f9-45137a182f77
[create_time] => 2025-06-28T16:01:33+08:00
[resource_type] => encrypt-resource
[event_type] => TRANSACTION.SUCCESS
[summary] => 支付成功
[resource] => Array
(
[original_type] => transaction
[algorithm] => AEAD_AES_256_GCM
[ciphertext] => /gtBbEH2H/p5mv2+URCJZUCCPW3MY+ZvXLXNlHp3ljg04sLrP73ZOcyUZkNBevEmYfE3/23gadwIq6Q6V5072M4UpkZUUg9/ELk8TEeRnFv+xHoQPpwfcqRibVJNRgwiglZuG1kFE9nbMMBRFg01sRmEWyUVJAyCnvNKUPd/5D8vVKzRjGJ1OsVugNYB0TXmdKFufzQkFcFxHNnntqnYCh2NttuQcod6IeA4UVTbBKneQCuGz+2CiTaDDvWCM7eX+vGPWsXEPElNllypuEdb3HlRRg/H3l8XwqeGyejPgFG5NmTIyYS/DV3RaL/8GCVoaw+V9k+HsC4Up8huUKGlI3biS3r3HsyzjF59d9hX4eFD0M/emppc/MNKLUk/GgXyOp+sOcc5Q14r34KtIoZFsNXMnBTo8z9FfZ17VdHwiE3o0gBIcL6itEzwyOMnYw7NAv5MckbMof1EynkZIZgRtpOmKpIYOYNFET38bfPPr25GOoFiXjJPLLDz+QtchtR57Fl8VD1iL6QnrBvGV+IKYT8KLAUHYUX1OHXpE4nH93ejBWO4uwiSHHhyEDxk8t7sBu39OTNUc0XCRx5C4G/NrOfhRoXmJz2xLvhiBRqjjFCNRvw=
[associated_data] => transaction
[nonce] => 5T4dZ9fO9sdx
)
)
# 退款回调
[2025-07-01 16:07:58]
Array
(
[id] => 7dc5c5dd-3d4f-5f79-ad56-ce96ff9c5fca
[create_time] => 2025-07-01T16:07:51+08:00
[resource_type] => encrypt-resource
[event_type] => REFUND.SUCCESS
[summary] => 退款成功
[resource] => Array
(
[original_type] => refund
[algorithm] => AEAD_AES_256_GCM
[ciphertext] => FO8/PKYW2UO2KnB9r064E8wKUW2KeauFmK+17r7nj85aTlVaJsERTALo0/9dnWZ1s1LQUpkttJVdAx3QCTPLSVe6L885Wx2CeEpGvHCnnAj+p5YsE1ihfe9KxATawv5GP7Gf5knRfNC8gPx+JUINWx3eFuLiafdYWYW1ppiwxFOc0M6kasTFeN5mBk5NFmG+PyCZreO9CUMMBtpTJop/dZzyBtEodeYUJMJvNBYXZ1KWHr67/5s0azx98K0MXdOklBgUjndM7X0kKeNT+GzUK7iVq8P+QoL78clASW1GnkUn0HgGzX5S/ohceARFqgDY4F5i7GjD/EB8T3WfpP3FKxmZ/FYhVoai9WOGav2Z/IX9sSNIL3PrA/WivLogs7T9iLl/UTEhW0a2vDhZ/Ff8uLMa072t/nie/O0rYoioCk9tF82uwC5Awj8JECh8255h4hdC/WRZXycWidFsit56a8eTzSnym3s8jXFCwzInsbzpw/NFETpcYxD5b3stIPASx2Jo+ibI4rTKXzK/fAnDjvAK6pQdk4m1gSQiRuJhmg+VUhCC+DvpQXVgTLXYGpuiNgBds1XCoa1vB+L6ShPOVS5LG1UVX2Q2eNDJVjWyYsxrJ7yIwQcwuEmz8OJju9ALcz8lqDQNviRAl/0EQ6qEISM3q2Wgz/5jd2Eh7TKtnrCEJ4e2QiQH9Vw=
[associated_data] => refund
[nonce] => ToK9fpFXUxNR
)
)
文档资料
-
composer require wechatpay/wechatpay
https://github.com/wechatpay-apiv3/wechatpay-php -
商户号管理 https://pay.weixin.qq.com/index.php/xphp/v/coverseas_mch_pin/sec_center_page#/
-
小程序管理 https://mp.weixin.qq.com/wxamp/devprofile/get_profile?token=266415899&lang=zh_CN
-
商户 API 证书 && 商户 API 私钥 && 微信支付平台证书 概念理解 https://github.com/wechatpay-apiv3/wechatpay-php?tab=readme-ov-file#%E6%A6%82%E5%BF%B5
-
JSAPI支付下单 https://pay.weixin.qq.com/doc/global/v3/zh/4013014150
-
微信支付API列表 https://pay.weixin.qq.com/doc/global/v3/zh/4012354163
-
uni.requestPayment https://uniapp.dcloud.net.cn/api/plugins/payment.html#requestpayment
-
国内版支付文档(跟境外版本不一样) https://pay.weixin.qq.com/doc/v3/merchant/4012076511 https://pay.weixin.qq.com/doc/v3/merchant/4012791897
-
如何获取商户API证书 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
-
通过HBuilderX运行uniapp到微信者开发工具 https://cloud.tencent.com/developer/article/2344571