记录下自己写的微信扫码支付(网页版),适合初入编程的人参考借鉴,因为我也是看了很多csdn上的例子加上查看文档和自己钻研总结出来的,总的来说网上的例子都不是很完善,缺少一些方法或者实体类(可能是觉得大家都知道吧- -!),得自己填平。。我写这篇文章尽量把代码和逻辑都阐述清晰,希望能帮到困在这个问题上的朋友们,代码直接看第三节。。
一, 场景分析
在编写代码前,首先你要知道你要实现什么?也就是你要作出什么东西,有什么效果,怎么用。。大家可以先阅读官方文档-扫码支付这块仔细阅读一下,包括场景介绍到模式2都要读完并理解这样我觉得你可能就不用看我下面的文章了。。
微信支付文档:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_1,以下必读(api需要什么功能看什么)
二,逻辑分析
如果看完了上面的文档但又没有完全理解,无从下手,那请看我下面的详细攻略:
打开模式2仔细阅读业务逻辑:
1) 点击购买按钮跳转到支付页面,传递统一下单所需参数
2) 支付页面,后台实现统一下单的方法,成功將获取code_url这个参数,根据这个参数和对应api生成二维码
3) 生成二维码用户扫描并支付成功后,会返回給微信消息,而同时微信会返回给我们一个消息,也就是支付结果通知
4) 通过判定通知中的信息决定返回給用户什么消息(比如成功跳转或者手机端的跳转等)
5) 超时支付等额外的细节,这里不细说
三, 准备开发
1)导入weixin4j依赖包(直接到官网下载,这款sdk还是不错的) http://www.weixin4j.org
2) service代码(controller中直接调用即可我就不发了- -!)
a.统一下单
@Override
public JsonResult weixinPay(HttpServletRequest request, HttpServletResponse response) {
JsonResult json = new JsonResult();
//统一下单
UnifiedOrder ufo = new UnifiedOrder();
String appid = Configuration.getOAuthAppId(); // appid
String mch_id = Configuration.getOAuthpartnerId();//商户id
String mch_key = Configuration.getOAuthpartnerKey(); // 商户key
String nonce_str = UUID.randomUUID().toString().substring(0, 15);
String total_fee = "1"; // 价格 注意:价格的单位是分
String body = "test"; // 商品名称
String date = String.valueOf(new Date().getTime());
String randomA = String.valueOf((int)(Math.random() * 100000000 ));
String randomB = String.valueOf((int)(Math.random() * 100000000 ));
String randomC = String.valueOf((int)(Math.random() * 100000000 ));
String str = (randomA+randomA+randomA).substring(0, 15);
String out_trade_no = date+str; // 订单号
// 获取发起电脑 ip
String spbill_create_ip = request.getRemoteAddr();
// 回调接口
String notify_url = "自己公司配置的支付回调地址,不知道就问上司或度哥吧";
String trade_type = "NATIVE";
ufo.setAppid(appid);
ufo.setBody(body);
ufo.setMch_id(mch_id);
ufo.setNonce_str(nonce_str);
ufo.setNotify_url(notify_url);
ufo.setOut_trade_no(out_trade_no);
ufo.setSpbill_create_ip(spbill_create_ip);
ufo.setTotal_fee(total_fee);
ufo.setTrade_type(trade_type);
ufo.setOpenid("");
// 获得预支付Map 10个参数
Map<String, String> unifiedMap = ufo.toMap();
logger.debug("unifiedMap is::"+ unifiedMap);
// 获取预支付签名
String estimateSign = SignUtil.getSign(unifiedMap, mch_key);
ufo.setSign(estimateSign);
logger.debug("estimateSign is::"+ estimateSign);
Weixin wx = new Weixin();
try {
UnifiedOrderResult ufor = wx.payUnifiedOrder(ufo);
ufor.setDevice_info(out_trade_no+":"+total_fee);
//记录订单信息到数据库
HpWechatPayOrder hwpo = new HpWechatPayOrder();
hwpo.setId(UUID.randomUUID().toString());
hwpo.setAppid(appid);
hwpo.setBody(body);
hwpo.setMchId(mch_id);
hwpo.setNotifyUrl(notify_url);
hwpo.setOutTradeNo(out_trade_no);
hwpo.setSpbillCreateIp(spbill_create_ip);
hwpo.setTotalFee(Integer.valueOf(total_fee));
hwpo.setTradeType(trade_type);
hwpo.setSign(estimateSign);
hwpo.setNonceStr(nonce_str);
hwpo.setOrderStatus("N");
hwpo.setTs(new Date());
logger.debug("hwpo is::"+ hwpo);
hpWechatPayOrderMapper.insert(hwpo);
logger.debug("dbSUCCESS");
json = new JsonResult(ufor);
} catch (Exception e1) {
logger.debug("WeixinException is::"+ e1);
System.out.println(e1);
json = new JsonResult(new ServiceException("微信认证失败"));
e1.printStackTrace();
}
return json;
}
b.生成二维码,需要导包后面统一说需要的sdk和导包
// 加载二维码到网页
public void getCode(String url,HttpServletRequest request, HttpServletResponse response) {
// 二维码图片输出流
OutputStream out = null;
try {
response.setContentType("image/jpeg;charset=UTF-8");
JsonResult json = generateQrcode(url);
// 实例化输出流对象
out = response.getOutputStream();
// 画图
ImageIO.write((BufferedImage) json.getData(), "png", response.getOutputStream());
out.flush();
out.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (null != response.getOutputStream()) {
response.getOutputStream().close();
}
if (null != out) {
out.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
//生成二维码
public JsonResult generateQrcode(String codeurl) {
JsonResult json = null;
try {
BufferedImage image = null;
// 二维码图片输出流
MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
HashMap<EncodeHintType, Comparable> hints = new HashMap<EncodeHintType, Comparable>();
// 设置编码方式
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
// 设置QR二维码的纠错级别(H为最高级别)具体级别信息
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
BitMatrix bitMatrix = multiFormatWriter.encode(codeurl, BarcodeFormat.QR_CODE, 400, 400, hints);
bitMatrix = updateBit(bitMatrix, 10);
image = MatrixToImageWriter.toBufferedImage(bitMatrix);
json = new JsonResult(image);
}catch(Exception e){
json = new JsonResult(new ServiceException(e));
}
return json;
}
/**
* 自定义白边边框宽度
*
* @param matrix
* @param margin
* @return
*/
private static BitMatrix updateBit(final BitMatrix matrix, final int margin) {
int tempM = margin * 2;
// 获取二维码图案的属性
int[] rec = matrix.getEnclosingRectangle();
int resWidth = rec[2] + tempM;
int resHeight = rec[3] + tempM;
// 按照自定义边框生成新的BitMatrix
BitMatrix resMatrix = new BitMatrix(resWidth, resHeight);
resMatrix.clear();
// 循环,将二维码图案绘制到新的bitMatrix中
for (int i = margin; i < resWidth - margin; i++) {
for (int j = margin; j < resHeight - margin; j++) {
if (matrix.get(i - margin + rec[0], j - margin + rec[1])) {
resMatrix.set(i, j);
}
}
}
return resMatrix;
}
c.支付回调
//支付回调
public JsonResult payResult(HttpServletRequest res,HttpServletResponse rep) {
JsonResult json = null;
try {
InputStream inputStream;
StringBuffer sb = new StringBuffer();
inputStream = res.getInputStream();
String s;
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
while ((s = in.readLine()) != null) {
sb.append(s);
}
logger.debug("返回结果 xml::" + sb);
Map<String, Object> map = new HashMap<String, Object>();
//判定是支付成功的返回结果
if(sb.toString().indexOf("xml")!=-1) {
//將xml转化为map
Document doc;
doc = DocumentHelper.parseText(sb.toString());
Element root = doc.getRootElement();
List children = root.elements();
if (children != null && children.size() > 0) {
for (int i = 0; i < children.size(); i++) {
Element child = (Element) children.get(i);
map.put(child.getName(), child.getTextTrim());
}
}
}
//先验证签名(此处我没来及写,聪明的你怕是已经自行参悟出来了把)
String resXml = "";
if(map.get("return_code").equals("SUCCESS")) {
logger.debug("返回结果:: "+ map.get("openid") +"用户支付成功");
resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
+ "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
//保存支付结果到数据库
HpWechatPayResult hwpr = new HpWechatPayResult();
hwpr.setId(UUID.randomUUID().toString());
hwpr.setAppid((String) map.get("appid"));
hwpr.setBankType((String) map.get("bank_type"));
hwpr.setOpenid((String) map.get("openid"));
hwpr.setCashFee(Integer.valueOf((String) map.get("cash_fee")));
hwpr.setErrCode((String) map.get("err_code"));
hwpr.setFeeType((String) map.get("fee_type"));
hwpr.setMchId((String) map.get("mch_id"));
hwpr.setNonceStr((String) map.get("nonce_str"));
hwpr.setOpenid((String) map.get("openid"));
hwpr.setOutTradeNo((String) map.get("out_trade_no"));
hwpr.setResultCode((String) map.get("result_code"));
hwpr.setReturnCode((String) map.get("return_code"));
hwpr.setReturnMsg((String) map.get("return_msg"));
hwpr.setSign((String) map.get("sign"));
hwpr.setTimeEnd((String) map.get("time_end"));
hwpr.setTotalFee((Integer.valueOf((String) map.get("total_fee"))));
hwpr.setTradeType((String) map.get("trade_type"));
hwpr.setTransactionId((String) map.get("transaction_id"));
hwpr.setTs(new Date());
hpWechatPayResultMapper.insert(hwpr);
//將hp_wechat_pay_order表中状态改为1
HpWechatPayOrderExample hwpoe = new HpWechatPayOrderExample();
hwpoe.or().andAppidEqualTo((String) map.get("appid"))
.andOutTradeNoEqualTo((String) map.get("out_trade_no"));
List<HpWechatPayOrder> list = hpWechatPayOrderMapper.selectByExample(hwpoe);
HpWechatPayOrder hwpo = list.get(0);
hwpo.setOrderStatus("Y");
hpWechatPayOrderMapper.updateByExample(hwpo, hwpoe);
}else {
logger.info("支付失败,错误信息:" + map.get("err_code"));
resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
+ "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
}
BufferedOutputStream out = new BufferedOutputStream(rep.getOutputStream());
out.write(resXml.getBytes());
out.flush();
out.close();
logger.debug("返回结果 map::" + map);
//json = new JsonResult(map);
//关闭连接
in.close();
inputStream.close();
} catch (Exception e) {
logger.debug("返回结果 Exception is::"+ e);
//json = new JsonResult(new Exception(e));
}
return json;
}
3)需要导的包
框架包我就不说了,什么mysql的依赖,logger依赖等等,这块就说微信支付需要的包
a.生成二维码的
<!-- 生成二维码 -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>2.2</version>
</dependency>
b.读取xml的
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
c.JsonResult类,其实就是个返回结果的对象类,自己写的方便接收数据后的判断,大家可以用object类都一样
package rpk_web.com.basic;
/**借助此对象封装Controller方法上有
* @ResponseBody注解的方法的返回值,
* 目的:统一返回值类型,便于在页面上进
* 行统一处理
* */
public class JsonResult {
private static final int SUCCESS=1;
private static final int ERROR=0;
/**状态*/
private int state;
/**对应状态的消息*/
private String message;
/**具体业务数据*/
private Object data;
public JsonResult(){
this.state=SUCCESS;//1
}
/**此构造方法应用于data为null的场景*/
public JsonResult(String message){
this();
this.message=message;
}
/**有具体业务数据返回时,使用此构造方法*/
public JsonResult(Object data){
this();
this.data=data;
}
/**此方法用于两个拥有业务数据返回时合并的方法*/
public JsonResult(Object data1,Object data2) {
this();
this.data = data1;
this.data = data2;
}
/**出现异常以后要调用此方法封装异常信息*/
public JsonResult(Throwable t){
this.state=ERROR;
this.message=t.getMessage();
}
public Object getData() {
return data;
}
public int getState() {
return state;
}
public String getMessage() {
return message;
}
public void setState(int state) {
this.state = state;
}
@Override
public String toString() {
return "JsonResult [state=" + state + ", message=" + message + ", data=" + data + "]";
}
}
d.还有一个封装错误信息的类
package rpk_web.com.basic;
public class ServiceException extends RuntimeException {
/**
*
*/
private static final long serialVersionUID = -4598281045212684924L;
public ServiceException() {
super();
}
public ServiceException(String message, Throwable cause, boolean enableSuppression,
boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public ServiceException(String message, Throwable cause) {
super(message, cause);
// TODO Auto-generated constructor stub
}
public ServiceException(String message) {
super(message);
// TODO Auto-generated constructor stub
}
public ServiceException(Throwable cause) {
super(cause);
// TODO Auto-generated constructor stub
}
}
package rpk_web.com.basic;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
public class ControllerExceptionHandler {
/**当spring发现系统出现异常了,且异常的
* 类型为ServiceException类型,此时就会
* 回调此方法,并将异常值传递给这个方法,
* 这时我们就可以在此方法中对业务异常进行
* 统一处理,例如封装到jsonResult,然后
* 写到客户端告诉用户.*/
@ExceptionHandler(ServiceException.class)
@ResponseBody
public JsonResult handleServiceException(
ServiceException e){
e.printStackTrace();
return new JsonResult(e);
//this.state=ERROR;
//this.message=e.getMessage();
}
@ExceptionHandler(RuntimeException.class)
public ModelAndView handleRuntimeException(RuntimeException e) {
System.out.println("handleRuntimeException");
ModelAndView mv = new ModelAndView("error");
mv.addObject("exp", e.getMessage());
return mv;
}
}
4)前端代码
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<c:set var="basePath" value="${pageContext.request.contextPath}"></c:set>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport"
content="width=device-width, initial-scale=1, user-scalable=no">
<title></title>
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet"
href="${basePath}/lib/bootstrap/css/bootstrap.css">
<link rel="stylesheet" href="${basePath}/css/main.css">
<script src="${basePath}/lib/jquery/jquery.js"></script>
<script type="text/javascript">
var interval;
$(function(){
doGetObjects();
interval = setInterval(doCheckObjects,4000);
})
//计数
var count = 0;
function doGetObjects(){
var url="codeUrl";
$.post(url,function(result){
$(".orderDetails").append("<p>订单编号:"+result.data.device_info.split(":")[0]+"</p><p>订单类型:"+result.data.trade_type+"</p>");
$(".orderAmount").append("<p>应付金额:"+result.data.device_info.split(":")[1]+"</p>")
$(".weixinCodeContent").append(""+
"<div style='height:100%;width:100%;margin-top:14px;'><img style='width:60%;' id='QRCode' src='${basePath}/getCode?url="+result.data.code_url+"' />"+
"<img class= 'weixinText' alt='' src='${basePath}/img/text.png'/></div>");
});
}
//查询订单
function doCheckObjects(){
count++;
if(count > 20){
clearInterval(interval);
//发起关闭订单
CloseOrder();
}
var url="checkPayResult";
var out_trade_no = $(".orderDetails p:first").html().split(":")[1];
var params = {"out_trade_no":out_trade_no};
$.post(url,params,function(result){
console.log(result)
if(result.data.orderStatus == "Y"){
window.location.href='返回支付成功的页面';
clearInterval(interval);
}
});
}
//关闭订单
function CloseOrder(){
alert("订单超时");
}
</script>
<style>
.orderInformation{
width:100%;
height:100px;
}
.orderDetails{
width:50%;
float:left;
height:100px;
padding: 20px 50px;
}
.orderAmount{
width:50%;
float:right;
height:100px;
text-align:center;
line-height:100px;
font-size:20px;
font-weight:bolder;
}
.weixinPayContent{
padding:20px 60px;
width:100%;
height:600px;
}
.weixinContent{
width:100%;
height:680px;
border:solid 1px #4e4e4e;
}
.weixinLogo{
width:15%;
float:left;
margin:14px 24px;
}
.weixinText{
margin-top:10px;
width:60%;
}
.weixinCodeContent{
width:46%;
height:400px;
margin:0 auto;
text-align:center;
}
</style>
</head>
<body>
<!-- 头部区域 -->
<header id="header">
<%@include file="basic/header.jsp"%>
<%@include file="basic/navbar.jsp"%>
</header>
<!-- 订单信息 -->
<div class="orderInformation">
<div class="orderDetails">
</div>
<div class="orderAmount">
</div>
</div>
<div class= "weixinPayContent">
<div class= "weixinContent">
<img class= "weixinLogo" alt="" src="${basePath}/img/WePayLogo.png">
<div class= "weixinCodeContent">
</div>
</div>
</div>
</body>
</html>
四,总结
微信支付或者说三方支付看起来容易其实坑很多,看文档是至关重要的,因为你调用别人的接口人家叫你传什么参数就要传什么参数不能写错,还有就是一点,支付结果通知一开始我是没看,是去循环调用查询支付这个api去得知用户支付返回的结果的,这样效率不是很高,所以尽量把api都大致读一遍,心理有个底,这样需要什么功能再去看就事倍功半了