欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Laravel 集成微信H5支付

程序员文章站 2022-07-13 17:14:45
...

前期准备:

1.下载SDK。

因为目前H5支付没有DEMO,所以这篇文章时作者自己参考微信扫码支付的DEMO摸索着写的,如果有不对的地方,大家可以告诉作者来改正,下载扫码支付的SDK压缩文件,因为有些代码是公用的,用扫码支付的就行了。

微信扫码支付SDK与DEMO下载

2.把下载的zip文件解压,放到项目目录里,这里作者放在app文件夹里,方便查看

然后在根目录的composer.json文件的autoload数组中的classmap中加入该文件夹的路径,代码如下:

"autoload" : {
	"classmap" : [
		"database/seeds",
		"database/factories",
		"vendor/yansongda/pay/src",
		"vendor/yansongda/supports/src",
		"app/WeChatPay",
		"app/AliPay"
	],
	"psr-4" : {
		"App\\" : "app/"
	}
},

这里特别要注意,在引入命名空间后,要执行一下composer的自动加载类命令(应该是这个意思)

首先,进入项目的根目录,然后执行下面的命令

composer dump-autoload

这个命令很重要,不然上面加入的微信支付的类文件无法被识别

作者就在这里吃了大亏,说到底是对这个框架不熟悉

3.因为Laravel框架的原因,文件的入口在/public/index.php这个文件这里,所以所有需要require的文件的路径都要在相应的文件里修改一下,这里作者的路劲修改后举例为:

require_once '../app/WeChatPay/config.php';

为了方便使用,对部分类文件使用命名空间,这样使用起来目标明确,虽然会额外费点功夫,但作者觉得思路最重要。

因为是支付这块的内容,然后为了区别微信支付还有网银支付,所以命名空间定为:

namespace APP\Pay\WxPay;

4.为了方便日后管理,把一些相关的类文件集中起来放置在一个文件夹里,文件夹名为 qy ,意为为企业支付建立的这个文件夹

qy文件夹内有2个目录:

base: 存放一些基础的类文件,如配置等

notify : 存放相关的接口实现类

plugins: 存放一些需要的插件类,如二维码生成(phpqrcode.php)

Tool.php:工具类,存放如获取客户端IP等方法

这里对部分需要引入命名空间的文件列举一下(目前只做了支付,退款还没做,所以退款涉及的类文件不做举例)

//接口访问类
/app/WeChatPay/lib/WxPay.Api.php
//回调基础类
/app/WeChatPay/lib/WxPay.Notify.php
//配置类
/app/WeChatPay/qy/base/WxPay.Config.php
//H5支付实现类,封装了一些辅助方法
/app/WeChatPay/qy/base/WxPay.H5Pay.php
//真正的微信支付回调类
/app/WeChatPay/notify/WxPay.NotifyQY.php
//工具类
/app/WeChatPay/Tool.php

这里特别要注意,在引入命名空间后,要执行一下composer的自动加载类命令(应该是这个意思)

首先,进入项目的根目录,然后执行下面的命令

composer dump-autoload

这个命令很重要,不然上面引入命名空间的类文件无法被识别

作者就在这里吃了大亏,说到底是对这个框架不熟悉

这里只列举了部分需要引入命名空间的类文件,有些没列举到的,大家把微信支付功能跑起来后,再根据错误提示加上去就行了

还有,部分文件因为在第2步的composer.json里已经自动引入了,所以只需要在类名前加上 \ (反斜杠符号),即可使用

5.配置支付宝接口需要的基础信息,在/app/WeChatPay/qy/base/WxPay.Config.php这个文件里设置,这步按照里面的说明去修改就行了,没什么好说的

开始调用接口

6.前端 - 传递下单的金额,调用后端预下单接口(即第7步的接口),然后显示生成的支付二维码,轮询订单状态

Html部分

<div class="mui-content">
	<div class="mui-row" style="background:#FFFFFF;">
		<div class="mui-col-sm-6 label-div" style="margin-left:4px;"><label class="col-lg-2">充值金额:</label></div>
		<div class="mui-col-sm-6" style="height:40px;"><input id="price" name="price" type="text" value="100"></div>
		<div class="mui-col-sm-6 label-div" style="margin-left:14px;">元</div>
	</div>
