PHP对接微信支付v3版本
【以下内容是在thinkphp框架中编写的,做简单修改就可使用】
一、扩展
1-1、支付扩展
扩展路径:extend/wxpay/Wx.php,根据项目实际情况修改。
<?php
/**
* User: Lee
* Date: 2025/2/23
* Time: 22:23
*/
namespace extend\wxpay;
class Wx
{
private $appid=''; // 小程序id
private $mch_id=''; // 商户号
private $serial_no=''; // 商户证书序列号
private $private_key=''; // 商户私钥
private $api_key=''; // API密钥
private $notify_url='https://*******/callback.html'; // 支付结果通知地址
private $apiclient_cert_url = ''; // apiclient_cert.pem文件在服务器的磁盘路径
private $apiclient_key_url = ''; // apiclient_key.pem文件在服务器的磁盘路径
/**
* 构造函数
*
* @param string $mch_id 商户号
* @param string $serial_no 商户证书序列号
* @param string $private_key 商户私钥
* @param string $api_key API密钥
* @param string $notify_url 支付结果通知地址
*/
public function __construct()
{
$serial_no = $this->getSerialNoFromCert($this->apiclient_cert_url);
$this->serial_no = $serial_no;
$this->private_key = file_get_contents($this->apiclient_key_url);
}
/**
* 统一下单接口
*
* @param string $openid 用户openid
* @param string $out_trade_no 商户订单号
* @param string $description 商品描述
* @param int $amount 订单金额(单位:分)
* @return array 返回统一下单结果
*/
public function unifiedOrder($openid, $out_trade_no, $description, $amount)
{
// 统一下单接口URL
$url = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";
// 请求参数
$params = [
'appid' => $this->appid, // 小程序或公众号的appid
'mchid' => $this->mch_id,
'description' => $description,
'out_trade_no' => $out_trade_no,
'notify_url' => $this->notify_url,
'amount' => [
'total' => $amount, // 订单金额(单位:分)
'currency' => 'CNY', // 货币类型
],
'payer' => [
'openid' => $openid, // 用户openid
],
];
// 生成签名
$authorization = $this->generateAuthorization('POST', $url, $params);
// 发送HTTP请求
$response = $this->httpPost($url, json_encode($params), [
'Authorization: ' . $authorization,
'Content-Type: application/json',
'Accept: application/json',
'User-Agent:yaode/1.0',
]);
// 解析响应
return json_decode($response, true);
}
/**
* 生成签名
*
* @param string $method HTTP方法(GET/POST等)
* @param string $url 请求URL
* @param array $body 请求体
* @return string 返回Authorization头
*/
private function generateAuthorization($method, $url, $body)
{
// 1. 生成随机字符串
$nonce_str = $this->generateNonceStr();
// 2. 生成时间戳
$timestamp = time();
// 3. 生成签名原文
$signature = $this->generateSignature($method, $url, $timestamp, $nonce_str, $body);
// 4. 生成Authorization头
return sprintf(
'WECHATPAY2-SHA256-RSA2048 mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
$this->mch_id,
$nonce_str,
$timestamp,
$this->serial_no,
$signature
);
}
/**
* 生成签名
*
* @param string $method HTTP方法
* @param string $url 请求URL
* @param int $timestamp 时间戳
* @param string $nonce_str 随机字符串
* @param array $body 请求体
* @return string 返回签名
*/
private function generateSignature($method, $url, $timestamp, $nonce_str, $body)
{
// 1. 拼接签名原文
$message = sprintf(
"%s\n%s\n%d\n%s\n%s\n",
$method,
parse_url($url, PHP_URL_PATH),
$timestamp,
$nonce_str,
json_encode($body)
);
// 2. 使用私钥生成签名
openssl_sign($message, $signature, $this->private_key, OPENSSL_ALGO_SHA256);
// 3. 返回Base64编码的签名
return base64_encode($signature);
}
/**
* 生成随机字符串
*/
private function generateNonceStr($length = 32)
{
$chars = "abcdefghijklmnopqrstuvwxyz0123456789";
$str = "";
for ($i = 0; $i < $length; $i++) {
$str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
}
return $str;
}
/**
* 发送HTTP POST请求
*/
private function httpPost($url, $data, $headers = [])
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 不验证SSL证书
$response = curl_exec($ch);
curl_close($ch);
return $response;
}
public function getSerialNoFromCert($certPath) {
$cert = file_get_contents($certPath);
$ssl = openssl_x509_parse($cert);
return $ssl['serialNumberHex'];
}
/**
* 生成小程序调起支付所需的参数
*
* @param string $prepay_id 统一下单接口返回的prepay_id
* @return array 返回小程序调起支付所需的参数
*/
public function getPaymentParams($prepay_id)
{
// 参数
$params = [
'appId' => $this->appid, // 小程序或公众号的appid
'timeStamp' => (string)time(), // 时间戳
'nonceStr' => $this->generateNonceStr(), // 随机字符串
'package' => 'prepay_id=' . $prepay_id, // 预支付交易会话标识
'signType' => 'RSA', // 签名类型
];
// 生成签名
$params['paySign'] = $this->generateSignature_($params);
return $params;
}
private function generateSignature_($params)
{
$stringToSign = sprintf(
"%s\n%s\n%s\n%s\n",
$params['appId'],
$params['timeStamp'],
$params['nonceStr'],
$params['package']
);
// 3. 使用商户私钥生成签名
openssl_sign($stringToSign, $signature, $this->private_key, OPENSSL_ALGO_SHA256);
// 4. 返回 Base64 编码的签名
return base64_encode($signature);
}
}
1-2、回调扩展
路径:extend/wxpay/AesUtil.php,根据项目实际情况修改。
下面代码是微信支付文档给的代码示例,可用。
<?php
/**
* User: Lee
* Date: 2025/2/28
* Time: 17:10
*/
namespace extend\api\wxpay;
class AesUtil
{
public $aesKey = 'API密钥';
const KEY_LENGTH_BYTE = 32;
const AUTH_TAG_LENGTH_BYTE = 16;
/**
* Constructor
*/
public
function __construct()
{
$aesKey = 'IAMpnZCXj70oRGuS5gA4N00fHVcCXJJp';
if (strlen($aesKey) != self::KEY_LENGTH_BYTE) {
throw new InvalidArgumentException('无效的ApiV3Key,长度应为32个字节');
}
$this->aesKey = $aesKey;
}
/**
* Decrypt AEAD_AES_256_GCM ciphertext
*
* @param string $associatedData AES GCM additional authentication data
* @param string $nonceStr AES GCM nonce
* @param string $ciphertext AES GCM cipher text
*
* @return string|bool Decrypted string on success or FALSE on failure
*/
public
function decryptToString($associatedData, $nonceStr, $ciphertext)
{
$ciphertext = \base64_decode($ciphertext);
if (strlen($ciphertext) <= self::AUTH_TAG_LENGTH_BYTE) {
return false;
}
// ext-sodium (default installed on >= PHP 7.2)
if (function_exists('\sodium_crypto_aead_aes256gcm_is_available') && \sodium_crypto_aead_aes256gcm_is_available()) {
return \sodium_crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $this->aesKey);
}
// ext-libsodium (need install libsodium-php 1.x via pecl)
if (function_exists('\Sodium\crypto_aead_aes256gcm_is_available') && \Sodium\crypto_aead_aes256gcm_is_available()) {
return \Sodium\crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $this->aesKey);
}
// openssl (PHP >= 7.1 support AEAD)
if (PHP_VERSION_ID >= 70100 && in_array('aes-256-gcm', \openssl_get_cipher_methods())) {
$ctext = substr($ciphertext, 0, -self::AUTH_TAG_LENGTH_BYTE);
$authTag = substr($ciphertext, -self::AUTH_TAG_LENGTH_BYTE);
return \openssl_decrypt($ctext, 'aes-256-gcm', $this->aesKey, \OPENSSL_RAW_DATA, $nonceStr,
$authTag, $associatedData);
}
throw new \RuntimeException('AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php');
}
}
二、controller文件
controller文件路径:app/api/controoler/Pay.php,根据项目实际情况修改。
<?php
/**
* User: Lee
* Date: 2025/2/23
* Time: 17:05
*/
namespace app\api\controller;
use extend\api\wxpay\Wx;
class Pay extends BaseApi
{
public function pay()
{
// 订单编号
$out_trade_no = $this->params['out_trade_no'];
$token = $this->checkToken();
if ($token['code'] < 0) return $this->response($token);
// 这里写业务逻辑
$total_fee = 1; // 订单金额(单位:分)
$openid = '用户openid';
$body = '描述';
$Wx = new Wx();
$res = $Wx->unifiedOrder($openid, $out_trade_no, $body, $total_fee);
if (isset($res['prepay_id'])) {
// 生成小程序调起支付所需的参数
$paymentParams = $Wx->getPaymentParams($res['prepay_id']);
// 根据项目情况修改返回信息格式及参数
return json(['data'=>array_merge($res,$paymentParams)]);
}else{
// 根据项目情况修改返回信息格式及参数
return json();
}
}
}
三、回调
<?php
/**
* User: Lee
* Date: 2025/2/23
* Time: 15:21
*/
namespace app\api\controller;
use extend\wxpay\AesUtil;
class Callback
{
public $appid='';
public $mch_id=''; // 商户号
public $serial_no=''; // 商户证书序列号
public $private_key=''; // 商户私钥
public $public_key=''; // 商户公钥
public $api_key=''; // API密钥
const AUTH_TAG_LENGTH_BYTE = 16;
private $notify_url='https://*******/callback.html'; // 支付结果通知地址
private $apiclient_cert_url = ''; // apiclient_cert.pem文件在服务器的磁盘路径
private $apiclient_key_url = ''; // apiclient_key.pem文件在服务器的磁盘路径
public function __construct()
{
$serial_no = $this->getSerialNoFromCert($this->apiclient_cert_url);
$this->serial_no = $serial_no;
$this->private_key = file_get_contents($this->apiclient_key_url);
}
public function getSerialNoFromCert($certPath) {
$cert = file_get_contents($certPath);
$ssl = openssl_x509_parse($cert);
return $ssl['serialNumberHex'];
}
public function index()
{
$return = file_get_contents('php://input');
if(!empty($return)){
$return = json_decode($return, true);
$all = getallheaders();
if($return['event_type']!='TRANSACTION.SUCCESS' || empty($return['resource'])){
return json(['code'=>"FAIL",'message'=>"失败-3"]);
}
$associatedData = $return['resource']['associated_data']; //获取associated_data数据,附加数据
$nonceStr = $return['resource']['nonce']; //获取nonce数据,加密使用的随机串
$ciphertext = $return['resource']['ciphertext']; //获取ciphertext数据,base64编码后的数据密文
$aes = new AesUtil();
$res = $aes->decryptToString($associatedData,$nonceStr,$ciphertext);
if(!empty($res)){
$res = json_decode($res,true);
if(!empty($res)){
$out_trade_no = $res['out_trade_no'];
// 这里写业务逻辑
return json();
}else{
return json(['code'=>"FAIL",'message'=>"失败-2"]);
}
}
}
return json(['code'=>"FAIL",'message'=>"失败-1"]);
}
}