微信扫码支付和微信JSAPI支付

项目中用到了PC端扫码支付和 微信公众号的JSAPI支付,在此记录, 以免小伙伴被网上的‘拿来主义’给误导。

使用框架THINKPHP5, 类文件保存在extend/payment 文件夹内。

包含功能:扫码支付(采用先生成预支付订单,然后返回支付二维码地址,在页面上使用qrcode.js 生成二维码 ),JSAPI支付。

 

欢迎访问: http://www.kevink.club 查看更多内容

 


 *
 * @DateTime 2018-11-15 14:53:21
 * 微信支付
 */
class Wxwebpay {

	//密钥
	private $key;
	//商户号
	private $mch_id;
	//微信公众号ID
	private $appid;
	//支付方式ID
	private $payment_id;

	public function __construct(){

		$payment = Db::name('payment')->where('code', 'wxwebpay')->find();
		$json = json_decode($payment['json'], true);

		$this->key = $payment['key'];
		$this->mch_id = $payment['mch_id'];
		$this->appid = $json['appid'];
		$this->payment_id = $payment['id'];
	}

	/**
	 * @author     mselect <[email protected]>
	 *
	 * @DateTime 2018-11-15 15:14:29
	 * 统一下单
	 *
	 * @param        $order_id  The order identifier
	 */
	public function pay($order_id){

		$order = Db::name('order')->where('id', $order_id)->find();

		//微信统一下单地址
		$url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';

		$notify_url = '异步回调地址';

		$param = [
			'appid' => $this->appid,
			'mch_id' => $this->mch_id,
			//'device_info' => '',
			'nonce_str' => $this->create_nonce_str(),
			'sign' => '',
			//'sign_type' => 'MD5' , 
			'body' => $order['body'],
			//'detail' => '',
			//'attach' => '',
			'out_trade_no' => $order['order_unique'],
			//'fee_type' => 'CNY',
			'total_fee' => $order['real_total_fee'] * 100, 
			'spbill_create_ip' => '***',
			//'time_start' => '',
			//'time_expire' => '',
			//'goods_tag' => '',
			'notify_url' => $notify_url,
			'trade_type' => 'NATIVE',
			'product_id' => $order['order_unique'],
			//'limit_pay' => '',
			//'openid' => '',
			//'scene_info' => '',
		];

		//组合sign 数组
		$signdata = [];
		$signdata['appid'] = $param['appid'];
		$signdata['mch_id'] = $param['mch_id'];
		$signdata['nonce_str'] = $param['nonce_str'];
		$signdata['body'] = $param['body'];
		$signdata['out_trade_no'] = $param['out_trade_no'];
		$signdata['total_fee'] = $param['total_fee'];
		$signdata['spbill_create_ip'] = $param['spbill_create_ip'];
		$signdata['notify_url'] = $param['notify_url'];
		$signdata['trade_type'] = $param['trade_type'];
		$signdata['product_id'] = $param['product_id'];
		//生成sign
		$sign = $this->get_sign($signdata);
		$param['sign'] = $sign;
		$xml = $this->array2xml($param);

		 
		//访问接口
		$return = $this->curl_xml($xml, $url);
		//返回xml 转化成数组
		$back = $this->xml2array($return);

		if($back['return_code'] == 'SUCCESS'){

			if($back['result_code'] == 'SUCCESS'){
				//用于生成用户扫描的二维码链接
				$code_url = $back['code_url'];
				return ['code' =>1 , 'msg' => '成功' , 'data'=>['code_url'=>$code_url] ];

			}else {
				file_put_contents('wxwebpay.log', 'result code:'.$back['err_code'] . ', result msg:'. $back['err_code_des'] . '\r\n', FILE_APPEND);
				return ['code' =>-1, 'msg' => '错误代码:'. $back['err_code'] . ',错误描述:' . $back['err_code_des']];
			}
		}else {
			file_put_contents('wxwebpay.log', 'return code:' . $back['return_code'] . ', return msg:' . $back['return_msg'] . '\r\n' , FILE_APPEND );
			return ['code' => -1, 'msg' => '错误代码:' . $back['return_code'] . ', 错误描述:' . $back['return_msg']];
		}

	}