</div>
<div class="form-group clearfix" style="padding-bottom: 20px">
	<p class="text-center hide"><span class="anchor-WxPayPopover">&nbsp;</span></p>
	<label style="font-size:14px;margin-left:14px;" class="pay-way">支付方式:</label>
	<div class="col-lg-10">
		<div class="bank-div" style="float: right;padding-left: 5px;">
			<input name="bank" style="width: 100%;" class="bank ltts_pay_wx" value="Wxpay" autocomplete="off"type="radio">
			<div class="bank-wrap " style="width: 100%;"><img style="width: 100%;" src="basic/img/common/weixinpay.jpg" alt="微信支付"><i></i></div>
		</div>
	</div>
</div>
<div class="form-group clearfix" style="text-align: center;padding-bottom:30px; " >
	<button id="btnSubmit" type="submit" class="btn btn-danger btn-primary" style="width: 50%;" >开始充值</button>
</div>

<!-- 微信确认支付是否完成提示框 -->
<div id="WxPayPopover" class="mui-popover">
	<div class="mui popover-arrow hide"></div>
	<div class="mui-scroll-wrapper">
		<div class="mui-scroll">
			<ul class="mui-table-view text-center">
				<li class="mui-table-view-cell"><p class="title font-size-20 font-weight-500" style="color:#333;">请确认微信支付是否已完成</p></li>
				<li class="mui-table-view-cell text-success"><p class="text-success font-size-16 font-weight-500" style="color:#46be8a;" onclick="queryOrderWxPay()">已完成支付</p></li>
				<li class="mui-table-view-cell text-danger"><p class="text-danger font-size-16 font-weight-300" style="color:#f96868;" onclick="WxPaycancel()">支付遇到问题,重新支付</p></li>
			</ul>
		</div>
	</div>
</div>
<!-- 微信确认支付是否完成提示框 -->

JavaScript部分

function WxPaycancel(){
	mui("#WxPayPopover").popover('hide');
}
var trade_no = "";
function queryOrderWxPay() {
	WxPaycancel();
	//设置每隔1000毫秒执行一次load() 方法  轮询订单
	var myIntval = setInterval(function () {
		load()
	}, 1000);
	
	function load() {
		if(trade_no=="") return false;
		$.ajax({
			type: "POST",
			url: "{{ route( 'filling.queryOrderWxPay')}}",
			dataType: "json",
			data: {'_token': '{{csrf_token()}}','trade_no':trade_no},
			success: function (json) {
				if(json.trade_state  == 'SUCCESS'){
					$('.pay_status').val("支付成功");
					setTimeout(function(){
						$('#payWarpModel').modal('hide');
						clearInterval(myIntval);
					},1000);
				}else{
					$('.pay_status').html("支付正在校验,请不要关闭页面,稍后...");
				}
			}
		});
	}
}

//充值按钮绑定事件
$("#btnRecharge").click(function(){
	$_money = $("input#money");
	var money = $_money.val();
	var min = "1";
	var msg = "";
	if(money==""){
		msg = "请填写充值金额";
	}
	if(parseFloat(money)<parseFloat(min)){
		msg = "充值金额不能小于充值最小金额,请重新填写";
	}
	if(msg!=""){
		$_money.val(money).focus();
		alert(msg);
		return false;
	}
	$.ajax({
		type: "POST",
		url: "{{ route( 'filling.getMobilePayOrder')}}",
		dataType: "json",
		data: {'_token': '{{csrf_token()}}','money':money},
		success: function (json) {
			if(json.code==1){
				queryMoney();
				setTimeout(function(){
					trade_no = json.data.trade_no;
					mui("#WxPayPopover").popover('toggle', document.getElementById('btnSubmit'));
				}, 500);
				window.location.href = json.data.url;
				//queryOrder(json.data.trade_no);
			} else{
				mui.toast("<p class='toast-error'>"+json.msg+"</p>");
			}
		},
		error: function (error) {
			console.log(error);
		}
	});

});

7.后端 - 生成支付订单,打印(返回)下单结果(不知道这样子描述是否有误。。)

use App\Pay\WxPay\H5Pay;
use APP\Pay\WxPay\Tool as WxTool;
use App\Pay\WxPay\WxPayApi;
use App\Pay\WxPay\WxPayConfig;

