在經(jīng)過微信掃碼支付DEMO的蹂躪后缭召,決定將繁復(fù)雜亂的DEMO文件重新整合封裝,最后就有了這個(gè)清晰簡潔的Wxpay.class.php文件了。
終極版 Wxpay.class.php
<?php
/**
* Wxpay 自定義微信掃碼類
* 主要是將支微信支付DEMO中的方法整合成了一個(gè)文件
* @version 1.0 2016-07-06
*/
class Wxpay {
// 交易類型 公眾號支付
const TRADE_TYPE_JSAPI = 'JSAPI';
// 交易類型 原生掃碼支付
const TRADE_TYPE_NATIVE = 'NATIVE';
// 交易類型 app支付
const TRADE_TYPE_APP = 'APP';
// 統(tǒng)一下單接口
const URL_UNIFIED_ORDER = "https://api.mch.weixin.qq.com/pay/unifiedorder";
// 訂單查詢接口
const URL_ORDER_QUERY = "https://api.mch.weixin.qq.com/pay/orderquery";
// 關(guān)單接口
const URL_CLOSE_ORDER = 'https://api.mch.weixin.qq.com/pay/closeorder';
// 申請退款接口
const URL_REFUND = 'https://api.mch.weixin.qq.com/secapi/pay/refund';
// 查詢退款接口
const URL_REFUND_QUERY = 'https://api.mch.weixin.qq.com/pay/refundquery';
// 下載對賬單接口
const URL_DOWNLOAD_BILL = 'https://api.mch.weixin.qq.com/pay/downloadbill';
// 交易保障接口
const URL_REPORT = 'https://api.mch.weixin.qq.com/payitil/report';
// 轉(zhuǎn)換短鏈接接口
const URL_SHORT_URL = 'https://api.mch.weixin.qq.com/tools/shorturl';
const URL_MICRO_PAY = 'https://api.mch.weixin.qq.com/pay/micropay';
// 微信支付配置數(shù)組
// appid 公眾賬號appid
// mch_id 商戶號
// apikey 加密key
// appsecret 公眾號appsecret
// sslcertPath 證書路徑(apiclient_cert.pem)
// sslkeyPath 密鑰路徑(apiclient_key.pem)
private $_config;
/**
* 構(gòu)造方法 初始化微信支付配置
* @access public
* @param $config 微信支付配置數(shù)組
*/
public function __construct($config){
$this->_config = $config;
}
/**
* unifiedOrder 統(tǒng)一下單
* @access public
* @param array 統(tǒng)一下單參數(shù)數(shù)組
* @return array
*/
public function unifiedOrder($para = array()) {
// out_trade_no 商戶系統(tǒng)訂單 必填
if ( ! isset($para['out_trade_no'])) return false;
// body 商品描述/訂單描述 必填
if ( ! isset($para['body'])) return false;
// total_fee 總金額 必填
if ( ! isset($para['total_fee'])) return false;
// spbill_create_ip 終端IP 必填
if ( ! isset($para['spbill_create_ip'])) $para['spbill_create_ip'] = $_SERVER['REMOTE_ADDR'];
// APPID 公眾賬號ID 必填
$para['appid'] = $this->_config['APPID'];
// MCHID 商戶號 必填
$para['mch_id'] = $this->_config['MCHID'];
// device_info 設(shè)備號
if ( ! isset($para['device_info'])) $para['device_info'] = 'WEB';
// nonce_str 32位隨機(jī)字符串
$para['nonce_str'] = $this->getNonceStr();
// detail 商品詳情 可選
if ( ! isset($para['detail'])) $para['detail'] = '';
// attach 附加數(shù)據(jù) 可選
if ( ! isset($para['attach'])) $para['attach'] = '';
// fee_type 貨幣類型 可選
if ( ! isset($para['fee_type'])) $para['fee_type'] = 'CNY';
// time_start 交易起始時(shí)間 可選
if ( ! isset($para['time_start'])) $para['time_start'] = '';
// time_expire 交易結(jié)束時(shí)間 可選
if ( ! isset($para['time_expire'])) $para['time_expire'] = '';
// goods_tag 商品標(biāo)記 可選
if ( ! isset($para['goods_tag'])) $para['goods_tag'] = '';
// notify_url 通知地址 必填
if ( ! isset($para['notify_url'])) $para['notify_url'] = $this->_config['NOTIFY_URL'];
// trade_type 交易類型
if ( ! isset($para['trade_type'])) $para['trade_type'] = 'NATIVE';
if ($para['trade_type'] == 'NATIVE') {
// product_id 商品ID 自定義 trade_type=NATIVE時(shí) 必填
if ( ! isset($para['product_id'])) $para['product_id'] = $this->_config['PRODUCT_ID'];
if ( ! isset($para['openid'])) $para['openid'] = '';
}
if ($para['trade_type'] == 'JSAPI') {
if ( ! isset($para['product_id'])) $para['product_id'] = '';
// openid 用戶標(biāo)識 trade_type=JSAPI時(shí) 必填
if ( ! isset($para['openid'])) return false;
}
return $this->getHttpResponsePOST(self::URL_UNIFIED_ORDER, $para);
}
/**
* getCodeUrl 掃碼支付 模式二 獲取支付二維碼
* @access public
* @param array $para 支付參數(shù)數(shù)組
* @return string
*/
public function getCodeUrl($para = array()){
$r = $this->unifiedOrder($para);
if ($r['return_code'] == 'SUCCESS') {
if ($r['result_code'] == 'SUCCESS') {
return $r['code_url'];
} else {
$this->logDebug('#獲取支付CODE_URL錯(cuò)誤# '.$r['err_code'].' : '.$r['err_code_des']);
return false;
}
} else {
$this->logDebug('#獲取支付CODE_URL錯(cuò)誤# '.$r['return_msg']);
return false;
}
}
/**
* orderQuery 查詢訂單
* @access public
* @param string $order_code
* @param bool $mode
* @return array
*/
public function orderQuery($order_code, $mode = false){
$para = array();
$para['appid'] = $this->_config['APPID'];
$para['mch_id'] = $this->_config['MCHID'];
$para['nonce_str'] = $this->getNonceStr();
if ($mode) { // 使用微信訂單號
$para['transaction_id'] = $order_code;
} else { // 使用商戶訂單號
$para['out_trade_no'] = $order_code;
}
return $this->getHttpResponsePOST(self::URL_ORDER_QUERY, $para);
}
/**
* closeOrder 關(guān)閉訂單
* @access public
* @param string $out_trade_no
* @return array
*/
public function closeOrder($out_trade_no){
$para = array(
'appid' => $this->_config['APPID'],
'mch_id' => $this->_config['MCHID'],
'nonce_str' => $this->getNonceStr(),
'out_trade_no' => $out_trade_no
);
return $this->getHttpResponsePOST(self::URL_CLOSE_ORDER, $para);
}
/**
* refund 申請退款
* @access public
* @param array $para 參數(shù)數(shù)組
* @param bool $mode
* @return array
*/
public function refund($para = array(), $mode = false){
$para['appid'] = $this->_config['APPID'];
$para['mch_id'] = $this->_config['MCHID'];
$para['nonce_str'] = $this->getNonceStr();
if ($mode) { // 使用微信訂單號退款
if ( ! isset($para['transaction_id'])) return false;
} else { // 使用商戶訂單號退款
if ( ! isset($para['out_trade_no'])) return false;
}
if ( ! isset($para['out_refund_no'])) return false;
if ( ! isset($para['total_fee'])) return false;
if ( ! isset($para['refund_fee'])) return false;
if ( ! isset($para['op_user_id'])) $para['op_user_id'] = $this->_config['MCHID'];
return $this->getHttpResponsePOST(self::URL_REFUND, $para, true);
}
/**
* downloadBill 下載對賬單
* @access public
* @param string $bill_date 下載對賬單的日期,格式:20140603
* @param string $bill_type 類型
* @return array
*/
public function downloadBill($bill_date, $bill_type = 'ALL'){
$para = array();
$para['appid'] = $this->_config['APPID'];
$para['mch_id'] = $this->_config['MCHID'];
$para['nonce_str'] = $this->getNonceStr();
$para['bill_date'] = $bill_date;
$para['bill_type'] = $bill_type;
return $this->getHttpResponsePOST(self::URL_DOWNLOAD_BILL, $para);
}
/**
* 獲取js支付使用的第二個(gè)參數(shù)
*/
public function get_package($prepay_id) {
$data = array();
$data['appId'] = $this->_config['APPID'];
$data['nonceStr'] = $this->getNonceStr();
$data['timeStamp'] = time();
$data['package'] = 'prepay_id='.$prepay_id;
$data['signType'] = 'MD5';
$data['paySign'] = $this->sign($data);
return $data;
}
/**
* getWxpayBackData 獲取微信返回?cái)?shù)據(jù)
* @access public
* @param void
* @return array | bool
*/
public function getWxpayBackData() {
$xml = file_get_contents('php://input');
$data = $this->xml2array($xml);
return $this->checkSign($data) ? $data : NULL;
}
/**
* replyNotify 回復(fù)通知
* @access public
* @param string $return_code 返回狀態(tài)碼
* @param string $return_msg 返回信息
* @return void
*/
final public function replyNotify($return_code = 'SUCCESS', $return_msg = ''){
$data = array();
$data['return_code'] = $return_code;
if ($return_msg) $data['return_msg'] = $return_msg;
echo $this->array2xml($data);
}
/**
* sign 生成簽名
* @access private
* @param array $data
* @return string
*/
private function sign($data = array()){
// 簽名步驟一 按字典序排序參數(shù)
ksort($data);
$buff = '';
// 去除空值
// 把數(shù)組所有元素 按照“參數(shù)=參數(shù)值”的模式用“&”字符拼接成字符串
// 簽名步驟二 格式化參數(shù)格式化成url參數(shù)
foreach ($data as $k => $v) {
if($k != 'sign' && $v != '' && ! is_array($v)){
$buff .= $k . '=' . $v . '&';
}
}
$buff = trim($buff, '&');
// 簽名步驟三 在buff后加入KEY
$string_sign_temp = $buff . '&key=' . $this->_config['KEY'];
return strtoupper(md5($string_sign_temp));
}
/**
* checkSign 驗(yàn)證數(shù)據(jù)簽名
* @access public
* @param array $data 數(shù)據(jù)數(shù)組
* @return bool 數(shù)據(jù)校驗(yàn)結(jié)果
*/
public function checkSign($data) {
if ( ! isset($data['sign'])) return false;
$sign = $data['sign'];
unset($data["sign"]);
return $this->sign($data) == $sign;
}
/**
* array2xml 將數(shù)組轉(zhuǎn)換為XML
* @access private
* @param array $arr
* @return xml
*/
private function array2xml($arr = array()){
$xml = '<xml>' . PHP_EOL;
foreach ($arr as $k => $v) {
if ($v > 0 && trim($v) != '' && ! is_array($v)) {
if (is_numeric($v)) {
$xml.='<'.$k.'>'.$v.'</'.$k.'>' . PHP_EOL;
}else{
$xml.='<'.$k.'><![CDATA['.$v.']]></'.$k.'>' . PHP_EOL;
}
}
}
$xml .= '</xml>';
return $xml;
}
/**
* xml2array 將XML轉(zhuǎn)換為數(shù)組
* @access private
* @param string $xml
* @return array $arr
*/
private function xml2array($xml) {
try {
return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
} catch(Exception $e) {
}
}
/**
* getNonceStr 產(chǎn)生隨機(jī)字符串庐舟,不長于32位
* @access public
* @param void
* @return 產(chǎn)生的隨機(jī)字符串
*/
public function getNonceStr($len = 32){
return substr(str_shuffle("abcdefghijklmnopqrstuvwxyz0123456789"), 0, $len);
}
/**
* getHttpResponsePOST 遠(yuǎn)程獲取數(shù)據(jù) POST模式
* @access private
* @param string $url
* @param array $data
* @param bool $is_need_cert
* @return array
*/
private function getHttpResponsePOST($url, $data = array(), $is_need_cert = false){
$data['sign'] = $this->sign($data);
$xml = $this->array2xml($data);
$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_URL, $url);
if ($is_need_cert == true) {
//使用證書 cert 與 key 分別屬于兩個(gè).pem文件
curl_setopt($ch,CURLOPT_SSLCERTTYPE, 'PEM');
curl_setopt($ch,CURLOPT_SSLCERT, $this->_config['SSLCERT_PATH']);
curl_setopt($ch,CURLOPT_SSLKEYTYPE, 'PEM');
curl_setopt($ch,CURLOPT_SSLKEY, $this->_config['SSLKEY_PATH']);
}
$content = curl_exec($ch);
$array = $this->xml2array($content);
return $array;
}
/**
* logDebug
* 寫日志纽乱,方便測試(看網(wǎng)站需求幸缕,也可以改成把記錄存入數(shù)據(jù)庫)
* 注意:服務(wù)器需要開通fopen配置
* @access public
* @param string $content 要寫入日志里的文本內(nèi)容 默認(rèn)值:空值
* @return void
*/
public function logDebug($content = '') {
$fp = fopen($this->_config['LOG_PATH'], 'a+');
flock($fp, LOCK_EX); // 文件鎖定 避免其他人同時(shí)操作
fwrite($fp, '# Wxpay Debug : ' . date('Y-m-d H:i:s') . ' # '. $content . "\n\n");
flock($fp, LOCK_UN);
fclose($fp);
}
}