基于PHP+HTML的“微信支付”全流程(2021)

1.注册微信支付的商户

2.将程序的路径配置在微信支付后台

3.创建订单信息

在微信浏览器上访问HTML页面上操作。根据传入用户openid、金额,返回的订单创建的信息。代码分为以下三个组成部分:

第一部分、JS监听支付按钮的点击事件,并发送到后台获取创建的订单信息,代码如下:

//点击支付的按钮
$("#pay").on("click", function(){
    //异步请求后台,创建订单
    $.ajax({
        type:'post',
        url:'confirm_order.php',
        async:true,
        dataType: 'json',
        data:{
            pay_fee: 100,  //元为单位,可以在前端用户自行填写,也可以直接写死
        },
        success: function(res){
            //判断订单创建是否成功res.state,1-成功,2-失败
            if (res.state == 1) {
                //以下这一部分不做修改
                jsApiParameters = {
                    "appId": res.result.jsApiParameters.appId,
                    "nonceStr": res.result.jsApiParameters.nonceStr,
                    "package": res.result.jsApiParameters.package,
                    "paySign": res.result.jsApiParameters.paySign,
                    "signType": res.result.jsApiParameters.signType,
                    "timeStamp": res.result.jsApiParameters.timeStamp,
                }
                out_trade_no = res.result.out_trade_no;
                mchid = res.result.mchid;
                //发起支付,后续文章说明
                callpay();
            }else if(res.state == 2){
                alert(data.msg);
            }else{
                alert("订单发起异常。");
            }
        },
        error: function(res){
            alert("订单发起失败。");
        }
    })
});

第二部分、confirm_order.php接收创建订单请求,向支付模块程序发送进一步的订单信息,并将创建返回的订单信息写入数据库,代码如下:

//接收整理数据
$openId='03434343434343434343434';
$pay_fee=$_POST['pay_fee'];
$data=[
	'pay_name'=>'订单名称',
	'pay_tel'=>'订单联系电话',
	'pay_remark'=>'订单备注',
	'pay_fee'=>$pay_fee,
	'openId'=>$openId,
];
//发送订单数据
$result=curl_post('http://域名/项目/支付模块/create_order.php', $data);
//获取订单数据
$resultArr=json_decode($result, true);
$out_trade_no=$resultArr['out_trade_no'];
$mchid=$resultArr['mchid'];
$appid=$resultArr['appid'];
//数据库记录订单信息
//$sql = "insert into pay (funds_name,funds_fee,funds_tel,funds_remark,funds_openid,funds_out_trade_no,funds_mchid,funds_appid,funds_start_time,funds_state,funds_result_desc) values('".$data['pay_name']."','".($data['pay_fee'] * 100)."','".$data['pay_tel']."','".$data['pay_remark']."','$openId','$out_trade_no','$mchid','$appid',NOW(),'2','支付未完成')";
//$res = $db->execute($sql);
if(!empty($resultArr)){
	$arr['state']=1;
	$arr['result']=$resultArr;
}else{
	$arr['state']=2;
	$arr['msg']='订单创建失败';
}
echo json_encode($arr);
exit;

function curl_post($url, $data = array())
{
	$ch = curl_init();
	curl_setopt($ch, CURLOPT_URL, $url);
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
	curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
	curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
	// POST数据
	curl_setopt($ch, CURLOPT_POST, 1);
	// 把post的变量加上
	curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
	$output = curl_exec($ch);
	curl_close($ch);
	return $output;
}

第三部分、域名/项目/支付模块/create_order.php,最终将订单信息发送给微信接口,并获取接口返回结果。首先需要先获取并部署微信支付类。

1.访问微信支付官网,在首页最下方点击“开发文档”商户或服务商。进入后,再次拉到最下方,点击“开发文档(V2版)”。进入后,选择任意一样产品点击。进入详细说明界面后,点击“SDK与DEMO下载”,在其中下载需要的程序包。

2.查看程序包php_sdk_v3.0.10,查看其中的说明文档,根据说明文档进行部署配置。

(1)修改lib/WxPay.Config.Interface.php为自己申请的商户号的信息,类似这样:

