微信支付实现

1.二维码

1.1什么是二维码

二维码又称QR Code,QR全称Quick Response,是一个近几年来移动设备上超流行的一种编码方式,它比传统的Bar Code条形码能存更多的信息,也能表示更多的数据类型。

二维条码/二维码(2-dimensional bar code)是用某种特定的几何图形按一定规律在平面(二维方向上)分布的黑白相间的图形记录数据符号信息的;在代码编制上巧妙地利用构成计算机内部逻辑基础的“0”、“1”比特流的概念,使用若干个与二进制相对应的几何形体来表示文字数值信息,通过图象输入设备或光电扫描设备自动识读以实现信息自动处理:它具有条码技术的一些共性:每种码制有其特定的字符集;每个字符占有一定的宽度;具有一定的校验功能等。同时还具有对不同行的信息自动识别功能、及处理图形旋转变化点。

1.2二维码优势

l 信息容量大, 可以容纳多达1850个大写字母或2710个数字或500多个汉字

l 应用范围广, 支持文字,声音,图片,指纹等等…

l 容错能力强, 即使图片出现部分破损也能使用

l 成本低, 容易制作

1.3二维码容错级别

L级(低) 7%的码字可以被恢复。

M级(中) 的码字的15%可以被恢复。

Q级(四分)的码字的25%可以被恢复。

H级(高) 的码字的30%可以被恢复。

1.4二维码生成插件qrious

qrious是一款基于HTML5 Canvas的纯JS二维码生成插件。通过qrious.js可以快速生成各种二维码,你可以控制二维码的尺寸颜色,还可以将生成的二维码进行Base64编码。

qrious.js二维码插件的可用配置参数如下:

参数类型默认值描述
backgroundString“white”二维码的背景颜色。
foregroundString“black”二维码的前景颜色。
levelString“L”二维码的误差校正级别(L, M, Q, H)。
mimeString“image/png”二维码输出为图片时的MIME类型。
sizeNumber100二维码的尺寸,单位像素。
valueString“”需要编码为二维码的值

下面的代码即可生成一张二维码

<html>
<head>
<title>二维码入门小demo</title>
</head>
<body>
<img id="qrious">
<script src="qrious.min.js"></script>
<script>
 var qr = new QRious({
	   element:document.getElementById('qrious'),
	   size:250, 	   
	   level:'H',	   
	   value:'https://www.csdn.net/'
	});
</script>
</body>
</html>