//生成微信H5预支付订单
public function getMobilePayOrder(Request $request){
	if(!$request->has('money') || floatval($request->money)<floatval(config('recharge.minRecharge'))){
		$this->jsonError('参数错误');
		return false;
	}
	//付款金额,必填
	$money = $request->money;
	$totalFee = floatval($money)*100;
	$totalFee = 1;
	$body = $this->getBody();
	$outTradeNo = $this->getOutTradeNo(5);
	//$spbillCreateIp = WxTool::GetClientIp();
	$input = new \WxPayUnifiedOrder();
	$input->SetBody($body);
	//$input->SetAttach("{}");
	$input->SetOut_trade_no($outTradeNo);
	$input->SetTotal_fee($totalFee);
	$input->SetTime_start(date("YmdHis"));
	$input->SetTime_expire(date("YmdHis", time() + 600));
	$input->SetGoods_tag("test");
	$input->SetNotify_url('http://'.$_SERVER['HTTP_HOST']."/ApiNotify/Notify/WxPayNativeCallBack");
	$input->SetTrade_type("MWEB");
	$input->SetScene_info('{"h5_info":{"type":"Wap","wap_url":"http://116.26.94.166:9001","wap_name":"群英网络"}}');
	
	$H5Pay = new H5Pay();
	$res = $H5Pay->GetPayResult($input);

	
	//调用统一下单接口成功
	if($res['return_code']=="SUCCESS" && $res['result_code']=="SUCCESS"){
		//生成预充值订单 - 微信H5支付
		$remarks = $this->_toJsonString($res, ['appid','mch_id','trade_type','prepay_id','result_code','return_code']);
		$this->addChargeRecord($money, "weixin", $outTradeNo, 1, $remarks);
		$this->ajaxData(Array(
			"url" => $res['mweb_url'],
			"trade_no" => $outTradeNo,
			"time" => date("Y-m-d H:i:s")
		));
	} else{
		//生成失败预充值订单 - 微信H5支付
		if($res['return_code']=="SUCCESS"){
			//通信成功
			$remarks = $this->_toJsonString($res, ['appid','mch_id','trade_type','prepay_id','result_code','return_code']);
			$this->addFailedChargeRecord($money, "weixin", $outTradeNo, 1, $res['err_code_des'], $remarks);
		} else{
			//通信失败
			$remarks = $this->_toJsonString($res);
			$this->addFailedChargeRecord($money, "weixin", $outTradeNo, 1, $res['return_msg'], $remarks);
		}
		$this->ajaxError("生成微信充值订单失败");
	}
	
}


//按传入的键值获取数组对应的值,转为字符串
function _toJsonString($data, $keys=null){
	$str = "";
	$type = gettype($data);
	switch($type){
		case "string":
			$str = "{\"data\":\"{$data}\"}";
			break;
		case "object":
		case "array":
			$str .= "{";
			$data = json_decode(json_encode($data), true);
			foreach($data as $k => $v){
				if($keys === null){
					$str .= "\"{$k}\":\"{$v}\", ";
				} else{
					if(in_array($k, $keys))
						$str .= "\"{$k}\":\"{$v}\", ";
				}
			}
			$str = substr($str, 0, -2)."}";
			break;
	}
	return $str;
}

8.后端 - 支付结果异步通知接口 ,这里只能在异步通知接口里调用第9步我们自己写的微信回调处理类

use App\Pay\WxPay\WxPayConfig;
use App\Pay\WxPay\WxPayNotifyQY;

public function WxPayNativeCallBack(Request $request){
	$config = new WxPayConfig();
	$notify = new WxPayNotifyQY();
	$notify->Handle($config, false);
}

9.真正的回调处理类,这里需要我们处理的只有 NotifyProcess 这个函数

<?php
namespace App\Pay\WxPay;

use App\Http\Controllers\PayController as UserPayController;

use App\Pay\WxPay\WxPayApi;
use App\Pay\WxPay\WxPayConfig;
use App\Pay\WxPay\WxPayNotify;

use Illuminate\Support\Facades\Log;