class WxPayConfigInterface
{
	//=======【基本信息设置】=====================================
	/**
	 * TODO: 修改这里配置为您自己申请的商户信息
	 * 微信公众号信息配置
	 * 
	 * APPID:绑定支付的APPID(必须配置,开户邮件中可查看)
	 * 
	 * MCHID:商户号(必须配置,开户邮件中可查看)
	 * 
	 */
	public function GetAppId(){
		return '12312312';
	}
	public function GetMerchantId()
	{
		return '12312313';
	}
	
	
	//=======【支付相关配置:支付成功回调地址/签名方式】===================================
	/**
	* TODO:支付回调url
	* 签名和验证签名方式, 支持md5和sha256方式
	**/
	public function GetNotifyUrl()
	{
		return '';
	}
	public function GetSignType()
	{
		return 'MD5'; 
	}

	//=======【curl代理设置】===================================
	/**
	 * TODO:这里设置代理机器,只有需要代理的时候才设置,不需要代理,请设置为0.0.0.0和0
	 * 本例程通过curl使用HTTP POST方法,此处可修改代理服务器,
	 * 默认CURL_PROXY_HOST=0.0.0.0和CURL_PROXY_PORT=0,此时不开启代理(如有需要才设置)
	 * @var unknown_type
	 */
	public function GetProxy(&$proxyHost, &$proxyPort)
	{
		$proxyHost='0.0.0.0';
		$proxyPort=0;
	}
	

	//=======【上报信息配置】===================================
	/**
	 * TODO:接口调用上报等级,默认紧错误上报(注意:上报超时间为【1s】,上报无论成败【永不抛出异常】,
	 * 不会影响接口调用流程),开启上报之后,方便微信监控请求调用的质量,建议至少
	 * 开启错误上报。
	 * 上报等级,0.关闭上报; 1.仅错误出错上报; 2.全量上报
	 * @var int
	 */
	public function GetReportLevenl()
	{
		return '1';
	}


	//=======【商户密钥信息-需要业务方继承】===================================
	/*
	 * KEY:商户支付密钥,参考开户邮件设置(必须配置,登录商户平台自行设置), 请妥善保管, 避免密钥泄露
	 * 设置地址:https://pay.weixin.qq.com/index.php/account/api_cert
	 * 
	 * APPSECRET:公众帐号secert(仅JSAPI支付的时候需要配置, 登录公众平台,进入开发者中心可设置), 请妥善保管, 避免密钥泄露
	 * 获取地址:https://mp.weixin.qq.com/advanced/advanced?action=dev&t=advanced/dev&token=2005451881&lang=zh_CN
	 * @var string
	 */
	public function GetKey()
	{
		return 'sfdfsfddf213212312323';
	}
	public function GetAppSecret()
	{
		return 'sfdfsfddf213212312323';
	}


	//=======【证书路径设置-需要业务方继承】=====================================
	/**
	 * TODO:设置商户证书路径
	 * 证书路径,注意应该填写绝对路径(仅退款、撤销订单时需要,可登录商户平台下载,
	 * API证书下载地址:https://pay.weixin.qq.com/index.php/account/api_cert,下载之前需要安装商户操作证书)
	 * 注意:
	 * 1.证书文件不能放在web服务器虚拟目录,应放在有访问权限控制的目录中,防止被他人下载;
	 * 2.建议将证书文件名改为复杂且不容易猜测的文件名;
	 * 3.商户服务器要做好病毒和木马防护工作,不被非法侵入者窃取证书文件。
	 * @var path
	 */
	public function GetSSLCertPath(&$sslCertPath, &$sslKeyPath)
	{
		$sslCertPath='cert.pem';
		$sslKeyPath='key.pem';
	}
}

注意:demo中的配置文件是接口类,这里就用抽象类就可以了;“MD5”必须要大写,这个很坑。

(2)下载证书放于对应位置

3.部署完成后,添加主流程create_order.php,然后需要一步一步访问调试,原有demo程序包中问题比较多,主要来自于修改了配置文件(WxPay.Config.Interface.php)的名称与类名,以及APPID等的调用方法,没改统一就发布了.......主控程序代码如下:

require_once "lib/WxPay.Api.php";
$config = new WxPayConfigInterface();
require_once "example/WxPay.JsApiPay.php";
$tools = new JsApiPay();

