此文章发布于26个月前,部分信息可能已经过时,请自行斟酌确认
近期正好对接微信支付功能,写了一个微信支付类,方便以后使用,这里分享给大家,配置一下直接调用就能用!
食用说明:
- 设置为自己的
$appid
,$mchid
,$mch_key
。详情看代码。 - 根据自己情况设置文件的命名空间,也就是
namespace
。 - 整个类为整体,不要只复制其中一部分。
- 调用里面支付类方法时需要设置回调地址,根据自己实际情况修改。
<?php
/**
* Created by PhpStorm.
* User: Qingzhi
* Blog: Https://www.xinyouqu.com
* Date: 2020/12/22
* Time: 15:06
*/
namespace app\paycurrency\controller;
class WechatPayCurrency
{
protected static $appid = '';//直连商户申请的公众号或移动应用appid。
protected static $mchid = '';//直连商户的商户号,由微信支付生成并下发。
protected static $mch_key = '';//支付密钥
protected static $RefundUrl = '';//退款回调地址
protected static $NotifyUrl = '';//支付回调地址
protected static $sslCertPath = '';//微信退款时才需要的_cert.pem证书文件路径
protected static $sslKeyPath = '';//微信退款时才需要的_key.pem证书文件路径
/**
* 微信app支付接口
* @param $order 商户订单编号
* @param $price 金额(分)
* @param string $body 商品描述
* @return array
*/
public static function wechatPaymentApp($order, $price, $body = '')
{
$gatewayUrl = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
/** 商品描述 $body */
$body = $body ?: '微信支付费用';
/** 回调路径 $NotifyUrl */
// $NotifyUrl = request()->domain() . self::$NotifyUrl;
$NotifyUrl = self::$NotifyUrl;
// 获取统一下单参数 xml
$post = [
'appid' => self::$appid, // 必填--服务商商户的APPID
'body' => $body, // 必填--商品描述
'mch_id' => self::$mchid, // 必填--微信支付分配的商户号
'nonce_str' => self::randNbumer(32, 3), // 必填--随机字符串,不长于32位。推荐随机数生成算法
'notify_url' => $NotifyUrl, //支付完成回调地址url,不能带参数
'out_trade_no' => $order, // 商户订单号
'spbill_create_ip' => self::contrast_ip(), //必填--终端IP。调用微信支付API的机器IP contrast_ip $_SERVER['SERVER_ADDR']
'total_fee' => (int)$price, // 订单价格
'trade_type' => 'APP'//交易类型 默认JSAPI APP
];
// 生成签名
$KEY = self::$mch_key;
$sign = self::MakeSign($post, $KEY);
$post['sign'] = $sign;
// 数组转xml
$post_xml = self::arrayToXml($post);
//POST方式请求http
$xdXml = self::http_request($gatewayUrl, $post_xml);
//微信返回 xml 转为数组
$xdArray = self::xmlToArray($xdXml);
if ($xdArray['return_code'] == 'SUCCESS' && $xdArray['result_code'] == 'SUCCESS') {
//二次验签
$data = self::getSignApp($xdArray);
return self::return_result($data, 20000, '订单创建成功');
} else {
$msg = $xdArray['err_code_des'] ?? ($xdArray['return_msg'] ?? '订单生成失败,请重试');
return self::return_result([], 50000, $msg);
}
}
/**
* 随机生成字符串
* @param int $len 字符串长度
* @param int $type 字符串类型
* @return bool|string
*/
private static function randNbumer($len = 32, $type = 3)
{
switch ($type) {
case 1:
$codeSet = '123456789';
break;
case 2:
$codeSet = 'abcdefhijkmnpqrstuvwxyz';
break;
case 3:
$codeSet = '2345678abcdefhijkmnpqrstuvwxyz';
break;
case 4:
$codeSet = 'abcdefhijkmnpqrstuvwxyzABCDEFGHJKLMNPQRTUVWXY';
break;
default:
$codeSet = '2345678abcdefhijkmnpqrstuvwxyzABCDEFGHJKLMNPQRTUVWXY';
break;
}
return substr(str_shuffle(str_repeat($codeSet, $len)), 0, $len);
}
/**
* 传入ip则验证是否一致,不传入则返回当前ip
* @param null $contrast 想要对比的IP地址
* @return array|bool|false|string
*/
private static function contrast_ip($contrast = null)
{
if (isset($_SERVER)) {
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$realip = $_SERVER['HTTP_X_FORWARDED_FOR'];
} elseif (isset($_SERVER['HTTP_CLIENT_IP'])) {
$realip = $_SERVER['HTTP_CLIENT_IP'];
} else {
$realip = $_SERVER['REMOTE_ADDR'];
}
} else {
//不允许就使用getenv获取
if (getenv("HTTP_X_FORWARDED_FOR")) {
$realip = getenv("HTTP_X_FORWARDED_FOR");
} elseif (getenv("HTTP_CLIENT_IP")) {
$realip = getenv("HTTP_CLIENT_IP");
} else {
$realip = getenv("REMOTE_ADDR");
}
}
if (!$contrast) return $realip;
if ($contrast == $realip) return true;
return false;
}
/**
* @param $params
* @param $KEY 支付key
* @param string $type
* @return string 签名
*/
private static function MakeSign($params, $KEY, $type = "MD5")
{
//签名步骤一:按字典序排序数组参数
ksort($params);
$string = self::ToUrlParams($params); //参数进行拼接 1
$string = $string . "&key=" . $KEY;
//签名步骤二:加密
if ($type == 'MD5') {
$string = md5($string);
} else {
$string = hash_hmac('sha256', $string, $KEY);
}
//签名步骤三:所有字符转为大写
$result = strtoupper($string);
return $result;
}
/**
* 将参数拼接为url(key=value&key=value)
* @param $params
* @return string
*/
private static function ToUrlParams($params)
{
$string = '';
if (!empty($params)) {
$array = array();
foreach ($params as $key => $value) {
$array[] = $key . '=' . $value;
}
$string = implode("&", $array);
}
return $string;
}
/**
* array转xml
* @param $arr
* @return string
*/
private static function arrayToXml($arr)
{
$xml = "<xml>";
foreach ($arr as $key => $val) {
// 将字符转为大写
//$key = strtoupper($key);
// 进遍历
if (is_array($val)) {
$xml .= "<" . $key . ">" . self::arrayToXml($val) . "</" . $key . ">";
} else {
$xml .= "<" . $key . ">" . $val . "</" . $key . ">";
}
}
$xml .= "</xml>";
return $xml;
}
/**
* 微信传输调用接口
* @param $url 接口链接
* @param null $data 数组
* @param array $cert 证书路径信息
* @param array $headers 头信息
* @return bool|string
*/
private static function http_request($url, $data = null, $cert = array(), $headers = array())
{
$curl = curl_init();
if (count($headers) >= 1) {
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
}
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
// 判断是使用证书
if ($cert) {
curl_setopt($curl, CURLOPT_SSLCERTTYPE, 'PEM'); // 证书的类型。支持的格式有"PEM" (默认值), "DER"和"ENG"。
curl_setopt($curl, CURLOPT_SSLCERT, $cert['cert_url']); // 证书,用于双向认证
curl_setopt($curl, CURLOPT_SSLKEYTYPE, 'PEM'); // 客户端私钥类型,支持的私钥类型为"PEM"(默认值)、"DER"和"ENG"
curl_setopt($curl, CURLOPT_SSLKEY, $cert['cert_key_url']); // 私钥的文件路径 getcwd()
//curl_setopt($curl,CURLOPT_CAINFO,getcwd()."/customerkey/rootca.pem");// 公共pem
//curl_setopt($curl, CURLOPT_KEYPASSWD,'');// 私钥密码,私钥在创建时可以选择加密。
}
curl_setopt($curl, CURLOPT_URL, $url);
if (!empty($data)) {
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
}
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($curl);
//$meta = curl_getinfo($curl);// 发送信息
//dump($meta);
curl_close($curl);
return $output;
}
/**
* xml转array
* @param unknown $xml
* @return Ambigous <string, unknown>
*/
private static function xmlToArray($xml)
{
//禁止引用外部xml实体
libxml_disable_entity_loader(true);
$xmlstring = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
$val = json_decode(json_encode($xmlstring), true);
return $val;
// 方法一
// $result = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
// return $result;
// 方法二 建名全大写不适应
// $p = xml_parser_create();
// xml_parse_into_struct($p, $xml, $vals, $index);
// xml_parser_free($p);
// $data = "";
// foreach ($index as $key => $value) {
// if ($key == 'xml' || $key == 'XML') continue;
// $tag = $vals[$value[0]]['tag'];
// $value = $vals[$value[0]]['value'];
// $data[$tag] = $value;
// }
// return $data;
// 方法三 键值对不对
// $data = simplexml_load_string($xml); //xml转object
// $data = json_encode($data); //objecct转json
// $data = json_decode($data, true); //json转array
// return $data;
}
/**
* app二次验签
* @param $xdArray
* @return mixed
*/
private static function getSignApp($xdArray)
{
$getPost['appid'] = $xdArray['appid'];
$getPost['partnerid'] = $xdArray['mch_id'];
$getPost['prepayid'] = $xdArray['prepay_id'];
$getPost['package'] = 'Sign=WXPay';
$getPost['noncestr'] = self::randNbumer(32, 3);
$getPost['timestamp'] = (string)time();
$KEY = self::$mch_key;
$sign = self::MakeSign($getPost, $KEY);
$getPost['sign'] = $sign;
return $getPost;
}
/**
* 自己封装的返回方法
* @param array $data
* @param int $code
* @param string $msg
* @return array
*/
private static function return_result($data = [], $code = 20000, $msg = '')
{
if (is_string($data)) {
$msg = $data;
$data = [];
}
return ['data' => $data, 'code' => $code, 'msg' => $msg, 'time' => time()];
}
/**
* 小程序支付接口
* @param $open_id 用户的openid
* @param $order 订单编号
* @param $price 金额(分)
* @param string $body 商品描述
* @return array
*/
public static function wechatPaymentApple($open_id, $order, $price, $body = '')
{
$gatewayUrl = 'https://api.mch.weixin.qq.com/pay/unifiedorder';//微信统一下单接口
// $notify_url = request()->domain() . self::$NotifyUrl;//异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
$notify_url = self::$NotifyUrl;//异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
// 获取统一下单参数 xml
$post = [
'appid' => self::$appid, // 必填--服务商商户的APPID
'body' => $body ?? '商品名称', // 必填--商品描述
'mch_id' => self::$mchid, // 必填--微信支付分配的商户号
'nonce_str' => self::randNbumer(32, 3), // 必填--随机字符串,不长于32位。推荐随机数生成算法
'notify_url' => $notify_url, //支付完成回调地址url,不能带参数
'openid' => $open_id,
'out_trade_no' => $order, // 商户订单号
'spbill_create_ip' => self::contrast_ip(), //必填--终端IP。调用微信支付API的机器IP
'total_fee' => (int)$price, // 订单价格
'trade_type' => 'JSAPI'//交易类型 默认JSAPI
];
// 生成签名
$KEY = self::$mch_key;
$sign = self::MakeSign($post, $KEY);
$post['sign'] = $sign;
// 数组转xml
$post_xml = self::arrayToXml($post);
// dump($post_xml);exit;
//POST方式请求http
$xdXml = self::http_request($gatewayUrl, $post_xml);
//微信返回 xml 转为数组
$xdArray = self::xmlToArray($xdXml);
// 当生成订单成功后进行二次签名
if ($xdArray['return_code'] == 'SUCCESS' && $xdArray['result_code'] == 'SUCCESS') {
//二次签名 小程序调起支付数据签名字段列表:
$res = self::getSign($xdArray);
$data = [
'res' => $xdArray,
'result' => $res,
];
return self::return_result($data, 20000, $xdArray['return_msg'] ?? '');
} else {
$data = [
'res' => $xdArray,
'result' => $xdArray,
];
return self::return_result($data, 50000, $xdArray['return_msg'] ?? '');
}
}
/**
* 小程序二次验签
* @param $xdArray
* @return mixed
*/
private static function getSign($xdArray)
{
$getPost['appId'] = self::$appid;
$getPost['timeStamp'] = (string)time();
$getPost['nonceStr'] = self::randNbumer(32, 3);
$getPost['package'] = 'prepay_id=' . $xdArray['prepay_id'];
$getPost['signType'] = "MD5";
$KEY = self::$mch_key;
$sign = self::MakeSign($getPost, $KEY);
$getPost['paySign'] = $sign;
return $getPost;
}
/**
* 微信web支付
* @param $order 订单编号
* @param $price 订单金额
* @param string $body 商品描述
* @return array
*/
public static function wechatPaymentWeb($order, $price, $body = '')
{
$gatewayUrl = 'https://api.mch.weixin.qq.com/pay/unifiedorder';//微信统一下单接口
// $notify_url = request()->domain() . self::$NotifyUrl;//异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
$notify_url = self::$NotifyUrl;//异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
//统一下单参数 xml
$post = [
'appid' => self::$appid, // 必填--服务商商户的APPID
'body' => $body ?? '商品名称', // 必填--商品描述
'mch_id' => self::$mchid, // 必填--微信支付分配的商户号
'nonce_str' => self::randNbumer(32, 3), // 必填--随机字符串,不长于32位。推荐随机数生成算法
'notify_url' => $notify_url, //支付完成回调地址url,不能带参数
'out_trade_no' => $order, // 商户订单号
'spbill_create_ip' => self::contrast_ip(), //必填--终端IP。调用微信支付API的机器IP contrast_ip $_SERVER['SERVER_ADDR']
'total_fee' => (int)$price, // 订单价格
'trade_type' => 'NATIVE'//交易类型 默认JSAPI
];
// 生成签名
$KEY = self::$mch_key;
$sign = self::MakeSign($post, $KEY);
$post['sign'] = $sign;
// 数组转xml
$post_xml = self::arrayToXml($post);
//POST方式请求http
$xdXml = self::http_request($gatewayUrl, $post_xml);
//微信返回 xml 转为数组
$xdArray = self::xmlToArray($xdXml);
if ($xdArray['return_code'] == 'SUCCESS' && $xdArray['result_code'] == 'SUCCESS') return self::return_result($xdArray, 20000, '订单创建成功');
$msg = $xdArray['err_code_des'] ?? ($xdArray['return_msg'] ?? '订单生成失败,请重试');
return self::return_result([], 50000, $msg);
}
/**
* 微信退款查询
* @param $out_trade_no
* @param string $type //微信订单号:transaction_id 商户订单号:out_trade_no 商户退款单号:out_refund_no 微信退款单号:refund_id
* @return array
*/
public static function wechatRefundInquiry($out_trade_no, $type = 'out_trade_no')
{
$queryRefundUrl = "https://api.mch.weixin.qq.com/pay/refundquery";//微信退款查询接口
if (empty($out_trade_no)) return self::return_result([], 50000, '订单号不能为空');
$query = self::wechatpayQuery($out_trade_no, $type);
//POST方式请求http
$xdXml = self::http_request($queryRefundUrl, $query);
//微信返回 xml 转为数组
$xdArray = self::xmlToArray($xdXml);
if (!empty($xdArray['return_code']) && $xdArray['return_code'] == 'SUCCESS' && isset($xdArray['result_code']) && $xdArray['result_code'] == 'SUCCESS') {
return self::return_result($xdArray, 20000, '订单查询成功');
}
$msg = $xdArray['err_code_des'] ?? ($xdArray['return_msg'] ?? '订单查询失败,请重试');
return self::return_result([], 50000, $msg);
}
/**
* 获取商户平台配置
* @param $out_trade_no 订单号
* @return string
*/
private static function wechatPayQuery($out_trade_no, $type = 'out_trade_no')
{
$post = [
'appid' => self::$appid, // 必填--服务商商户的APPID
'mch_id' => self::$mchid, // 必填--微信支付分配的商户号
'nonce_str' => self::randNbumer(32, 3), // 必填--随机字符串,不长于32位。推荐随机数生成算法
];
$post[$type] = $out_trade_no;
// 生成签名
$KEY = self::$mch_key;
$sign = self::MakeSign($post, $KEY);
$post['sign'] = $sign;
// 数组转xml
$post_xml = self::arrayToXml($post);
return $post_xml;
}
/**
* 微信退款申请
* @param $order 原订单号
* @param int $price 金额 分
* @param null $out_request_no 退款单号
* @param string $refund_reason 退款信息
* @return array
*/
public static function wechatPaymentRefund($order, int $price, $out_request_no = null, $refund_reason = '退款')
{
$refundUrl = 'https://api.mch.weixin.qq.com/secapi/pay/refund';
// $refund_url = request()->domain() . self::$RefundUrl;//退款回调地址
$refund_url = self::$RefundUrl;//退款回调地址
$out_request_no = $out_request_no ?? date('YmdHis') . self::randNbumer(6, 1);//生成退款订单号
$post_arr = [
'appid' => self::$appid,
'mch_id' => self::$mchid,
'nonce_str' => self::randNbumer(32, 3),//随机字符串
'sign_type' => 'HMAC-SHA256',
// 'transaction_id' => '',
'out_trade_no' => $order,
'out_refund_no' => $out_request_no,
'total_fee' => $price,
'refund_fee' => $price,
'refund_desc' => $refund_reason,//退款原因
'notify_url' => $refund_url
];
// 2、制作签名
$KEY = self::$mch_key;
$sign = self::MakeSign($post_arr, $KEY, $post_arr['sign_type']);
$post_arr['sign'] = $sign;
// 3、 转xml
$post_xml = self::arrayToXml($post_arr);
//4、发送请求
//证书
// $sslCertPath = env('extend_path') . '/cert/apiclient_cert.pem';
// $sslKeyPath = env('extend_path') . '/cert/apiclient_key.pem';
$sslCertPath = self::$sslCertPath;
$sslKeyPath = self::$sslKeyPath;
$cert = [
'cert_url' => $sslCertPath,
'cert_key_url' => $sslKeyPath,
];
//POST方式请求http
$xdXml = self::http_request($refundUrl, $post_xml, $cert);
//微信返回 xml 转为数组
$xdArray = self::xmlToArray($xdXml);
if ($xdArray['return_code'] == 'SUCCESS' && $xdArray['result_code'] == 'SUCCESS') {
return self::return_result($xdArray, 20000, '退款申请成功');
} else {
$msg = $xdArray['err_code_des'] ?? ($xdArray['return_msg'] ?? '退款申请失败,请重试');
return self::return_result([], 50000, $msg);
}
}
/**
* 微信支付订单查询
* @param bool $out_trade_no
* @return Ambigous|bool
*/
public static function wechatPaymentQuery($out_trade_no)
{
$querywayUrl = "https://api.mch.weixin.qq.com/pay/orderquery";//微信订单查询接口
if (empty($out_trade_no)) return self::return_result([], 50000, '订单号不能为空');
$query = self::wechatPayQuery($out_trade_no);
//POST方式请求http
$xdXml = self::http_request($querywayUrl, $query);
//微信返回 xml 转为数组
$xdArray = self::xmlToArray($xdXml);
if (empty($xdArray['return_code'])) return self::return_result($xdArray, 50000, '查询失败');
if ($xdArray['return_code'] == 'SUCCESS' && $xdArray['result_code'] == 'SUCCESS') {
return self::return_result($xdArray, 20000, '查询成功');
}
$msg = $xdArray['err_code_des'] ?? ($xdArray['return_msg'] ?? '订单查询失败');
return self::return_result($xdArray, 50000, $msg);
}
}
最后修改:2021 年 03 月 25 日 12 : 07 AM
© 允许规范转载
超赞!直接就可以用了!
注意要修改好自己的支付回调地址哦