class WxPayNotifyQY extends WxPayNotify
{
	//查询订单
	public function Queryorder($transaction_id)
	{
		$input = new \WxPayOrderQuery();
		$input->SetTransaction_id($transaction_id);

		$config = new WxPayConfig();
		$result = WxPayApi::orderQuery($config, $input);
		Log::DEBUG("query:" . json_encode($result));
		if(
			array_key_exists("return_code", $result)
			&& array_key_exists("result_code", $result)
			&& $result["return_code"] == "SUCCESS"
			&& $result["result_code"] == "SUCCESS"
		){
			return true;
		}
		return false;
	}

	/**
	*
	* 回包前的回调方法
	* 业务可以继承该方法,打印日志方便定位
	* @param string $xmlData 返回的xml参数
	*
	**/
	public function LogAfterProcess($xmlData)
	{
		Log::DEBUG("call back, return xml:" . $xmlData);
		return;
	}
	
	//重写回调处理函数
	/**
	 * @param WxPayNotifyResults $data 回调解释出的参数
	 * @param WxPayConfigInterface $config
	 * @param string $msg 如果回调处理失败,可以将错误信息输出到该方法
	 * @return true回调出来完成不需要继续回调,false回调处理未完成需要继续回调
	 */
	public function NotifyProcess($objData, $config, &$msg)
	{
		$data = $objData->GetValues();
		//TODO 1、进行参数校验
		if(!array_key_exists("return_code", $data) 
			||(array_key_exists("return_code", $data) && $data['return_code'] != "SUCCESS")) {
			//TODO失败,不是支付成功的通知
			//如果有需要可以做失败时候的一些清理处理,并且做一些监控
			$msg = "异常异常";
			Log::error("微信支付回调 WxPayNotify NotifyProcess 进行参数校验 ".$msg);
			return false;
		}
		if(!array_key_exists("transaction_id", $data)){
			$msg = "输入参数不正确";
			Log::error("微信支付回调 WxPayNotify NotifyProcess 进行参数校验 ".$msg);
			return false;
		}
		//TODO 2、进行签名验证
		try {
			$checkResult = $objData->CheckSign($config);
			if($checkResult == false){
				//签名错误
				Log::error("微信支付回调 WxPayNotify NotifyProcess 签名错误... ");
				return false;
			}
		} catch(Exception $e) {
			Log::ERROR("微信支付回调 WxPayNotify NotifyProcess 签名失败... ".json_encode($e));
		}
		//TODO 3、处理业务逻辑
		$notfiyOutput = array();
		
		//查询订单,判断订单真实性
		if(!$this->Queryorder($data["transaction_id"])){
			$msg = "订单查询失败";
			Log::error("微信支付回调 WxPayNotify NotifyProcess 3、处理业务逻辑 ".$msg );
			return false;
		}
		
		$PayController = new UserPayController();
		$PayController->UserCharge($data['total_fee']*100, "weixin", $data['out_trade_no'], $data['openid'], 1, $data['mch_id'], $data['appid']);
		
		return true;
	}
}

10.支付接口、查询接口对应的回调处理

/**
 * [会员充值回调处理]
 * @param  int $money    [订单金额]
 * @param  int $payType [订单类型 weixin|alipay]
 * @param  int $outTradeNo [商户订单号]
 * @param  int $payAccount [支付账号]
 * @param  int $status [支付状态]
 * @param  int $appID [商务ID]
 * @param  int $smId [收款支付宝账号对应的支付宝唯一用户号/微信支付分配的商户号]
 */