//获取订单数据
$body=$_POST['pay_name'];
$pay_fee=$_POST['pay_fee'];
$pay_tel=$_POST['pay_tel'];
$pay_remark=$_POST['pay_remark'];
$openId=$_POST['openId'];
//补充订单数据
$out_trade_no=$config -> GetMerchantId().date("YmdHis");	  
$time_start=date("YmdHis");
// $time_expire=date("YmdHis", time() + 600);
//组织发送接口的数据   
$input = new WxPayUnifiedOrder();
$input->SetBody($body);  //商品描述
$input->SetAttach($pay_remark);  //附加数据
$input->SetOut_trade_no($out_trade_no);  //商户订单号
$input->SetTotal_fee($pay_fee*100);  //订单总金额,单位为分
$input->SetTime_start($time_start);  //交易起始时间
//$input->SetTime_expire($time_expire);  //交易结束时间
$input->SetGoods_tag("test");  //订单优惠标记
$input->SetNotify_url("http://域名/redcross/项目/notify.php");  //通知地址
$input->SetTrade_type("JSAPI");  //交易类型
$input->SetOpenid($openId);  //用户标识
//var_dump($input);
//发送创建订单请求
$order = WxPayApi::unifiedOrder($config, $input);
$jsApiParameters = $tools->GetJsApiParameters($order);
//获取创建订单数据
$arr['jsApiParameters']=json_decode($jsApiParameters,true);
$arr['out_trade_no']=$out_trade_no;
$arr['mchid']=$config -> GetMerchantId();
$arr['appid']=$config -> GetAppId();
echo json_encode($arr);
exit;

这里记录一个其他类型的bug,来自于对https的检验,报错为curl,错误码60。网上找到的解决办法如下:

//找到
curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,TRUE);
curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,2);//严格校验
//替换为		
if(stripos($url,"https://")!==FALSE){
	curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);
	curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
	curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
}else{
	curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,TRUE);
	curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,2);//严格校验
} 

这里第一次做的时候需要多留一点时间,一步一步调试才可。

4.订单创建成功后,发起支付

在页面增加如下代码,可以点击发起支付。代码如下:

//发起支付		
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();
	}
}
//调用微信JS api 支付
function jsApiCall() {
	WeixinJSBridge.invoke(
		'getBrandWCPayRequest',
		jsApiParameters,
		function(res) {
			if(res.err_msg=='get_brand_wcpay_request:cancel'){
				alert('您已取消支付。');
			//支付完成,跳转入支付结果php
			}else if(res.err_msg=='get_brand_wcpay_request:ok'){
				location.href = 'pay_result.php?out_trade_no=' + out_trade_no + '&mchid=' + mchid;
			}else{
				alert('支付出现异常。');
			}
		}
	);
}		

这样就可以在页面点击支付,测试啦。

5.支付完成后,在配置的返回地址验证订单一致,并更新数据库记录

支付完成后,微信进入配置的返回地址nodify.php,代码如下:

//获取接口数据,如果$_REQUEST拿不到数据,则使用file_get_contents函数获取
$post = $_REQUEST;
if ($post == null) {
	$post = file_get_contents("php://input");
}
if ($post == null) {
	$post = isset($GLOBALS['HTTP_RAW_POST_DATA']) ? $GLOBALS['HTTP_RAW_POST_DATA'] : '';
}
if (empty($post) || $post == null || $post == '') {
	//阻止微信接口反复回调接口  文档地址 https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=9_7&index=7,下面这句非常重要!!!
	$str='<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';  
	echo $str;
	exit('Notify 非法回调');
}
/*****************微信回调返回数据样例*******************
 $post = '<xml>
	<return_code><![CDATA[SUCCESS]]></return_code>
	<return_msg><![CDATA[OK]]></return_msg>
	<appid><![CDATA[wx2421b1c4370ec43b]]></appid>
	<mch_id><![CDATA[10000100]]></mch_id>
	<nonce_str><![CDATA[IITRi8Iabbblz1Jc]]></nonce_str>
	<sign><![CDATA[7921E432F65EB8ED0CE9755F0E86D72F]]></sign>
	<result_code><![CDATA[SUCCESS]]></result_code>
	<prepay_id><![CDATA[wx201411101639507cbf6ffd8b0779950874]]></prepay_id>
	<trade_type><![CDATA[APP]]></trade_type>
	</xml>';
	*************************微信回调返回*****************/

