近期正好对接微信支付功能,写了一个微信支付类,方便以后使用,这里分享给大家,配置一下直接调用就能用!
食用说明:
- 设置为自己的
$appid
,$mchid
,$mch_key
。详情看代码。 - 根据自己情况设置文件的命名空间,也就是
namespace
。 - 整个类为整体,不要只复制其中一部分。
- 调用里面支付类方法时需要设置回调地址,根据自己实际情况修改。
<?php
/**
* Created by PhpStorm.
* User: Qingzhi
* Blog:
* Date: 2020/12/22
* Time: 15:06
*/
namespace app\currency\controller;
class WechatPayCurrency
{
protected static $appid = 'wxea7bb0eb62dsada54';//直连商户申请的公众号或移动应用appid。
protected static $mchid = '1571256472';//直连商户的商户号,由微信支付生成并下发。
protected static $mch_key = 'zhifumiyaozhifumiyao44244244';//支付密钥
/**
* 微信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() . '/wechat/pay_notify_url';
// 获取统一下单参数 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' => intval($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') {
return self::return_result($xdArray, 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, getcwd() . $cert['cert_url']); // 证书,用于双向认证
curl_setopt($curl, CURLOPT_SSLKEYTYPE, 'PEM'); // 客户端私钥类型,支持的私钥类型为"PEM"(默认值)、"DER"和"ENG"
curl_setopt($curl, CURLOPT_SSLKEY, getcwd() . $cert['cert_key_url']); // 私钥的文件路径
//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;
}
/**
* 自己封装的返回方法
* @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() . '/wechat/pay_notify_url';//异步接收微信支付结果通知的回调地址,通知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' => intval($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() . '/wechat/pay_notify_url';//异步接收微信支付结果通知的回调地址,通知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' => intval($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
* @return Ambigous|bool
*/
public static function wechatRefundInquiry($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);
//POST方式请求http
$xdXml = self::http_request($queryRefundUrl, $query);
//微信返回 xml 转为数组
$xdArray = self::xmlToArray($xdXml);
if (!empty($xdArray['return_code']) && $xdArray['return_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)
{
$post = [
'appid' => self::$appid, // 必填--服务商商户的APPID
'mch_id' => self::$mchid, // 必填--微信支付分配的商户号
'nonce_str' => self::randNbumer(32, 3), // 必填--随机字符串,不长于32位。推荐随机数生成算法
'out_trade_no' => $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 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') return self::return_result($xdArray, 20000, '查询成功');
$msg = $xdArray['err_code_des'] ?? ($xdArray['return_msg'] ?? '订单查询失败');
return self::return_result($xdArray, 50000, $msg);
}
}
最后修改:2020 年 12 月 28 日 12 : 02 AM
? 允许规范转载
超赞!直接就可以用了!
注意要修改好自己的支付回调地址哦