微信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;
		}
	}
}

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