微信APP支付工具類

微信APP支付工具類,已經測試通過,可以使用,如果是安卓返回數字類型的數據時請將數字轉換爲字符型再返回給客戶端,畢竟PHP是弱類型語言而java是強類型語言,這個坑也是讓博主很蛋疼很久的呢。以下是代碼片段:

<?php
// +----------------------------------------------------------------------
// | 微信APP支付工具類
// +----------------------------------------------------------------------
// | Author:  dorisnzy 
// +----------------------------------------------------------------------
// | History:
// | 	[2017-05-26] 	dorisnzy 	first release
// +----------------------------------------------------------------------

namespace Common\Util;

class WechatAppPay {
	/* 統一下單地址 */
	const UNIFIED_ORDER_URL = 'https://api.mch.weixin.qq.com/pay/unifiedorder';

	private $appid; // 應用ID

	private $secret; // app_secret

	private $mch_id; // 商戶ID

	private $key; // 簽名key

	private $ssl_pem_cret; // 證書cret路徑

	private $ssl_pem_key; // 證書key

	private $trade_type = 'APP'; // 支付類型

	private static $instance; // 本類實例對象

	public $error; // 錯誤信息

	private function __construct($option = array()) {
		$this->appid 	=  	isset($option['appid']) 	? 	$option['appid'] 	: 	'';
		$this->secret   = 	isset($option['secret']) 	? 	$option['secret']	:	'';
		$this->mch_id 	= 	isset($option['mch_id']) 	? 	$option['mch_id'] 	:	'';
		$this->key 		=	isset($option['key'])		? 	$option['key']		: 	'';
		$this->ssl_pem_cret = 	isset($option['ssl_pem_cret']) ? $option['ssl_pem_cret'] : '';
		$this->ssl_pem_key  =   isset($option['ssl_pem_key'])  ? $option['ssl_pem_key']  : '';
	}

	public function setConfig($config, $value) {
		$this->$config = $value;
	}

	public function __get($key) {
		return $this->$key;
	}

	public function __set($key, $value) {
		$this->$key = $value;
	}

	/**
	 * 獲取當前類實例,採用單例模式
	 * 參數值
	 * @return object 
	 */
	public static function getInstance($option) {
		if (self::$instance == null) {
			self::$instance = new self($option);
		}
		return self::$instance;
	}

	/**
	 * 生成一個20位的訂單號,最好是使用1位的前綴
	 * @param  string $prefix 訂單號前綴,區分業務類型
	 * @return string
	 */
	public static function createOrderId($prefix = '') {
		$code = date('ymdHis').sprintf("%08d", mt_rand(1, 99999999));
		if (!empty($prefix)) {
			$code = $prefix.substr($code, strlen($prefix));
		}
		return $code;
	}

	/**
	 * 統一下單接口生成支付請求
	 * @param  $body        string  商品描述 少於127字節
	 * @param  $orderId     string  系統中唯一訂單號
	 * @param  $money       integer 支付金額(單位:元)
	 * @param  $notify_url  string  通知URL
	 * @param  $extend      array|string   擴展參數
	 * @return json|boolean json 直接可賦給APP接口使用,boolean錯誤
	 */
	public function unifiedOrder($body, $orderId, $money, $notify_url = '', $extend = array()) {
		if (strlen($body) > 127) $body = substr($body, 0, 127);
		$params = array(
			'appid'            => $this->appid,
			'mch_id'           => $this->mch_id,
			'nonce_str'        => self::_getRandomStr(),
			'body'             => $body,
			'out_trade_no'     => $orderId,
			'total_fee'        => $money * 100, // 轉換成分
			'spbill_create_ip' => get_client_ip(),
			'notify_url'       => $notify_url,
			'trade_type'       => 'APP',
		);
		if (is_string($extend)) {
			$params['attach']  = $extend;
		} elseif (is_array($extend) && !empty($extend)) {
			$params = array_merge($params, $extend);
		}
		// 生成簽名
		$params['sign'] = self::_getOrderMd5($params);
		$data = self::_array2Xml($params);
		$data = $this->http(self::UNIFIED_ORDER_URL, $data, 'POST');
		if (!$data) {
			return false;
		}
		$data = self::_extractXml($data);
		if ($data) {
			if ($data['return_code'] == 'SUCCESS') {
				if ($data['result_code'] == 'SUCCESS') {
					return $this->createPayParams($data['prepay_id']);
				} else {
					$this->error = $data['err_code'];
					return false;
				}
			} else {
				$this->error = $data['return_msg'];
				return false;
			}
		} else {
			$this->error = '創建訂單失敗';
			return false;
		}
	}

