<?php
/**
* Created by PhpStorm.
* User: 阿影博客
* Date: 2024-04-20
* Time: 10:11
*/
namespace app\service;
/**
* 微信支付
* Class WxPay
* @package app\service
*/
class WxPay
{
//偏移量
const AUTH_TAG_LENGTH_BYTE = 16;
/**
* 获取微信支付参数
* @param string $outTradeNo
* @param int $amount
* @param string $notifyUrl
* @return mixed
*/
static function getPayData($outTradeNo = "", $amount = 0 ,$notifyUrl = "" )
{
#获取配置参数
$wxAppId = Config('appConfig.wx_pay_app_id'); //微信appId
$wxMerchantId = Config('appConfig.wx_mch_id'); //商户号Id
$wxApiSerialNo = Config('appConfig.wx_api_serial_no'); //商户API证书序列号
$wxMerchantApiPrivateKey = public_path() . "wx/apiclient_key.pem"; //商户私钥证书
#设置超时时间
$outTime = time() + 2 * 60;
$timeExpire = date("Y-m-d", $outTime) . "T" . date("H:i:s", $outTime) . "+08:00";
#请求接口地址
$url = "https://api.mch.weixin.qq.com/v3/pay/transactions/app";
#订单金额转换【单位为分】
$amount = $amount * 100;
#组合基本信息
$params = array(
"appid" => $wxAppId, //appId
"mchid" => $wxMerchantId, //直连商户号
"description" => "商品购买", //描述
"out_trade_no" => $outTradeNo, //商户订单号
"time_expire" => $timeExpire, //超时时间
"notify_url" => $notifyUrl, //回调地址
"amount" => array( //订单金额
"total" => $amount, //订单金额
"currency" => "CNY" //金额单位
),
);
#获取authorization
$authorization = self::RequestSign("POST", $url, json_encode($params), $wxMerchantId, $wxMerchantApiPrivateKey, $wxApiSerialNo);
#请求接口
$data = self::curlPostWithWx($url, $authorization, $params, 30);
#返回
return $data;
}
/**
* 获取呼起支付参数
* @param string $prepayId
* @return array
*/
static function getPayParams($prepayId = "")
{
#获取配置参数
$wxAppId = Config('appConfig.wx_pay_app_id'); //微信appId
$wxMerchantId = Config('appConfig.wx_mch_id'); //商户号Id
$wxMerchantApiPrivateKey = public_path() . "wx/apiclient_key.pem"; //商户私钥证书
#获取当前时间戳
$timeStamp = time();
#生成一个随机字符串
$nonceStr = self::getNonceStr();
#构造签名串
$requestSign = sprintf("%s\n%s\n%s\n%s\n", $wxAppId, $timeStamp, $nonceStr, $prepayId);
#计算计算签名值
$sign = self::calculateSignatureValue($requestSign, $wxMerchantApiPrivateKey);
#组合参数
$params = array(
"appid" => $wxAppId,
"partnerid" => $wxMerchantId,
"prepayid" => $prepayId,
"package" => "Sign=WXPay",
"noncestr" => $nonceStr,
"timestamp" => (string)$timeStamp,
"sign" => $sign
);
#返回
return $params;
}
/**
* 解密数据
* @param $associatedData
* @param $nonceStr
* @param $cipherText
* @return bool|string
*/
public static function decryptToString($associatedData, $nonceStr, $cipherText)
{
$cipherText = \base64_decode($cipherText);
if (strlen($cipherText) <= self::AUTH_TAG_LENGTH_BYTE) {
return false;
}
//微信API v3密钥
$wxApiV3Key = Config('appConfig.wx_v3_key');
// 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, $wxApiV3Key);
}
// 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, $wxApiV3Key);
}
// 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', $wxApiV3Key, \OPENSSL_RAW_DATA, $nonceStr,
$authTag, $associatedData);
}
throw new \RuntimeException('AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php');
}
/**
* 生成签名
* @param string $method
* @param string $url
* @param string $request
* @param string $wxMerchantId
* @param string $certKey
* @param string $wxApiSerialNo
* @return string
*/
static function RequestSign($method = "POST", $url = "", $request = "", $wxMerchantId = "", $certKey = "", $wxApiSerialNo = "")
{
#截取获取当前请求地址【去除域名】
$url_parts = parse_url($url);
$path = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
#获取当前时间戳
$timeStamp = time();
#生成一个随机字符串
$nonceStr = self::getNonceStr();
#构造签名串
$requestSign = sprintf("%s\n%s\n%s\n%s\n%s\n", $method, $path, $timeStamp, $nonceStr, $request);
#计算计算签名值
$sign = self::calculateSignatureValue($requestSign, $certKey);
#设置HTTP头获取Authorization
$token = self::createToken($wxMerchantId, $nonceStr, $timeStamp, $wxApiSerialNo, $sign);
#返回
return $token;
}
/**
* 计算签名值
* @param $requestSign
* @param $certKey
* @return string
* 使用商户私钥对待签名串进行SHA256 with RSA签名,并对签名结果进行Base64编码得到签名值
*/
static function calculateSignatureValue($requestSign, $certKey)
{
$certKey = file_get_contents($certKey);
openssl_sign($requestSign, $raw_sign, $certKey, 'sha256WithRSAEncryption');
$sign = base64_encode($raw_sign);
return $sign;
}
/**
* 获取token
* @param $merchant_id
* @param $nonce
* @param $timestamp
* @param $serial_no
* @param $sign
* @return string
*/
static function createToken($merchant_id, $nonce, $timestamp, $serial_no, $sign)
{
$token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
$merchant_id, $nonce, $timestamp, $serial_no, $sign);
return $token;
}
/**
* 产生随机字符串,不长于32位
* @param int $length
* @return string
*/
static function getNonceStr($length = 32)
{
$chars = "abcdefghijklmnopqrstuvwxyz0123456789";
$str = "";
for ($i = 0; $i < $length; $i++) {
$str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
}
return $str;
}
/**
* post请求
* @param string $url
* @param string $authorization
* @param array $params
* @param int $timeout
* @return mixed
*/
static function curlPostWithWx($url = "", $authorization = "", $params = array(), $timeout = 30)
{
$paramsString = json_encode($params);
// 初始化curl
$ch = curl_init();
// 设置超时
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
curl_setopt($ch, CURLOPT_HEADER, FALSE);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
// post数据
curl_setopt($ch, CURLOPT_POST, 1);
// post的变量
curl_setopt($ch, CURLOPT_POSTFIELDS, $paramsString);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json; charset=utf-8',
'Content-Length: ' . strlen($paramsString),
'Authorization: ' . "WECHATPAY2-SHA256-RSA2048 " . $authorization,
'Accept: application/json',
'User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36'
)
);
// 运行curl,结果以jason形式返回
$res = curl_exec($ch);
curl_close($ch);
// 取出数据
$data = json_decode($res, true);
return $data;
}
}
//微信支付参数
'wx_pay_app_id' => '微信应用appId',
'wx_mch_id'=> '微信商户Id',
'wx_v3_key'=> '微信V3密钥Key',
'wx_api_serial_no'=> '微信商户API证书序列号',
然后我们在public目录下新建wx目录,将生成的私钥证书放到该目录下,并且命名为apiclient_key.pem。
#初始化数据
$order_sn = "1223333" //订单编号
$total_money = 0.01; //订单金额
$notify_url = ""; //回调地址
#获取微信付款参数
$wxPay = new WxPay();
$wxPayInfo = $wxPay->getPayData($order_sn,$total_money,$notify_url);
#获取支付参数
$wxPayUrl = $wxPay->getPayParams($wxPayInfo["prepay_id"]);
这样我们就可以获得前端呼起支付所有参数。
#获取用户参数
$signData = request()->post();
#记录请求参数
Log::write("微信回调",json_encode($signData));
#提取参数
$resource = isset($signData["resource"])?$signData["resource"]:[]; //加密信息体
#判断加密参数
if (!is_array($resource) || count($resource)<=0) {
return json(["code"=>"FAIL","message"=>"加密体获取失败","msg"=>""]);
}
#判断加密要素是否存在
$associatedData = isset($resource["associated_data"]) ? $resource["associated_data"] : "";
$nonce = isset($resource["nonce"]) ? $resource["nonce"] : "";
$cipherText = isset($resource["ciphertext"]) ? $resource["ciphertext"] : "";
if (empty($associatedData) || empty($nonce) || empty($cipherText)) {
return json(["code"=>"FAIL","message"=>"加密参数有误","msg"=>""]);
}
#解密数据
$wxPay = new WxPay();
$wxOrderInfo = $wxPay::decryptToString($associatedData, $nonce, $cipherText);
if (empty($wxOrderInfo)) {
return json(["code"=>"FAIL","message"=>"回调数据解密失败","msg"=>""]);
}
$wxOrderInfo = json_decode($wxOrderInfo, true);
#提取数据
$tradeState = isset($wxOrderInfo["trade_state"])?$wxOrderInfo["trade_state"]:"";
$outTradeNo = isset($wxOrderInfo["out_trade_no"])?$wxOrderInfo["out_trade_no"]:"";
$successTime = isset($wxOrderInfo["success_time"])?$wxOrderInfo["success_time"]:"";
$tradeNo = isset($wxOrderInfo["transaction_id"])?$wxOrderInfo["transaction_id"]:"";
if(empty($tradeState) || empty($outTradeNo) || empty($successTime) || empty($tradeNo)){
return json(["code"=>"FAIL","message"=>"回调数据获取失败","msg"=>""]);
}
#判断是否支付成功
if($tradeState!="SUCCESS"){
return json(["code"=>"FAIL","message"=>"支付失败","msg"=>""]);
}
#获取支付用户Id
$buyerId = $wxOrderInfo["payer"]["openid"];
#时间转化
$gmtPayment = strtotime($successTime);
以上是部分片段,这部分代码基本可以直接复制,下面的就是各自业务处理逻辑,我这里就不放上去了,仅供大家参考
版权说明
文章采用: 《署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)》许可协议授权。版权声明:本站资源来自互联网收集,仅供用于学习和交流,请勿用于商业用途。如有侵权、不妥之处,请联系客服并出示版权证明以便删除!
发表评论