libxml_disable_entity_loader(true); //禁止引用外部xml实体
$xml = simplexml_load_string($post, 'SimpleXMLElement', LIBXML_NOCDATA);//XML转数组
$post_data = (array)$xml;
/** 解析出来的数组
	*Array
	* (
	* [appid] => 45465465456454
	* [bank_type] => CFT
	* [cash_fee] => 100
	* [fee_type] => CNY
	* [is_subscribe] => N
	* [mch_id] => 1297210301
	* [nonce_str] => 151351513
	* [openid] => 1215315351
	* [out_trade_no] => 12132131
	* [result_code] => SUCCESS
	* [return_code] => SUCCESS
	* [sign] => F6890323B0A6A3765510D152D9420EAC
	* [time_end] => 20200626170839
	* [total_fee] => 100
	* [trade_type] => JSAPI
	* [transaction_id] => 1541515315151513
	* )
**/
//订单号
$out_trade_no = isset($post_data['out_trade_no']) && !empty($post_data['out_trade_no']) ? $post_data['out_trade_no'] : 0;
$mch_id=$post_data['mch_id'];
if($post_data['return_code']=="SUCCESS" && $post_data['result_code']=="SUCCESS"){
	$err_code_des='支付成功';
	$result_code=$post_data['result_code'];
}else{
	$err_code_des='支付结果异常';
	$result_code=$post_data['result_code'];
}
//查询订单信息
//$sql = "select * from pay where funds_out_trade_no='$out_trade_no'  ";
//$order_info = $db->getAll($sql);
$order_info=[1,2,3]; //测试使用,上线前删掉
if(count($order_info) > 0){
	//平台支付key
	$wxpay_key = '1231231231231231231231'; //这里需要人工配置
	//接收到的签名
	$post_sign = $post_data['sign'];
	unset($post_data['sign']);
	//重新生成签名
	$newSign = MakeSign($post_data,$wxpay_key);
	//签名统一,则更新数据库
	if($post_sign == $newSign){
		if ($result_code == 'SUCCESS') {
			$funds_state = 1;
		} else if ($result_code == 'REFUND') {
			$funds_state = 3;
		} else {
			$funds_state = 2;
		}
		//调试代码,上线后删除
		$myfile = fopen("newfile.txt", "w");
		$txt = $funds_state."\n";
		fwrite($myfile, $txt);
		$txt = $out_trade_no."\n";
		fwrite($myfile, $txt);
		fclose($myfile);
		//数据库状态更新
		//$sql = "update pay set funds_state='".$funds_state."',funds_result_desc='".$err_code_des."',funds_complete_time=NOW() where funds_out_trade_no='".$out_trade_no."' and funds_mchid='".$mch_id."' ";
		//$res = $db[0]->execute($sql);
	}
}
//阻止微信接口反复回调接口  文档地址 https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=9_7&index=7,下面这句非常重要!!!
$str='<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';  
echo $str;
exit;
		
function MakeSign($params,$key){
	//签名步骤一:按字典序排序数组参数
	ksort($params);
	$string = ToUrlParams($params);  //参数进行拼接key=value&k=v
	//签名步骤二:在string后加入KEY
	$string = $string . "&key=".$key;
	//签名步骤三:MD5加密
	$string = md5($string);
	//签名步骤四:所有字符转为大写
	$result = strtoupper($string);
	return $result;
}
				
function ToUrlParams( $params ){
	$string = '';
	if( !empty($params) ){
		$array = array();
		foreach( $params as $key => $value ){
			$array[] = $key.'='.$value;
		}
		$string = implode("&",$array);
	}
	return $string;
}

6.前端操作完成后,因为微信异步返回支付结果,所以主动查询,给用户进行提示

第一部分、pay_result.php获取商户与订单信息,向支付模块发送请求。代码如下:

//获取订单与商户信息
$out_trade_no=$_GET['out_trade_no'];
$mchid=$_GET['mchid'];
//发送至支付模块查询,并返回
$data=[
	'out_trade_no'=>$out_trade_no,
];
$result=curl_post('http://域名/支付模块/check_order.php', $data);
$result=json_decode($result,true);
if($result['result_code'] == 'SUCCESS')
{
	echo("您已缴费成功");
}else{
	echo("已操作缴费,等待缴费确认。");
}
exit;
function curl_post($url, $data = array())
{
	$ch = curl_init();
	curl_setopt($ch, CURLOPT_URL, $url);
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
	curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
	curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
	// POST数据
	curl_setopt($ch, CURLOPT_POST, 1);
	// 把post的变量加上
	curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
	$output = curl_exec($ch);
	curl_close($ch);
	return $output;
}

第二部分、支付模块中增加check_order.php,查询支付结果并返回。

require_once "lib/WxPay.Api.php";
$config = new WxPayConfigInterface();

$out_trade_no=$_POST['out_trade_no'];
$input = new WxPayUnifiedOrder();
$input->SetOut_trade_no($out_trade_no);  //商户订单号
$order = WxPayApi::orderQuery($config, $input);
echo json_encode($order);
exit;

至此,实现微信网页版支付功能。已根据以上代码形成测试demo,确认运行无误(2021-06-17)。

猜你喜欢

转载自blog.csdn.net/HoD_DoH/article/details/118081237