function UserCharge($money, $payType, $outTradeNo, $payAccount, $status, $smId, $appID=null){
	//支付类型 判断设置 日志类型
	$payLogType = "";
	if($payType=="alipay") $payLogType='Alipay';
	else if($payType=="weixin") $payLogType='WechatPay';
        //防止异步支付通知接口与查询接口同时调用回调处理,使用事务,通知在事务内使用lockForUpdate阻止同时读取修改用户的信息
	DB::beginTransaction();
	$Function= new Function();
        //按照商务订单号获取预充值订单
	$record = $Function->getUserChargeRecordByOutTradeNo($outTradeNo);
	$record = json_decode(json_encode($record),true);
	$remarks = json_decode($record['remarks'],true);
	if($record['status']!="0"){
		//已经处理过充值回调,直接返回true
		//防止多次处理
		return true;
	}
	
	/* -------------------- 检验 开始 -------------------- */
	$msg = "";
	$checkResult=1;
	//检验1:out_trade_no 商务订单号
	if($outTradeNo != $record['trade_no']){
		$msg = "商务订单号参数错误";
		$checkResult = 0;
	}
	//检验2:total_amount/total_fee 实际金额
	if($checkResult==1 && floatval($money) != floatval($record['money'])){
		$msg = "订单金额参数错误";
		$checkResult = 0;
	}
	//检验3:app_id/appid 商务号ID
	//支付宝查询订单时未返回这个参数,跳过这个检查
	if($checkResult==1 && $appID != null){
		if($payType == "alipay" && $appID != $remarks['app_id']){
			$msg = "支付宝分配给开发者的应用ID参数错误";
			$checkResult = 0;
		} else if($payType == "weixin" && $appID != $remarks['appid']){
			$msg = "微信支付分配的公众账号ID参数错误";
			$checkResult = 0;
		}
	}
	//检验4:seller_id/mch_id
	if($checkResult==1 && $payType == "alipay" && $smId != $remarks['seller_id']){
		$msg = "收款支付宝账号参数错误";
		$checkResult = 0;
	} else if($checkResult==1 && $payType == "weixin" && $smId != $remarks['mch_id']){
		$msg = "商户号参数错误";
		$checkResult = 0;
	}
	if($checkResult == 0){
		Log::error("用户充值失败,out_trade_no为{$outTradeNo},原因为{$msg}");
		$this->writePayLog("用户充值失败,out_trade_no为{$outTradeNo},原因为{$msg}", $payLogType);
        //更新用户预充值记录信息
		$res = ...;
		if(!$res){
			DB::rollback();
			Log::error("用户充值失败,更新充值表失,out_trade_no为{$outTradeNo},原因为{$msg}");
			$this->writePayLog("用户充值失败,更新充值表失败,out_trade_no为{$outTradeNo},原因为{$msg}", $payLogType);
		} else{
			DB::commit();
			Log::info("用户充值失败,更新充值表成功,out_trade_no为{$outTradeNo},原因为{$msg}");
			$this->writePayLog("用户充值失败,更新充值表成功,out_trade_no为{$outTradeNo},原因为{$msg}", $payLogType);
		}
		return Array('result'=>0,'msg'=>$msg);
	}
	/* -------------------- 检验 结束 -------------------- */
	
	//检验通过,开始处理业务流程
	//更新充值表
        ...
	//更新用户余额
	...
	DB::commit();
}

11.实现查询接口。

在第6步的JavaScript部分的调用后端预下单接口时,会回传商务订单号(out_trade_no)。

然后调用JavaScript部分的queryWxPayOrder方法,调用后端的查询订单接口。

接口代码如下:

//查询微信支付订单
function queryOrderWxPay(Request $request){
	if(!$request->has('trade_no') || empty($request->trade_no)){
		$this->ajaxError("参数错误");
		return false;
	}
	$tradeNo = $request->trade_no;
	$input = new \WxPayOrderQuery();
	$input->SetOut_trade_no($tradeNo);
	$config = new WxPayConfig();
	$response = WxPayApi::orderQuery($config, $input);
	if(
		array_key_exists("return_code", $response) && 
		array_key_exists("result_code", $response) && 
		$response['return_code'] == "SUCCESS" &&
		$response['result_code'] == "SUCCESS" &&
		$response['trade_state'] == "SUCCESS"
		
	){
		DB::beginTransaction();
		$outTradeNo = $response['out_trade_no'];
		$UserFunction = new UserFunction();
		$record = $UserFunction->getUserChargeRecordByOutTradeNo($outTradeNo);
		$record = json_decode(json_encode($record), true);
		if($record['status']==0){
			//未处理过充值回调,防止多次处理
			Log::info(" 充值后轮询更新用户信息 ");
			$this->UserCharge(floatval($response['total_fee'])*100,'weixin',$outTradeNo,$response['openid'],1,$response['mch_id'],$response['appid']);
		}
		DB::commit();
	}
	return json_encode(
		Array(
			"return_code" => $response['return_code'],
			"result_code" => $response['result_code'],
			"trade_state" => $response['trade_state'],
		),true
	);
}

 

后续有时间再做完善这篇文章