	/**
	 * 生成支付參數
	 */
	private function createPayParams($prepay_id) {
		if (empty($prepay_id)) {
			$this->error = 'prepay_id參數錯誤';
			return false;
		}
		$params['appid']     = $this->appid;
		$params['noncestr']  = self::_getRandomStr();
		$params['package']   = 'Sign=WXPay';
		$params['prepayid']  = $prepay_id; 
		$params['partnerid'] = $this->mch_id;

		$params['timestamp'] = time();
		$params['sign']   = self::_getOrderMd5($params);
		return $params;
	}

	/**
	 * 解析支付接口的返回結果
	 */
	public function parsePayRequest($data) {
		$data = self::_extractXml($data);
		if (empty($data) || empty($data[0])) {
			$this->error = '支付返回內容解析失敗';
			return false;
		}
		if (!self::_checkSign($data)) return false;
		// 有返回結果 並且是SUCCESS的時候
		if ($data['return_code'] == 'SUCCESS') {
			if ($data['result_code'] == 'SUCCESS') {
				return $data;
			} else {
				$this->error = $data['err_code'];
				return false;
			}
		} else {
			$this->error = $data['return_msg'];
			return false;
		}
	}

	/**
	 * 接口通知接收
	 */
	public function getNotify() {
		$data = $GLOBALS["HTTP_RAW_POST_DATA"];
		return self::parsePayRequest($data);
	}

	/**
	 * 對支付回調接口返回成功通知
	 */
	public function returnNotify($return_msg = true) {
		if ($return_msg == true) {
			$data = array(
				'return_code' => 'SUCCESS',
			);
		} else {
			$data = array(
				'return_code' => 'FAIL',
				'return_msg'  => $return_msg
			);
		}
		exit(self::_array2Xml($data));
	}

	/**
	 * 接收數據簽名校驗
	 */
	private function _checkSign($data) {
		$sign = $data['sign'];
		unset($data['sign']);
		if (self::_getOrderMd5($data) != $sign) {
			$this->error = '簽名校驗失敗';
 			return false;
		} else {
			return true;
		}
	}

	/**
	 * 捕獲錯誤信息
	 * @return string 中文錯誤信息
	 */
	public function getError() {
		return $this->error;
	}

	/**
	 * 本地MD5簽名
	 */
	private function _getOrderMd5($params) {
		ksort($params);
		$params['key'] = $this->key;
		return strtoupper(md5(urldecode(http_build_query($params))));
	}

	/**
	 * XML文檔解析成數組,並將鍵值轉成小寫
	 * @param  xml $xml
	 * @return array
	 */
	private function _extractXml($xml) {
		$data = (array)simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
		return array_change_key_case($data, CASE_LOWER);
	}

	private function _array2Xml($array) {
		$xml  = new \SimpleXMLElement('');
		$this->_data2xml($xml, $array);
		return $xml->asXML();
	}

	/**
	 * 數據XML編碼
	 * @param  object $xml  XML對象
	 * @param  mixed  $data 數據
	 * @param  string $item 數字索引時的節點名稱
	 * @return string xml
	 */
	private function _data2xml($xml, $data, $item = 'item') {
		foreach ($data as $key => $value) {
			/* 指定默認的數字key */
			is_numeric($key) && $key = $item;
			/* 添加子元素 */
			if(is_array($value) || is_object($value)){
				$child = $xml->addChild($key);
				$this->_data2xml($child, $value, $item);
			} else {
				if(is_numeric($value)){
					$child = $xml->addChild($key, $value);
				} else {
					$child = $xml->addChild($key);
					$node  = dom_import_simplexml($child);
					$node->appendChild($node->ownerDocument->createCDATASection($value));
				}
			}
		}
	}

