微信支付PHP类封装
2018-06-20 本文已影响119人
echo1028
一:类说明
Tool.php: 工具类,封装了xml转数组 、数组转xml、curl post请求 、curl get请求、获取用户openid等方法;
WxPayApi.php: 微信支付API列表类,例如统一下单接口等;
WxPayConfig.php: 微信配置类;
WxPayException: 异常处理类;
WxPayDataBase: 数据基类;
WxPayNotify:支付异步通知应答类;
二:代码如下
1.工具类:Tool.php
<?php
namespace wexin\pay;
class Tool
{
/**
* 生成随机字符串,不长于32位
*
* @param int $length
* @return string
*/
public 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;
}/*}}}*/
/**
* array 转为 xml
*
* @param array $data | object
* @return string
* @throws WxPayException
*/
public static function arrayToXml($data = [])
{/*{{{*/
if (!is_array($data) || count($data) == 0) {
throw new WxPayException('数组数据异常!');
}
$xml = '<xml>';
foreach ($data as $key => $val) {
if (is_numeric($val)) {
$xml .= '<' . $key . '>' . $val . '</' . $key . '>';
} else {
$xml .= '<' . $key . '><![CDATA[' . $val . ']]></' . $key . '>';
}
}
$xml .= '</xml>';
return $xml;
}/*}}}*/
/**
* xml 转为 array
*
* @param string $xml
* @return mixed
* @throws WxPayException
*/
public static function xmlToArray($xml = '')
{/*{{{*/
if (empty($xml))
throw new WxPayException('xml数据异常!');
// 将XML转为array
// 禁止引用外部xml实体
libxml_disable_entity_loader(true);
$data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
return $data;
}/*}}}*/
/**
* 格式化参数为URL参数
*
* @param array $data
* @return string
*/
public static function toUrlParams($data = [])
{/*{{{*/
$buff = '';
foreach ($data as $key => $val) {
if ($key != 'sign' && $val != '' && !is_array($val)) {
$buff .= $key . '=' . $val . '&';
}
}
$buff = trim($buff, '&');
return $buff;
}/*}}}*/
/**
* 生成签名
*
* @param array $data
* @return string
*/
public static function makeSign($data = [])
{/*{{{*/
// 签名步骤一:按字典排序参数
ksort($data);
$string = self::toUrlParams($data);
// 签名步骤二:在string后加入key
$string = $string . '&key=' . WxPayConfig::KEY;
// 签名步骤三:MD5加密
$string = md5($string);
// 签名步骤四:所有字符转为大写
$result = strtoupper($string);
return $result;
}/*}}}*/
/**
* 获取客户端IP地址
*
* @param int $type 返回类型 0 返回IP地址 1 返回IPV4地址数字
* @param bool $adv 是否进行高级模式获取(有可能被伪装)
* @return mixed
*/
public static function getClientIP($type = 0, $adv = false)
{/*{{{*/
$type = $type ? 1 : 0;
static $ip = null;
if (null !== $ip) {
return $ip[$type];
}
if ($adv) {
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$arr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
$pos = array_search('unknown', $arr);
if (false !== $pos) {
unset($arr[$pos]);
}
$ip = trim(current($arr));
} elseif (isset($_SERVER['HTTP_CLIENT_IP'])) {
$ip = $_SERVER['HTTP_CLIENT_IP'];
} elseif (isset($_SERVER['REMOTE_ADDR'])) {
$ip = $_SERVER['REMOTE_ADDR'];
}
} elseif (isset($_SERVER['REMOTE_ADDR'])) {
$ip = $_SERVER['REMOTE_ADDR'];
}
// IP地址合法验证
$long = sprintf("%u", ip2long($ip));
$ip = $long ? [$ip, $long] : ['0.0.0.0', 0];
return $ip[$type];
}/*}}}*/
/**
* 获取毫秒级时间戳
*
* @return array|string
*/
public static function getMillisecond()
{/*{{{*/
$time = explode (" ", microtime ());
$time = $time[1] . ($time[0] * 1000);
$time2 = explode( ".", $time );
$time = $time2[0];
return $time;
}/*}}}*/
/**
* post方式提交数据到对应的接口
*
* @param string $data 需要post的xml数据
* @param $url string url
* @param bool $useCert 是否需要证书,默认不需要
* @param int $timeout 请求执行超时时间,默认30s
* @return mixed
* @throws WxPayException
*/
public static function postCurl($data, $url, $useCert = false, $timeout = 30)
{/*{{{*/
$ch = curl_init();
// 设置超时
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
// 如果有配置代理这里就设置代理
if (WxPayConfig::CURL_PROXY_HOST != "0.0.0.0"
&& WxPayConfig::CURL_PROXY_PORT != 0) {
curl_setopt($ch, CURLOPT_PROXY, WxPayConfig::CURL_PROXY_HOST);
curl_setopt($ch, CURLOPT_PROXYPORT, WxPayConfig::CURL_PROXY_PORT);
}
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); // 严格校验
// 设置header
curl_setopt($ch, CURLOPT_HEADER, false);
// 要求结果为字符串且输出到屏幕上
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
if ($useCert == true) {
// 设置证书
// 使用证书:cert 与 key 分别属于两个.pem文件
curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');
curl_setopt($ch, CURLOPT_SSLCERT, WxPayConfig::SSLCERT_PATH);
curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');
curl_setopt($ch, CURLOPT_SSLKEY, WxPayConfig::SSLKEY_PATH);
}
// post方式提交数据
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
// 运行curl
$result = curl_exec($ch);
// 返回结果
if ($result) {
curl_close($ch);
return $result;
} else {
$error = curl_errno($ch);
curl_close($ch);
throw new WxPayException('curl出错,错误码: ' . $error);
}
}/*}}}*/
/**
* curl GET提交
*
* @param $url
* @param int $timeout
* @return mixed
*/
public static function getCurl($url, $timeout = 5)
{/*{{{*/
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); // 设置超时
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, 0);
$result = curl_exec($ch);
curl_close($ch);
return $result;
}/*}}}*/
/**
* 获取用户openid
*/
public static function getOpenid()
{
// 通过code获取openid
if (!isset($_GET['code'])) {
// 触发微信返回code码
$url = 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] . $_SERVER['QUERY_STRING'];
$baseUrl = urlencode($url);
$redirectUrl = self::createOauthUrlForCode($baseUrl);
header("Location: $redirectUrl");
exit();
} else {
// 获取code,通过code获取openid
$code = $_GET['code'];
// 构造获取openid的链接
$url = self::createOauthUrlForOpenid($code);
// 发送get请求
$data = self::getCurl($url);
$data = json_decode($data, true);
return $data['openid'];
}
}
/**
* 构造获取code的url连接
*
* @param string $redirectUrl 微信服务器回跳的url,需要url编码
* @return string
*/
public static function createOauthUrlForCode($redirectUrl)
{
$params['appid'] = WxPayConfig::APPID;
$params['redirect_uri'] = $redirectUrl;
$params['response_type'] = 'code';
$params['scope'] = 'snsapi_base';
$params['state'] = 'STATE#wechat_redirect';
$queryString = self::toUrlParams($params);
return 'https://open.weixin.qq.com/connect/oauth2/authorize?' . $queryString;
}
/**
* 构造通过code获取openid的url链接
*
* @param $code
* @return string
*/
public static function createOauthUrlForOpenid($code)
{
$params['appid'] = WxPayConfig::APPID;
$params['secret'] = WxPayConfig::APPSECRET;
$params['code'] = $code;
$params['grant_type'] = 'authorization_code';
$queryString = self::toUrlParams($params);
return 'https://api.weixin.qq.com/sns/oauth2/access_token?' . $queryString;
}
}
2.微信支付API列表类:WxPayApi.php
<?php
namespace wexin\pay;
class WxPayApi
{
/**
* 统一下单请求地址
*/
const UNIFORM_ORDER_URL = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
/**
* 统一下单接口
*
* @param $inputObj
* @param int $timeOut
* @return mixed
* @throws WxPayException
*/
public static function unifiedOrder($inputObj, $timeOut = 6)
{
// 检测必填参数
if (!isset($inputObj->appid)) {
throw new WxPayException('缺少统一下单接口必填参数公众号ID:appid!');
} elseif (!isset($inputObj->mch_id)) {
throw new WxPayException('缺少统一下单接口必填参数商户号:mch_id!');
} elseif (!isset($inputObj->sub_mch_id)) {
throw new WxPayException('缺少统一下单接口必填参数子商户号:sub_mch_id!');
} elseif (!isset($inputObj->sign)) {
throw new WxPayException('缺少统一下单接口必填参数签名:sign!');
} elseif (!isset($inputObj->body)) {
throw new WxPayException('缺少统一下单接口必填参数商品描述:body!');
} elseif (!isset($inputObj->out_trade_no)) {
throw new WxPayException('缺少统一下单接口必填参数商户订单号:out_trade_no!');
} elseif (!isset($inputObj->total_fee)) {
throw new WxPayException('缺少统一下单接口必填参数总金额:total_fee!');
} elseif (!isset($inputObj->notify_url)) {
throw new WxPayException('缺少统一下单接口必填参数通知地址:notify_url!');
} elseif (!isset($inputObj->trade_type)) {
throw new WxPayException('缺少统一下单接口必填参数交易类型:trade_type!');
} elseif (!isset($inputObj->nonce_str)) {
throw new WxPayException('缺少统一下单接口必填参数随机字符串:nonce_str!');
} elseif (!isset($inputObj->spbill_create_ip)) {
throw new WxPayException('缺少统一下单接口必填参数终端IP:spbill_create_ip!');
}
// 关联参数
if ($inputObj->trade_type == 'JSAPI' && !isset($inputObj->openid)) {
throw new WxPayException("统一下单接口中,缺少必填参数openid!trade_type为JSAPI时,openid为必填参数!");
}
if($inputObj->trade_type == 'NATIVE' && !isset($inputObj->product_id)) {
throw new WxPayException("统一下单接口中,缺少必填参数product_id!trade_type为NATIVE时,product_id为必填参数!");
}
// 格式化xml数据
$xml = Tool::arrayToXml($inputObj->getValues());
// 请求开始时间
// $startTimeStamp = Tool::getMillisecond();
// 发送请求
$response = Tool::postCurl($xml, self::UNIFORM_ORDER_URL, false, $timeOut);
$result = Tool::xmlToArray($response);
return $result;
}
}
3.配置类:WxPayConfig.php
<?php
namespace weixin\pay;
class WxPayConfig
{
/**
* 微信公众号信息配置
*
* APPID: 绑定支付的APPID,必须配置
* MCHID: 商户号 必须配置
* KEY: 商户支付密钥,必须配置
* APPSECRET: 公众号secert (仅JSAPI支付的时候需要配置, 登录公众平台,进入开发者中心可设置)
* @var string
*/
const APPID = '';
const MCHID = '';
const KEY = '';
const APPSECRET = '';
//=======【证书路径设置】==================================
/**
* 证书路径,绝对路径(仅退款、撤销订单时需要)
*/
const SSLCERT_PATH = '../cert/apiclient_cert.pem';
const SSLKEY_PATH = '../cert/apiclient_key.pem';
//=======【curl代理设置】===================================
const CURL_PROXY_HOST = "0.0.0.0";
const CURL_PROXY_PORT = 0;
//=======【上报信息配置】===================================
const REPORT_LEVENL = 1;
//=======【支付异步通知URL】===================================
const NOTIFY_URL = '';
}
4.异常类:WxPayException.php
<?php
namespace weixin\pay;
class WxPayException extends \Exception
{
/**
* @return string
*/
public function errorMessage()
{
return $this->getMessage();
}
}
5.数据基类:WxPayDataBase.php
<?php
namespace weixin\pay;
class WxPayDataBase
{
/**
* @var array
*/
protected $values = [];
/**
* @param $name
* @param $value
*/
public function __set($name, $value)
{
$this->values[$name] = $value;
}
/**
* __get
*
* @param mixed $name
* @access public
* @return void
*/
public function __get($name)
{
return $this->values[$name];
}
/**
* __isset
*
* @param mixed $name
* @access public
* @return void
*/
public function __isset($name)
{
return array_key_exists($name, $this->values);
}
/**
* setSign 设置签名
*
* @return void
*/
public function setSign()
{
$sign = Tool::makeSign($this->values);
$this->values['sign'] = $sign;
}
/**
* getValues 获取设置的值
*
* @return array
*/
public function getValues()
{
return $this->values;
}
}
6.支付异步通知应答类: WxPayNotify.php
<?php
namespace weixin\pay;
/**
* 微信支付异步通知应答
*
* Class WxPayNotify
* @package weixin\pay
*/
class WxPayNotify extends WxPayDataBase
{
/**
* @param $xml
*/
public function replayNotify($xml)
{
echo $xml;
}
}
使用如下:
// 统一下单
<?php
$WxPayData = new WxPayDataBase();
$WxPayData->appid = WxPayConfig::APPID;
$WxPayData->mch_id = WxPayConfig::MCHID;
$WxPayData->sub_mch_id = '1508144001';
$WxPayData->body = '商品';
$WxPayData->out_trade_no = Tool::getNonceStr();
// 金额:分
$WxPayData->total_fee = 1;
$WxPayData->notify_url = 'https://www.qq.com/Wx/WxPay/notify';
$WxPayData->trade_type = 'JSAPI';
$WxPayData->openid = '你的openid';
// 随机字符串
$WxPayData->nonce_str = Tool::getNonceStr();
// 终端ip
$WxPayData->spbill_create_ip = Tool::getClientIP();
$WxPayData->setSign();
try {
$result = WxPayApi::unifiedOrder($WxPayData);
} catch (WxPayException $e) {
die($e->getMessage());
}
// 支付异步通知处理函数
function notify()
{
$xml = file_get_contents('php://input');
$array = Tool::xmlToArray($xml);
// 记录日志
file_put_contents('/Data/logs/app/store/notify.log', json_encode($array) . PHP_EOL, FILE_APPEND);
// 商户应答
$notify = new WxPayNotify();
if ($array['result_code'] == 'SUCCESS' && $array['return_code'] == 'SUCCESS') {
$result_sign = $array['sign'];
unset($array['sign']);
$sign = Tool::makeSign($array);
// 验证签名
if ($sign == $result_sign) {
$notify->return_code = 'SUCCESS';
$notify->return_msg = 'OK';
$xml = Tool::arrayToXml($notify->getValues());
$notify->replayNotify();
} else {
$notify->return_code = 'FAIL';
$notify->return_msg = '签名错误';
$xml = Tool::arrayToXml($notify->getValues());
$notify->replayNotify();
}
} else {
$notify->return_code = 'FAIL';
$notify->return_msg = '支付失败';
$xml = Tool::arrayToXml($notify->getValues());
$notify->replayNotify();
}
}
jsapi发起支付
<!DOCTYPE html>
<html lang="en">
<head>
<title>微信支付样例-支付</title>
<script>
function jsApiCall()
{
WeixinJSBridge.invoke(
'getBrandWCPayRequest',
{
"appId": "",
"timeStamp": "",
"nonceStr": "",
"package": "",
"signType": "",
"paySign": "",
},
function(res) {
WeixinJSBridge.log(res.err_msg);
alert(res.err_code+res.err_desc+res.err_msg);
}
);
}
function callpay()
{
if (typeof WeixinJSBridge == "undefined"){
if ( document.addEventListener ) {
document.addEventListener('WeixinJSBridgeReady', jsApiCall, false);
} else if (document.attachEvent) {
document.attachEvent('WeixinJSBridgeReady', jsApiCall);
document.attachEvent('onWeixinJSBridgeReady', jsApiCall);
}
} else {
jsApiCall();
}
}
</script>
</head>
<body>
<br/>
<font color="#9ACD32"><b>该笔订单支付金额为<span style="color:#f00;font-size:50px">1分</span>钱</b></font><br/><br/>
<div align="center">
<button style="width:210px; height:50px; border-radius: 15px;background-color:#FE6714; border:0px #FE6714 solid; cursor: pointer; color:white; font-size:16px;" type="button" onclick="callpay()" >立即支付</button>
</div>
</body>
</html>