	/**
	 * @author     mselect <[email protected]>
	 *
	 * @DateTime 2018-10-09 17:28:49
	 * 产生随机字符串
	 *
	 * @param      integer  $length  The length
	 */
	private function create_nonce_str($length =32){
		$chars = "abcdefghijklmnopqrstuvwxyz0123456789";
		$str = "";
		for ($i = 0; $i < $length; $i++) {
            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
        }
        return $str;
	}

		/**
	 * @author     mselect <[email protected]>
	 *
	 * @DateTime 2018-10-09 17:31:02
	 * 生成签名
	 *
	 * @param        $arr    The arr
	 */
	private function get_sign($arr){
		foreach ($arr as $k => $v) {
            $Parameters[$k] = $v;
        }
        //签名步骤一:按字典序排序参数
        ksort($Parameters);
        $String = $this->formatBizQueryParaMap($Parameters, false);
        //签名步骤二:在string后加入KEY
        $String = $String . "&key=" . $this->key;
        //签名步骤三:MD5加密
        $String = md5($String);
        //签名步骤四:所有字符转为大写
        $result = strtoupper($String);
        return $result;
	}

		///作用:格式化参数,签名过程需要使用
    private function formatBizQueryParaMap($paraMap, $urlencode) {
        $buff = "";
        ksort($paraMap);
        foreach ($paraMap as $k => $v) {
            if ($urlencode) {
                $v = urlencode($v);
            }
            $buff .= $k . "=" . $v . "&";
        }
        $reqPar;
        if (strlen($buff) > 0) {
            $reqPar = substr($buff, 0, strlen($buff) - 1);
        }
        return $reqPar;
    }

		/**
	 * @author     mselect <[email protected]>
	 *
	 * @DateTime 2018-10-10 14:59:32
	 * 数组转为XML
	 */
	private function array2xml($arr){
		$xml = "";
        foreach ($arr as $key => $val) {
            if (is_array($val)) {
                $xml .= "<" . $key . ">" . arrayToXml($val) . "";
            } else {
                $xml .= "<" . $key . ">" . $val . "";
            }
        }
        $xml .= "";
        return $xml;

	}

		/**
	 * @author     mselect <[email protected]>
	 *
	 * @DateTime 2018-10-10 15:00:27
	 * 访问接口
	 */
	private function curl_xml($xml, $url, $second =30, $use_cert = false){
		$ch = curl_init();
        //设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        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);
        if($use_cert){
        	 
        	//第一种方法,cert 与 key 分别属于两个.pem文件
			//默认格式为PEM,可以注释
			curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
			curl_setopt($ch,CURLOPT_SSLCERT, EXTEND_PATH.'/payment/cert/cert.pem');
			//默认格式为PEM,可以注释
			curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
			curl_setopt($ch,CURLOPT_SSLKEY, EXTEND_PATH.'/payment/cert/key.pem');
        }
        //post提交方式
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
 
 
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 20);
        curl_setopt($ch, CURLOPT_TIMEOUT, 40);
        set_time_limit(0);
 
 
        //运行curl
        $return  = curl_exec($ch);
        