	/**
	 * 不轉義中文字符和\/的 json 編碼方法
	 * @param  array $array
	 * @return json
	 */
	private function json_encode($array = array()) {
		$array = str_replace("\\/", "/", json_encode($array));
		$search = '#\\\u([0-9a-f]+)#ie';
		if (strpos(strtoupper(PHP_OS), 'WIN') === false) {
			$replace = "iconv('UCS-2BE', 'UTF-8', pack('H4', '\\1'))";//LINUX
		} else {
			$replace = "iconv('UCS-2', 'UTF-8', pack('H4', '\\1'))";//WINDOWS
		}
		return preg_replace($search, $replace, $array);
	}

	/**
	 * 解析JSON編碼,如果有錯誤,則返回錯誤並設置錯誤信息d
	 * @param json $json json數據
	 * @return array
	 */
	private function parseJson($json) {
		$jsonArr = json_decode($json, true);
		if (isset($jsonArr['errcode'])) {
			if ($jsonArr['errcode'] == 0) {
				$this->result = $jsonArr;
				return true;
			} else {
				$this->error = $this->ErrorCode($jsonArr['errcode']);
				return false;
			}
		}else {
			return $jsonArr;
		}
	}

	/**
	 * 返回隨機填充的字符串
	 */
	private function _getRandomStr($lenght = 16)	{
		$str_pol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
		return substr(str_shuffle($str_pol), 0, $lenght);
	}

	/**
	 * 發送HTTP請求方法,目前只支持CURL發送請求
	 * @param  string  $url    請求URL
	 * @param  array   $params 請求參數
	 * @param  string  $method 請求方法GET/POST
	 * @param  boolean $ssl    是否進行SSL雙向認證
	 * @return array   $data   響應數據
	 * @author 、小陳叔叔 
	 */
	private function http($url, $params = array(), $method = 'GET', $ssl = false){
		$opts = array(
			CURLOPT_TIMEOUT        => 30,
			CURLOPT_RETURNTRANSFER => 1,
			CURLOPT_SSL_VERIFYPEER => false,
			CURLOPT_SSL_VERIFYHOST => false
		);
		/* 根據請求類型設置特定參數 */
		switch(strtoupper($method)){
			case 'GET':
				$getQuerys = !empty($params) ? '?'. http_build_query($params) : '';
				$opts[CURLOPT_URL] = $url . $getQuerys;
				break;
			case 'POST':
				$opts[CURLOPT_URL] = $url;
				$opts[CURLOPT_POST] = 1;
				$opts[CURLOPT_POSTFIELDS] = $params;
				break;
		}
		if ($ssl) {
			$pemPath = dirname(__FILE__).'/Wechat/';
			$pemCret = $pemPath.$this->pem.'_cert.pem';
			$pemKey  = $pemPath.$this->pem.'_key.pem';
			if (!file_exists($pemCret)) {
				$this->error = '證書不存在';
				return false;
			}
			if (!file_exists($pemKey)) {
				$this->error = '密鑰不存在';
				return false;
			}
			$opts[CURLOPT_SSLCERTTYPE] = 'PEM';
			$opts[CURLOPT_SSLCERT]     = $pemCret;
			$opts[CURLOPT_SSLKEYTYPE]  = 'PEM';
			$opts[CURLOPT_SSLKEY]      = $pemKey;
		}
		/* 初始化並執行curl請求 */
		$ch     = curl_init();
		curl_setopt_array($ch, $opts);
		$data   = curl_exec($ch);
		$err    = curl_errno($ch);
		$errmsg = curl_error($ch);
		curl_close($ch);
		if ($err > 0) {
			$this->error = 'ErrorCode:'.$err.', Msg:'.$errmsg;
			return false;
		}else {
			return $data;
		}
	}
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章