运行效果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dqD7yCcj-1675664444079)(file:///C:\Users\lhp\AppData\Local\Temp\ksohtml19224\wps19.jpg)]

2.微信扫码支付简介

2.1微信扫码支付申请

微信扫码支付是商户系统按微信支付协议生成支付二维码,用户再用微信“扫一扫”完成支付的模式。该模式适用于PC网站支付、实体店单品或订单支付、媒体广告支付等场景。

申请步骤:(了解)

第一步:注册公众号(类型须为:服务号)

请根据营业执照类型选择以下主体注册:个体工商户| 企业/公司| 政府| 媒体| 其他类型

第二步:认证公众号

公众号认证后才可申请微信支付,认证费:300元/次。

第三步:提交资料申请微信支付

登录公众平台,点击左侧菜单【微信支付】,开始填写资料等待审核,审核时间为1-5个工作日内。

第四步:开户成功,登录商户平台进行验证

资料审核通过后,请登录联系人邮箱查收商户号和密码,并登录商户平台填写财付通备付金打的小额资金数额,完成账户验证。

第五步:在线签署协议

本协议为线上电子协议,签署后方可进行交易及资金结算,签署完立即生效。

本课程已经提供好微信支付账号,无需申请。

2.2开发文档

微信支付接口调用的整体思路:

按API要求组装参数,以****XML方式****发送(POST)给微信支付接口(URL),微信支付接口也是以XML方式给予响应。程序根据返回的结果(其中包括支付URL)生成二维码或判断订单状态。

在线微信支付开发文档:

https://pay.weixin.qq.com/wiki/doc/api/index.html

我们在本章课程中会用到”统一下单”和”查询订单”两组API

1. appid:微信公众账号或开放平台APP的唯一标识

2. mch_id:商户号 (配置文件中的partner)

3. partnerkey:商户密钥

4. sign:数字签名, 根据微信官方提供的密钥和一套算法生成的一个加密信息, 就是为了保证交易的安全性

2.3微信支付SDK

微信支付提供了SDK, 大家下载后打开源码,install到本地仓库。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BlSXVHXM-1675664444081)(file:///C:\Users\lhp\AppData\Local\Temp\ksohtml19224\wps20.jpg)]

课程配套的本地仓库已经提供jar包,所以安装SDK步骤省略。

使用微信支付SDK,在maven工程中引入依赖

	<dependency>
		<groupId>com.github.wxpay</groupId>
		<artifactId>wxpay-sdk</artifactId>
		<version>0.0.3</version>
	</dependency>

我们主要会用到微信支付SDK的以下功能:

(1)获取随机字符串

WXPayUtil.generateNonceStr()

(2)MAP转换为XML字符串(自动添加签名)

WXPayUtil.generateSignedXml(param, partnerkey)

(3)XML字符串转换为MAP

WXPayUtil.xmlToMap(result)

2.4 HttpClient工具类

HttpClient是Apache Jakarta Common下的子项目,用来提供高效的、最新的、功能丰富的支持HTTP协议的客户端编程工具包,并且它支持HTTP协议最新的版本和建议。HttpClient已经应用在很多的项目中,比如Apache Jakarta上很著名的另外两个开源项目Cactus和HTMLUnit都使用了HttpClient。

HttpClient通俗的讲就是模拟了浏览器的行为,如果我们需要在后端向某一地址提交数据获取结果,就可以使用HttpClient.

关于HttpClient(原生)具体的使用不属于我们本章的学习内容,我们这里这里为了简化 HttpClient的使用,提供了工具类HttpClient(对原生HttpClient进行了封装)

HttpClient工具类使用的步骤:

HttpClient client=new HttpClient(请求的url地址);
client.setHttps(true);//是否是https协议
client.setXmlParam(xmlParam);//发送的xml数据
client.post();//执行post请求
String result = client.getContent(); //获取结果

2.5工程搭建与准备工作

(1)建立支付服务接口模块easyshop-pay-interface(jar)

(2)建立支付服务实现模块easyshop-pay-service(war) 依赖easyshop-pay-interface 和easyshop-common 、spring 、dubbox 相关依赖 、微信SDK (因为不需要连接数据库所以不用引用dao工程)

        <!-- 微信SDK -->
		<dependency>
			<groupId>com.github.wxpay</groupId>
			<artifactId>wxpay-sdk</artifactId>
			<version>0.0.3</version>
		</dependency>

添加tomcat插件,运行端口为9000

添加spring配置文件 ,参见其它服务工程

(3)在easyshop-common工程中添加工具类HttpClient.java ,并添加依赖

	  	<dependency>
	  		<groupId>org.apache.httpcomponents</groupId>
	  		<artifactId>httpclient</artifactId>	  		
	  	</dependency>

添加配置文件weixinpay.properties

appid=wx632c8f211f8122c6
partner=1497984412
partnerkey=sbNCm1JnevqI36LrEaxFwcaT0hkGxFnC
notifyurl=http://a31ef7db.ngrok.io/WeChatPay/WeChatPayNotify

appid: 微信公众账号或开放平台APP的唯一标识

partner:财付通平台的商户账号

partnerkey:财付通平台的商户密钥

notifyurl: 回调地址

(4)easyshop-cart-web依赖工程easyshop-pay-interface

(5)将二维码插件QRious 拷贝到easyshop-cart-web 的plugins目录中

3.微信支付二维码生成

3.1需求分析与实现思路

3.1.1需求分析

在支付页面上生成支付二维码,并显示订单号和金额

用户拿出手机,打开微信扫描页面上的二维码,然后在微信中完成支付

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1hIWLPzf-1675664444082)(file:///C:\Users\lhp\AppData\Local\Temp\ksohtml19224\wps21.jpg)]

3.1.2实现思路

我们通过HttpClient工具类实现对远程支付接口的调用。

接口链接:https://api.mch.weixin.qq.com/pay/unifiedorder

具体参数参见“统一下单”API, 构建参数发送给统一下单的url ,返回的信息中有支付url,根据url生成二维码,显示的订单号和金额也在返回的信息中。

3.2后端代码实现

3.2.1服务接口层

(1)在easyshop-pay-interface创建包com.easyshop.pay.service包下建立接口

package com.easyshop.pay.service;

import java.util.Map;

/*
 * 微信支付接口
 */
public interface WeixinPayService {

	/**
	 * 生成微信支付二维码
	 * @param out_trade_no
	 *            订单号
	 * @param total_fee
	 *            金额(分)
	 * @return
	 */
	public Map createNative(String out_trade_no, String total_fee);
	
}

3.2.2服务实现层

@Service
public class WeixinPayServiceImpl implements WeixinPayService {

	@Value("${appid}")
	private String appid;

	@Value("${partner}")
	private String partner;

	@Value("${partnerkey}")
	private String partnerkey;

	/**
	 * 生成二维码
	 * 
	 * @return
	 */
	public Map createNative(String out_trade_no, String total_fee) {
		// 1.创建参数
		Map<String, String> param = new HashMap();// 创建参数
		param.put("appid", appid);// 公众号
		param.put("mch_id", partner);// 商户号
		param.put("nonce_str", WXPayUtil.generateNonceStr());// 随机字符串
		param.put("body", "易购商城");// 商品描述
		param.put("out_trade_no", out_trade_no);// 商户订单号
		param.put("total_fee", total_fee);// 总金额(分)
		param.put("spbill_create_ip", "127.0.0.1");// IP
		param.put("notify_url", "http://test.easyshop.cn");// 回调地址(随便写)
		param.put("trade_type", "NATIVE");// 交易类型
		try {
			// 2.生成要发送的xml
			String xmlParam = WXPayUtil.generateSignedXml(param, partnerkey);
			System.out.println(xmlParam);
			HttpClient client = new HttpClient("https://api.mch.weixin.qq.com/pay/unifiedorder");
			client.setHttps(true);
			client.setXmlParam(xmlParam);
			client.post();
			// 3.获得结果
			String result = client.getContent();
			System.out.println(result);
			Map<String, String> resultMap = WXPayUtil.xmlToMap(result);
			Map<String, String> map = new HashMap<>();
			map.put("code_url", resultMap.get("code_url"));// 支付地址
			map.put("total_fee", total_fee);// 总金额
			map.put("out_trade_no", out_trade_no);// 订单号
			return map;
		} catch (Exception e) {
			e.printStackTrace();
			return new HashMap<>();
		}
	}

}

3.2.3控制层

@Controller
@RequestMapping("/pay")
public class PayController {

	@Reference
	private WeixinPayService weixinPayService;

	/**
	 * 生成二维码
	 * @return
	 */
	@RequestMapping("/createNative")
	public Map createNative() {
		IdWorker idworker = new IdWorker();
		return weixinPayService.createNative(idworker.nextId() + "", "1");
	}
	
}

这里我们订单号通过分布式ID生成器生成,金额暂时写成1分钱,后续开发我们再对接业务系统得到订单号和金额

浏览器测试

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mEBxMlBr-1675664444082)(file:///C:\Users\lhp\AppData\Local\Temp\ksohtml19224\wps22.jpg)]

3.3前端代码实现

3.3.1 修改控制层

	/**
	 * 生成二维码
	 * @return
	 */
	@RequestMapping("/createNative")
	public String createNative(Model model) {
		IdWorker idworker = new IdWorker();
		Map map = weixinPayService.createNative(idworker.nextId() + "", "1");
		Object out_trade_no = map.get("out_trade_no");
		Object code_url = map.get("code_url");
		Double total_fee = Double.valueOf(map.get("total_fee").toString());
		model.addAttribute("out_trade_no", out_trade_no);
		model.addAttribute("code_url", code_url);
		model.addAttribute("total_fee", total_fee);
		return "pay";
	}

3.3.2 前端页面显示

 <script type="text/javascript">
    
    //{"out_trade_no":"1102221179248291840","code_url":"weixin://wxpay/bizpayurl?pr=7b746YA","total_fee":"1"}
	var qr = new QRious({
		   element:document.getElementById('qrious'),
		   size:250, 	   
		   level:'H',	   
		   value:'[[${code_url}]]'
	});
  </script>

4.检测支付状态

4.1需求分析及实现思路

4.1.1需求分析

当用户支付成功后跳转到成功页面

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RPzoLEV4-1675664444083)(file:///C:\Users\lhp\AppData\Local\Temp\ksohtml19224\wps23.jpg)]

当返回异常时跳转到错误页面

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4lN2qHRR-1675664444083)(file:///C:\Users\lhp\AppData\Local\Temp\ksohtml19224\wps24.jpg)]

4.1.2实现思路

我们通过HttpClient工具类实现对远程支付接口的调用。

接口链接:https://api.mch.weixin.qq.com/pay/orderquery

具体参数参见“查询订单”API, 我们在controller方法中轮询调用查询订单(间隔3秒),当返回状态为success时,我们会在controller方法返回结果。前端代码收到结果后跳转到成功页面。

4.2检测支付状态-后端代码

4.2.1服务接口层

在easyshop-pay-interface的WeixinPayService.java中新增方法定义

	/**
	 * 查询支付状态
	 * 
	 * @param out_trade_no
	 */
	public Map queryPayStatus(String out_trade_no);

4.2.2服务实现层

	@Override
	public Map queryPayStatus(String out_trade_no) {
		
		Map param = new HashMap();
		param.put("appid", appid);// 公众账号ID
		param.put("mch_id", partner);// 商户号
		param.put("out_trade_no", out_trade_no);// 订单号
		param.put("nonce_str", WXPayUtil.generateNonceStr());// 随机字符串
		
		String url = "https://api.mch.weixin.qq.com/pay/orderquery";
		try {
			String xmlParam = WXPayUtil.generateSignedXml(param, partnerkey);
			HttpClient client = new HttpClient(url);
			client.setHttps(true);
			client.setXmlParam(xmlParam);
			client.post();
			String result = client.getContent();
			Map<String, String> map = WXPayUtil.xmlToMap(result);
			System.out.println(map);
			return map;
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

4.2.3控制层

在easyshop-cart-web的PayController.java新增方法

	/**
	 * 查询支付状态
	 * @param out_trade_no
	 * @return
	 */
	@RequestMapping("/queryPayStatus")
	public Result queryPayStatus(String out_trade_no) {
		Result result = null;
		while (true) {
			// 调用查询接口
			Map<String, String> map = weixinPayService.queryPayStatus(out_trade_no);
			if (map == null) {// 出错
				result = new Result(false, "支付出错");
				break;
			}
			if (map.get("trade_state").equals("SUCCESS")) {// 如果成功
				result = new Result(true, "支付成功");
				break;
			}
			try {
				Thread.sleep(3000);// 间隔三秒
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		return result;
	}

4.3检测支付状态-前端代码

 <script type="text/javascript">
    
    function ckStatus(){
    	$.ajax({
			url:"/pay/queryPayStatus/[[${out_trade_no}]]",
			type:"post",
			dataType:"json",
			//data:"out_trade_no=[[${out_trade_no}]]",  
			success:function(result){
				if(result.success==true){
					window.location.href="paySuccess.html";
				}else{
					window.location.href="payFail.html";
				}
			}
		});
    }
    
</script>

4.4查询时间限制

4.4.1问题分析

如果用户到了二维码页面一直未支付,或是关掉了支付页面,我们的代码会一直循环调用微信接口,这样会对程序造成很大的压力。所以我们要加一个时间限制或是循环次数限制,当超过时间或次数时,跳出循环。

4.4.2代码完善

(1)修改easyshop-cart-web工程PayController.java的queryPayStatus方法

	@RequestMapping("/queryPayStatus")
	public Result queryPayStatus(String out_trade_no){
		Result result=null;		
		int x=0;		
		while(true){
			//调用查询接口
			.......		
			try {
				Thread.sleep(3000);//间隔三秒
			} catch (InterruptedException e) {
				e.printStackTrace();
			}	
			//为了不让循环无休止地运行,我们定义一个循环变量,如果这个变量超过了这个值则退出循环,设置时间为5分钟
			x++;
			if(x>=100){
				result=new  Result(false, "二维码超时");
				break;
			}
		}
		return result;
	}

(2)修改js

	    function ckStatus(){
	    	$.ajax({
				url:"/pay/queryPayStatus/[[${out_trade_no}]]",
				type:"post",
				dataType:"json",
				//data:"out_trade_no=[[${out_trade_no}]]",  
				success:function(result){
					if(result.success==true){
						window.location.href="../paySuccess.html";
					}else if(result.message=="二维码超时"){
						//重新生成二维码,获取out_trade_no和金额重新请求后台
						window.location.reload();
					}else{
						window.location.href="../payFail.html";
					}
				}
			});
	    }

4.5支付成功页面显示金额

4.5.1问题分析

现在我们支付成功页面显示的是固定的值,怎么显示真正的支付金额呢?

4.5.2代码完善

(1)跳转页面传参

    function ckStatus(){
	    	$.ajax({
				url:"/pay/queryPayStatus/[[${out_trade_no}]]",
				type:"post",
				dataType:"json",
				//data:"out_trade_no=[[${out_trade_no}]]",  
				success:function(result){
					if(result.success==true){
						window.location.href="/pay/paySuccess/[[${total_fee/100}]]";
					}else if(result.message=="二维码超时"){
						//重新生成二维码,获取out_trade_no和金额重新请求后台
						window.location.reload();
					}else{
						window.location.href="/pay/payFail";
					}
				}
			});
	    }
	@RequestMapping("/paySuccess/{total_fee}")
	public String paySuccess(@PathVariable("total_fee") String total_fee,Model model){
		model.addAttribute("total_fee", total_fee);
		return "paySuccess";
	}
	
	@RequestMapping("/payFail")
	public String payFail() {
		return "payFail";
	}

页面显示

<div class="paysuccess">
	<div class="success">
	<h3><img src="images/right.png" width="48" height="48"> 恭喜您,支付成功啦!</h3>
	<div class="paydetail">
	<p>支付方式:微信支付</p>
	<p>支付金额:¥<span th:text="${total_fee}">345345</span></p>
	<p class="button"><a href="myOrder.html" class="sui-btn btn-xlarge btn-danger">查看订单</a>&nbsp;&nbsp;&nbsp;&nbsp;<a href="index.html" class="sui-btn btn-xlarge ">继续购物</a></p>
	</div>
	</div>
								
</div>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值