        //返回结果
        if($return){
        	curl_close($ch);
        	return $return;
        }else {
        	$error = curl_errno($ch);
        	curl_close($ch);
        	return 'FAIL'.$error.'';
        }
        
	}

		/**
	 * @author     mselect <[email protected]>
	 *
	 * @DateTime 2018-10-10 16:26:17
	 * xml转为array
	 *
	 * @param        $xml    The xml
	 *
	 * @return       ( description_of_the_return_value )
	 */
	public function xml2array($xml){
		libxml_disable_entity_loader(true);
        $xmlstring = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
        $val = json_decode(json_encode($xmlstring), true);
        return $val;
	}


	/**
	 * @author     mselect <[email protected]>
	 *
	 * @DateTime 2018-11-15 17:31:47
	 * 支付结果通知
	 */
	public function notify($data){

		$signdata = $data;
		unset($signdata['sign']);
		$sign = $this->get_sign($signdata);
		$time = time();
		if($data['sign'] == $sign){

			//根据支付唯一码找到订单
			$order = Db::name('order')->where('order_unique', $data['out_trade_no'])->where('status', -1)->find();
			if(!empty($order)){

				//查询订单状态
				$rst = $this->orderquery($order['id']);
				if($rst['code'] ==1 ){
					//微信返回的订单状态为成功

					//检查订单金额
					if($order['real_total_fee']* 100 == $data['total_fee']){
						//订单金额正确

						Db::startTrans();

						try {
							//修改订单状态
							$order_data = [];
							$order_data['status'] =1;
							$order_data['pay_time'] = $time;
							$order_data['trade_no'] = $data['transaction_id'];
							$order_data['payment_id'] = $this->payment_id;
							$rst = Db::name('order')->where('id', $order['id'])->update($order_data);

							if($order['type'] == 1){

								//赛事

								//修改报名用户表状态
								$ordermh =  Db::name('order_match')->where('order_id',$order['id'])->find();
								$rst = Db::name('match_member')->where('id', $ordermh['mhmember_id'])->update(['status'=>1]);

								$match_member = Db::name('match_member')->where('id', $ordermh['mhmember_id'])->find();
								$match = Db::name('match')->where('id', $match_member['match_id'])->find();

								//match 表报名总人数+1
								$rst = Db::name('match')->where('id', $match['id'])->setInc('order_count');

								//添加赛事报名记录
								$log_mm = [];
								$log_mm['match_id'] = $match_member['match_id'];
								$log_mm['member_id'] = $match_member['member_id'];
								$log_mm['create_time'] = $time;
								$log_mm['content'] = '[赛事]报名赛事:' .$match['title'] . ', 支付成功';
								$rst = Db::name('log_match_member')->insertGetId($log_mm);

								//添加订单日志
								$log_order = [];
								$log_order['order_id'] = $order['id'];
								$log_order['content'] = "[赛事]报名赛事:" . $match['title'] . ', 支付成功';
								$log_order['create_time'] = $time;
								$rst = Db::name('log_order')->insertGetId($log_order);

								//添加平台资金日志
								$log_money = [];
								$log_money['type'] = $order['venue_id'] == -1 ? 2 : 1;
								$log_money['money'] = $order['real_total_fee'];
								$log_money['content'] = '[赛事] 用户ID:'.$order['member_id'].' , 报名赛事:' . $match['title'] . ', 赛事ID:' . $match['id'] . ', 支付:' . $order['real_total_fee'];
								$log_money['create_time'] = $time;
								$rst = Db::name('log_money')->insertGetId($log_money);

								if($order['venue_id'] >0 ){
									//添加场馆资金日志
									$lvm = [];
									$lvm['type'] = 2;
									$lvm['venue_id'] = $order['venue_id'];
									$lvm['money'] = $order['real_total_fee'];
									$lvm['content'] = '[赛事] 用户ID:' . $order['member_id'] . ', 报名赛事:' .$match['title'] . ', 赛事ID:' . $match['id'] . ', 支付:' . $order['real_total_fee'];
									$lvm['create_time'] = $time;
									$rst = Db::name('log_venue_money')->insertGetId($lvm);
								}
							}else if ($order['type'] == 2){

								//代金券
								//修改用户代金券表
								$ordercp = Db::name('order_coupon')->where('order_id', $order['id'])->find();
								//生成唯一码
								$extra = cmf_create_rand_str(9);
								$rst = Db::name('coupon_member')->where('id', $ordercp['cpnmember_id'])->update(['status' =>1, 'extra'=>$extra ]);
									
								$coupon_member = Db::name('coupon_member')->where('id', $ordercp['cpnmember_id'])->find();
								$coupon = Db::name('venue_coupon')->where('id', $coupon_member['coupon_id'])->find();
								//添加代金券领取记录
								$log_cm = [];
								$log_cm['coupon_id'] = $coupon_member['coupon_id'];
								$log_cm['member_id'] = $coupon_member['member_id'];
								$log_cm['create_time'] = $time;
								$log_cm['content'] = '[代金券]购买代金券:' . $coupon['title'] . ', 已支付';
								$rst = Db::name('log_coupon_member')->insertGetId($log_cm);

								//添加订单记录
								$log_order = [];
								$log_order['order_id'] = $order['id'];
								$log_order['content'] = '[代金券]购买代金券:' . $coupon['title'] . ', 已支付' ;
								$log_order['create_time'] = $time;
								$rst = Db::name('log_order')->insertGetId($log_order);

								//添加平台资金日志
								$log_money = [];
								$log_money['type'] = 3;
								$log_money['money'] = $order['real_total_fee'];
								$log_money['content'] = '[代金券]用户ID:' . $order['member_id'] . ', 购买代金券:' .$coupon['title'] . ', 代金券ID:' .$coupon['id'] . ', 支付:' . $order['real_total_fee'];
								$log_money['create_time'] = $time;
								$rst = Db::name('log_money')->insertGetId($log_money);

								if($order['venue_id'] > 0 ){
									//添加场馆资金记录
									$log_vm = [];
									$log_vm['type'] = 1;
									$log_vm['money'] = $order['real_total_fee'];
									$log_vm['content'] = '[代金券]用户ID:' . $order['member_id'] . ', 购买代金券:' .$coupon['title'] . ', 代金券ID:' .$coupon['id'] . ', 支付:' . $order['real_total_fee'];
									$log_vm['create_time'] = $time;
									$rst = Db::name('log_venue_money')->insertGetId($log_vm);
								}
							}
							Db::commit();
						}catch(\Exception $e){
							Db::rollback();
							file_put_contents('wxwebnotify.log', '微信web支付通知数据库操作错误:' . $e->getMessage() . '\r\n', FILE_APPEND);
							exit;
						}

						if($order['type'] ==1 ){
							//发送成功短信通知
							$yunxin = new Yunxin;
							$arr = [];
							$arr[] = $match_member['name'];
							$arr[] = $match['title'];
							$arr[] = date("Y年m月d日H:i", $match['mhstart']);
							$arr[] = $match_member['idno'];
							$yunxin->sendSMSTemplate( 9294589, [$match_member['telephone']], $arr);
						}

						echo '
				              
				              
				          ';
				          exit;

					}

				}else {
					file_put_contents('wxwebpaynotify.txt', '查询订单状态错误\r\n', FILE_APPEND);
				}
			}else {
				file_put_contents('wxwebpaynotify.txt', ' 未找到订单\r\n' , FILE_APPEND );
			}
		}else {
			file_put_contents('wxwebpaynotify.txt', '签名验证失败 生成签名为:' . $sign . '\r\n' , FILE_APPEND );
		}
	}


	/**
	 * @author     mselect <[email protected]>
	 *
	 * @DateTime 2018-11-15 17:33:11
	 * 查询订单
	 */
	public function orderquery($order_id){

		$order = Db::name('order')->where('id', $order_id)->find();

		//查询订单链接
		$url = "https://api.mch.weixin.qq.com/pay/orderquery";
		if(!empty($order['trade_no'])){
			$param = [
				'appid' => $this->appid,
				'mch_id' => $this->mch_id,
				'transaction_id' => $order['trade_no'],
				'nonce_str' => $this->create_nonce_str(),
				'sign' => '',
			];
			//组合生成签名数据
			$signdata = [];
			$signdata['appid'] = $param['appid'];
			$signdata['mch_id'] = $param['mch_id'];
			$signdata['transaction_id'] = $param['transaction_id'];
			$signdata['nonce_str'] = $param['nonce_str'];
		}else {
			$param = [
				'appid' => $this->appid,
				'mch_id' => $this->mch_id,
				'out_trade_no' => $order['order_unique'],
				'nonce_str' => $this->create_nonce_str(),
				'sign' => '',
			];
			//组合生成签名数据
			$signdata = [];
			$signdata['appid'] = $param['appid'];
			$signdata['mch_id'] = $param['mch_id'];
			$signdata['out_trade_no'] = $param['out_trade_no'];
			$signdata['nonce_str'] = $param['nonce_str'];
		}

		//生成sign
		$sign = $this->get_sign($signdata);
		$param['sign'] = $sign;
		$xml = $this->array2xml($param);
		//访问接口
		$return = $this->curl_xml($xml, $url);
		//返回xml 转化成数组
		$back = $this->xml2array($return);
		
		if($back['return_code'] == 'SUCCESS'){

			if($back['result_code'] == 'SUCCESS'){

				if($back['trade_state'] != 'SUCCESS' || $back['total_fee'] != $order['real_total_fee']*100 ){
					return ['code' => -1, 'msg' => '支付失败' ];
				}else {
					return ['code' => 1 , 'msg' => '支付成功'];
				}
			}else {
				file_put_contents('wxwebpayquery.log', '错误代码:'.$back['err_code'] . ', 错误信息:' . $back['err_code_des']);
				return ['code' => -1, 'msg' => '错误代码:' . $back['err_code'] . ', 错误信息:' .$back['err_code_des']  ];
			}
		}else {
			file_put_contents('wxwebpayquery.log', '错误代码:'.$back['return_code'] . ',错误信息:'.$back['return_msg'] . '\r\n', FILE_APPEND);
			return ['code' => -1, 'msg' => '错误代码:' . $back['return_code'] . ', 错误信息:' . $back['return_msg']  ];
		}
	}

	/**
	 * @author     mselect <[email protected]>
	 *
	 * @DateTime 2018-11-21 10:19:00
	 * 微信JSAPI 支付
	 *
	 * @param        $order_id  The order identifier
	 */
	public function h5pay($order_id){

		$order = Db::name('order')->where('id', $order_id)->find();
		//微信统一下单地址
		$url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
		$notify_url = '异步回调地址';

		$member = Db::name('member')->where('id', $order['member_id'])->find();

		$param = [
			'appid' => $this->appid,
			'mch_id' => $this->mch_id,
			//'device_info' => '',
			'nonce_str' => $this->create_nonce_str(),
			'sign' => '',
			//'sign_type' => 'MD5' , 
			'body' => $order['body'],
			//'detail' => '',
			//'attach' => '',
			'out_trade_no' => $order['order_unique'],
			//'fee_type' => 'CNY',
			'total_fee' => $order['real_total_fee'] * 100, 
			'spbill_create_ip' => '***',
			//'time_start' => '',
			//'time_expire' => '',
			//'goods_tag' => '',
			'notify_url' => $notify_url,
			'trade_type' => 'JSAPI',
			//'product_id' => $order['order_unique'],
			//'limit_pay' => '',
			'openid' => $member['openid'],
			//'scene_info' => '',
		];

		//组合sign 数组
		$signdata = [];
		$signdata['appid'] = $param['appid'];
		$signdata['mch_id'] = $param['mch_id'];
		$signdata['nonce_str'] = $param['nonce_str'];
		$signdata['body'] = $param['body'];
		$signdata['out_trade_no'] = $param['out_trade_no'];
		$signdata['total_fee'] = $param['total_fee'];
		$signdata['spbill_create_ip'] = $param['spbill_create_ip'];
		$signdata['notify_url'] = $param['notify_url'];
		$signdata['trade_type'] = $param['trade_type'];
		//$signdata['product_id'] = $param['product_id'];
		$signdata['openid'] = $param['openid'];
		//生成sign
		$sign = $this->get_sign($signdata);
		$param['sign'] = $sign;
		$xml = $this->array2xml($param);

		//访问接口
		$return = $this->curl_xml($xml, $url);
		//返回xml 转化成数组
		$back = $this->xml2array($return);
		if($back['return_code'] == 'SUCCESS'){
			if($back['result_code'] == 'SUCCESS'){
				 
				//生成JSAPI调用参数
				$data = [];

				$param2 = [
					'appId' => $this->appid,							//公众号ID
					'timeStamp' => (string)time(),						//时间戳
					'nonceStr' => $this->create_nonce_str(),						//随机字符串
					'package' => 'prepay_id=' . $back['prepay_id'],						//订单详情扩展字符串
					'signType' => 'MD5',						//签名方式
					'paySign' => '',						//签名
				];

				//组合签名数据
				$signdata = [];
				$signdata = [
					'appId' => $param2['appId'],
					'timeStamp' => $param2['timeStamp'],
					'nonceStr' => $param2['nonceStr'],
					'package' => $param2['package'],
					'signType' => $param2['signType'],
				];

				$sign = $this->get_sign($signdata);
				$param2['paySign'] = $sign;
				 
				return ['code' =>1 , 'msg' => '成功' , 'data'=>$param2 ];

			}else {
				file_put_contents('wxwebpay.log', 'result code:'.$back['err_code'] . ', result msg:'. $back['err_code_des'] . '\r\n', FILE_APPEND);
				return ['code' =>-1, 'msg' => '错误代码:'. $back['err_code'] . ',错误描述:' . $back['err_code_des']];
			}
		}else {
			file_put_contents('wxwebpay.log', 'return code:' . $back['return_code'] . ', return msg:' . $back['return_msg'] . '\r\n' , FILE_APPEND );
			return ['code' => -1, 'msg' => '错误代码:' . $back['return_code'] . ', 错误描述:' . $back['return_msg']];
		}
	}

}

?>

你可能感兴趣的:(PHP)