Model
<?php
/**
* User: Eden
* Date: 2019/3/21
* 共有内容
*/
class WxPayModel extends Model {
protected static $SSL_CERT_PATH = './apiclient_cert.pem';//证书路径
protected static $SSL_KEY_PATH = './apiclient_key.pem';//证书路径
public static function unifiedOrder($openid,$order_num,$total_fee,$products_name,$notify_url = ''){
$trade_no = $order_num;
$url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
$param = [
'appid' => C('APPID'),
'mch_id' => C('MCHID'),
'nonce_str' => self::createNonceStr(),
'sign_type' => 'MD5',
'body' => $products_name, //商品名称组合
'attach' => C('APP_NAME').'-附加信息',
'out_trade_no' => $trade_no, //订单号
'fee_type' => 'CNY',
'total_fee' => $total_fee,
'spbill_create_ip' => $_SERVER['REMOTE_ADDR'],
'goods_tag' => C('APP_NAME').'-商品标记',
'notify_url' => $notify_url ?:C('NOTIFY_URL'),
'trade_type' => 'JSAPI',
'openid' => $openid
];
$sign = self::MakeSign($param);
$param['sign'] = $sign;
$xml = self::ToXml($param);
$result = self::FromXml(Http::postXmlCurl($url,$xml));
setlog($param,$result,__METHOD__);
// 加工数据
$data = [
'appId' => $result['appid'] ?: C('APPID'),
'timeStamp' => time(),
'nonceStr' => self::createNonceStr(),
'package' => 'prepay_id=' . $result['prepay_id'],
'signType' => 'MD5'
];
$sign = self::MakeSign($data);
$data['sign'] = $sign;
return $data;
}
/**
* 处理退款
* @param $out_trade_no
* @param $total_fee
* @param $refund_fee
* @return array
* @throws Exception
*/
public static function refundOrder($out_trade_no,$total_fee,$refund_fee) {
$refund_no = $out_trade_no.rand('1111,9999');
$param = array(
'appid' => C('APPID'),
'mch_id' => C('MCHID'),
'nonce_str' => self::createNonceStr(),
'out_refund_no' => $refund_no, //由后端生成的退款单号,需要保证唯一,因为多个同样的退款单号只会退款一次。
'out_trade_no' => $out_trade_no, //退款订单在支付时生成的订单号
'total_fee' => $total_fee,
'refund_fee' => $refund_fee,
'op_user_id' => C('MCHID'), //操作员 op_user_id .与商户号相同即可
);
$param['sign'] = self::MakeSign($param);
$xml_data = self::ToXml($param);
$xml_result = self::postXmlSSLCurl($xml_data,'https://api.mch.weixin.qq.com/secapi/pay/refund');
$result = self::FromXml($xml_result);
setlog($param,$result,__METHOD__);
if (!$result){
$result_arr = [
'num' => '0',
'desc' => '接口错误',
];
return $result_arr;
}
if ($result['result_code'] != 'SUCCESS'){
$result_arr = [
'num' => '-1',
'desc' => $result['err_code_des']
];
} else {
$result_arr = [
'num' => '1',
'desc' => '退款成功',
'refund_id' => $result['refund_id'],
'refund_no' => $refund_no,
];
}
return $result_arr;
}
public static function FromXml($xml)
{
if(!$xml){
throw new Exception("xml数据异常!");
}
//将XML转为array
//禁止引用外部xml实体
libxml_disable_entity_loader(true);
$values = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
return $values;
}
public static function ToXml($array){
if(!is_array($array)|| count($array) <= 0){
return ;
}
$xml = '<xml version="1.0">';
foreach ($array as $key=>$val){
if (is_numeric($val)){
$xml.="<".$key.">".$val."</".$key.">";
}else{
$xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
}
}
$xml.="</xml>";
return $xml;
}
public static function createNonceStr($length = 16) {
$chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
$str = '';
for ( $i = 0; $i < $length; $i++ ) {
$str .= substr($chars, mt_rand(0, strlen($chars)-1), 1);
}
return $str;
}
public static function MakeSign($data)
{
//签名步骤一:按字典序排序参数
ksort($data);
$string = self::ToUrlParams($data);
//签名步骤二:在string后加入KEY
$string = $string . "&key=".C('WEIXIN_PAY_KEY');
//签名步骤三:MD5加密
$string = md5($string);
//签名步骤四:所有字符转为大写
$result = strtoupper($string);
return $result;
}
public static function ToUrlParams($array)
{
$buff = "";
foreach ($array as $k => $v)
{
if($k != "sign" && $v != "" && !is_array($v)){
$buff .= $k . "=" . $v . "&";
}
}
$buff = trim($buff, "&");
return $buff;
}
/**
* 需要使用证书的请求
*/
public static function postXmlSSLCurl($xml,$url,$second=30)
{
$ch = curl_init();
//超时时间
curl_setopt($ch, CURLOPT_TIMEOUT, $second);
//这里设置代理,如果有的话
//curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');
//curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
//设置header
curl_setopt($ch, CURLOPT_HEADER, FALSE);
//要求结果为字符串且输出到屏幕上
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
//设置证书
//使用证书:cert 与 key 分别属于两个.pem文件
//默认格式为PEM,可以注释
curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');
curl_setopt($ch, CURLOPT_SSLCERT, self::$SSL_CERT_PATH);
//默认格式为PEM,可以注释
curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');
curl_setopt($ch, CURLOPT_SSLKEY, self::$SSL_KEY_PATH);
//post提交方式
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
$data = curl_exec($ch);
//返回结果
if ($data) {
curl_close($ch);
return $data;
} else {
$error = curl_errno($ch);
echo "curl出错,错误码:$error" . "<br>";
curl_close($ch);
return false;
}
}
}
Vendor类
<?php
/**
* User: Eden
* Date: 2019/3/21
* 共有内容
*/
class WxPay {
protected static $SSL_CERT_PATH = './apiclient_cert.pem';//证书路径
protected static $SSL_KEY_PATH = './apiclient_key.pem';//证书路径
public static function unifiedOrder($openid,$order_num,$total_fee,$products_name,$notify_url = ''){
$trade_no = $order_num;
$url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
$param = [
'appid' => C('APPID'),
'mch_id' => C('MCHID'),
'nonce_str' => self::createNonceStr(),
'sign_type' => 'MD5',
'body' => $products_name, //商品名称组合
'attach' => C('APP_NAME').'-附加信息',
'out_trade_no' => $trade_no, //订单号
'fee_type' => 'CNY',
'total_fee' => $total_fee,
'spbill_create_ip' => $_SERVER['REMOTE_ADDR'],
'goods_tag' => C('APP_NAME').'-商品标记',
'notify_url' => $notify_url ?:C('NOTIFY_URL'),
'trade_type' => 'JSAPI',
'openid' => $openid
];
$sign = self::MakeSign($param);
$param['sign'] = $sign;
$xml = self::ToXml($param);
$result = self::FromXml(Http::postXmlCurl($url,$xml));
setlog($param,$result,__METHOD__);
// 加工数据
$data = [
'appId' => $result['appid'] ?: C('APPID'),
'timeStamp' => time(),
'nonceStr' => self::createNonceStr(),
'package' => 'prepay_id=' . $result['prepay_id'],
'signType' => 'MD5'
];
$sign = self::MakeSign($data);
$data['sign'] = $sign;
return $data;
}
/**
* 处理退款
* @param $out_trade_no
* @param $total_fee
* @param $refund_fee
* @return array
* @throws Exception
*/
public static function refundOrder($out_trade_no,$total_fee,$refund_fee) {
$refund_no = $out_trade_no.rand('1111,9999');
$param = array(
'appid' => C('APPID'),
'mch_id' => C('MCHID'),
'nonce_str' => self::createNonceStr(),
'out_refund_no' => $refund_no, //由后端生成的退款单号,需要保证唯一,因为多个同样的退款单号只会退款一次。
'out_trade_no' => $out_trade_no, //退款订单在支付时生成的订单号
'total_fee' => $total_fee,
'refund_fee' => $refund_fee,
'op_user_id' => C('MCHID'), //操作员 op_user_id .与商户号相同即可
);
$param['sign'] = self::MakeSign($param);
$xml_data = self::ToXml($param);
$xml_result = self::postXmlSSLCurl($xml_data,'https://api.mch.weixin.qq.com/secapi/pay/refund');
$result = self::FromXml($xml_result);
setlog($param,$result,__METHOD__);
if (!$result){
$result_arr = [
'num' => '0',
'desc' => '接口错误',
];
return $result_arr;
}
if ($result['result_code'] != 'SUCCESS'){
$result_arr = [
'num' => '-1',
'desc' => $result['err_code_des']
];
} else {
$result_arr = [
'num' => '1',
'desc' => '退款成功',
'refund_id' => $result['refund_id'],
'refund_no' => $refund_no,
];
}
return $result_arr;
}
public static function FromXml($xml)
{
if(!$xml){
throw new Exception("xml数据异常!");
}
//将XML转为array
//禁止引用外部xml实体
libxml_disable_entity_loader(true);
$values = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
return $values;
}
public static function ToXml($array){
if(!is_array($array)|| count($array) <= 0){
return ;
}
$xml = '<xml version="1.0">';
foreach ($array as $key=>$val){
if (is_numeric($val)){
$xml.="<".$key.">".$val."</".$key.">";
}else{
$xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
}
}
$xml.="</xml>";
return $xml;
}
public static function createNonceStr($length = 16) {
$chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
$str = '';
for ( $i = 0; $i < $length; $i++ ) {
$str .= substr($chars, mt_rand(0, strlen($chars)-1), 1);
}
return $str;
}
public static function MakeSign($data)
{
//签名步骤一:按字典序排序参数
ksort($data);
$string = self::ToUrlParams($data);
//签名步骤二:在string后加入KEY
$string = $string . "&key=".C('WEIXIN_PAY_KEY');
//签名步骤三:MD5加密
$string = md5($string);
//签名步骤四:所有字符转为大写
$result = strtoupper($string);
return $result;
}
public static function ToUrlParams($array)
{
$buff = "";
foreach ($array as $k => $v)
{
if($k != "sign" && $v != "" && !is_array($v)){
$buff .= $k . "=" . $v . "&";
}
}
$buff = trim($buff, "&");
return $buff;
}
/**
* 需要使用证书的请求
*/
public static function postXmlSSLCurl($xml,$url,$second=30)
{
$ch = curl_init();
//超时时间
curl_setopt($ch, CURLOPT_TIMEOUT, $second);
//这里设置代理,如果有的话
//curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');
//curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
//设置header
curl_setopt($ch, CURLOPT_HEADER, FALSE);
//要求结果为字符串且输出到屏幕上
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
//设置证书
//使用证书:cert 与 key 分别属于两个.pem文件
//默认格式为PEM,可以注释
curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');
curl_setopt($ch, CURLOPT_SSLCERT, self::$SSL_CERT_PATH);
//默认格式为PEM,可以注释
curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');
curl_setopt($ch, CURLOPT_SSLKEY, self::$SSL_KEY_PATH);
//post提交方式
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
$data = curl_exec($ch);
//返回结果
if ($data) {
curl_close($ch);
return $data;
} else {
$error = curl_errno($ch);
echo "curl出错,错误码:$error" . "<br>";
curl_close($ch);
return false;
}
}
}
使用效果差不多。