当前位置: 移动技术网 > IT编程>开发语言>Java > Java SpringMVC实现PC端网页微信扫码支付(完整版)

Java SpringMVC实现PC端网页微信扫码支付(完整版)

2019年07月22日  | 移动技术网IT编程  | 我要评论

一:前期微信支付扫盲知识

前提条件是已经有申请了微信支付功能的公众号,然后我们需要得到公众号appid和微信商户号,这个分别在微信公众号和微信支付商家平台上面可以发现。其实在你申请成功支付功能之后,微信会通过邮件把mail转给你的,有了这些信息之后,我们就可以去微信支付服务支持页面:

打开这个页面,点击右上方的链接【开发文档】会进入到api文档说明页面,看起来如下

选择红色圆圈的扫码支付就是我们要做接入方式,鼠标移动到上面会提示你去查看开发文档,如果这个都不知道怎么查看,可以洗洗睡了,你真的不合适做程序员,地址如下:

在浏览器中打开之后会看到

我们重点要关注和阅读的内容我已经用红色椭圆标注好了,首先阅读【接口规则】里面的协议规范,开玩笑这个都不读你就想做微信支付,这个就好比你要去泡妞,得先收集点基本背景信息,了解对方特点,不然下面还怎么沟通。事实证明只有会泡妞得程序员才是好销售。跑题了我们接下来要看一下【场景介绍】中的案例与规范,只看一下记得一定要微信支付的logo下载下来,是为了最后放到我们自己的扫码支付网页上,这样看上去比较专业一点。之后重点关注【模式二】

我们这里就是要采用模式二的方式实现pc端页面扫码支付功能。

微信官方对模式二的解释是这样的“商户后台系统先调用微信支付的统一下单接口,微信后台系统返回链接参数code_url,商户后台系统将code_url值生成二维码图片,用户使用微信客户端扫码后发起支付。注意:code_url有效期为2小时,过期后扫码不能再发起支付”。看明白了吧就是我们首先要调用微信提供统一下单接口,得到一个关键信息code_url(至于这个code_url是什么鬼,我也不知道),然后我们通过自己的程序把这个url生成一个二维码,生成二维码我这里用了google的zxing库。然后把这个二维码显示在你的pc端网页上就行啦。这样终端用户一扫码就支付啦,支付就完成啦,看到这里你肯定很激动,发现微信支付如此简单,等等还有个事情我们还不知道,客户知道付钱了,我们服务器端还不知道呢,以微信开发人员的智商他们早就想到这个问题了,所以让你在调用统一下单接口的时候其中有个必填的参数就是回调url,就是如果客户端付款成功之后微信会通过这个url向我们自己的服务器提交一些数据,然后我们后台解析这些数据,完成我们自己操作。这样我们才知道客户是否真的已经通过微信付款了。这样整个流程才结束,这个就是模式二。微信用一个时序图示这样表示这个过程的。

表达起来比较复杂,看上去比较吃力,总结一下其实我们服务器该做的事情就如下件:

1. 通过统一下单接口传入正确的参数(当然要包括我们的回调url)与签名验证,从返回数据中得到code_url的对应数据

2. 根据code_url的数据我们自己生成一个二维码图片,显示在浏览器网页上

3. 在回调的url中添加我们自己业务逻辑处理。

至此扫盲结束了,你终于知道扫码支付什么个什么样的流程了,下面我们就一起来扒扒它的相关api使用,做好每步处理。

二:开发过程

在开发代码之前,请先准备几件事情。

1. 添加zxing的maven依赖

2. 添加jdom的maven依赖

3.下载java版本sdk演示程序,地址在这里

我们需要md5util.java和xmlutil.java两个文件

4. 我们使用httpclient版本是4.5.1,记得添加maven依赖

上面准备工作做好以后,继续往下看:

首先我们要调用微信的统一下单接口,我们点击【api列表】中的统一下单会看到这样页面:

以本人调用实际情况为例,如下的参数是必须要有的,为了大家的方便我已经把它变成一个pojo的对象, 代码如下:

public class unifiedorderdto implements weixinconstants {
private string appid;
private string body;
private string device_info;
private string mch_id;
private string nonce_str;
private string notify_url;
private string openid;
private string out_trade_no;
private string spbill_create_ip;
private int total_fee;
private string trade_type;
private string product_id;
private string sign;
public unifiedorderdto() {
this.appid = appid;
this.mch_id = wxpaymentaccount;
this.device_info = device_info_web;
this.notify_url = callback_url;
this.trade_type = trade_type_native;
}
public string getappid() {
return appid;
}
public void setappid(string appid) {
this.appid = appid;
}
public string getbody() {
return body;
}
public void setbody(string body) {
this.body = body;
}
public string getdevice_info() {
return device_info;
}
public void setdevice_info(string device_info) {
this.device_info = device_info;
}
public string getmch_id() {
return mch_id;
}
public void setmch_id(string mch_id) {
this.mch_id = mch_id;
}
public string getnonce_str() {
return nonce_str;
}
public void setnonce_str(string nonce_str) {
this.nonce_str = nonce_str;
}
public string getnotify_url() {
return notify_url;
}
public void setnotify_url(string notify_url) {
this.notify_url = notify_url;
}
public string getopenid() {
return openid;
}
public void setopenid(string openid) {
this.openid = openid;
}
public string getout_trade_no() {
return out_trade_no;
}
public void setout_trade_no(string out_trade_no) {
this.out_trade_no = out_trade_no;
}
public string getspbill_create_ip() {
return spbill_create_ip;
}
public void setspbill_create_ip(string spbill_create_ip) {
this.spbill_create_ip = spbill_create_ip;
}
public int gettotal_fee() {
return total_fee;
}
public void settotal_fee(int total_fee) {
this.total_fee = total_fee;
}
public string gettrade_type() {
return trade_type;
}
public void settrade_type(string trade_type) {
this.trade_type = trade_type;
}
public string getsign() {
return sign;
}
public void setsign(string sign) {
this.sign = sign;
}
public string getproduct_id() {
return product_id;
}
public void setproduct_id(string product_id) {
this.product_id = product_id;
}
public string generatexmlcontent() {
string xml = "<xml>" +
"" + this.appid + "</appid>" + 
"" + this.body + "" + 
"<device_info>web</device_info>" + 
"<mch_id>" + this.mch_id + "</mch_id>" + 
"<nonce_str>" + this.nonce_str + "</nonce_str>" +
"<notify_url>" + this.notify_url + "</notify_url>" + 
"<out_trade_no>" + this.out_trade_no + "</out_trade_no>" + 
"<product_id>" + this.product_id + "</product_id>" +
"<spbill_create_ip>" + this.spbill_create_ip+ "</spbill_create_ip>" +
"<total_fee>" + string.valueof(this.total_fee) + "</total_fee>" + 
"<trade_type>" + this.trade_type + "</trade_type>" + 
"<sign>" + this.sign + "</sign>" + 
"</xml>";
return xml;
}
public string makesign() {
string content ="appid=" + this.appid + 
"&body=" + this.body + 
"&device_info=web" + 
"&mch_id=" + this.mch_id + 
"&nonce_str=" + this.nonce_str + 
"?ify_url=" + this.notify_url +
"&out_trade_no=" + this.out_trade_no + 
"&product_id=" + this.product_id + 
"&spbill_create_ip=" + this.spbill_create_ip+
"&total_fee=" + string.valueof(this.total_fee) +
"&trade_type=" + this.trade_type;
content = content + "&key=" + weixinconstants.md5_api_key;
string esignature = weixinpaymentutil.md5encode(content, "utf-8");
return esignature.touppercase();
}
}

其中各个成员变量的解释可以参见【统一下单接口】的说明即可。

有这个之后我们就要要设置的内容填写进去,去调用该接口得到返回数据,从中拿到code_url的数据然后据此生成一个二维图片,把图片的地址返回给pc端网页,然后它就会显示出来,这里要特别说明一下,我们自己pc端网页在点击微信支付的时候就会通过ajax方式调用我们自己后台的springmvc controller然后在controller的对应方法中通过httpclient完成对微信统一下单接口调用解析返回的xml数据得到code_url的值,生成二维码之后返回给前台网页。controller中实现的代码如下:

map<string,object> result=new hashmap<string,object>();
unifiedorderdto dto = new unifiedorderdto();
if(cash == null || "".equals(cash)) {
result.put("error", "cash could not be zero");
return result;
}
int totalfee = 100*integer.parseint(cash);
logger.info("total recharge cash : " + totalfee);
dto.setproduct_id(string.valueof(system.currenttimemillis()));
dto.setbody("repair");
dto.setnonce_str(string.valueof(system.nanotime()));
logininfo logininfo = logininfoutil.getlogininfo();
// 通过我们后台订单号+uuid为身份识别标志
dto.setout_trade_no("你的订单号+关键信息,微信回调之后传回,你可以验证");
dto.settotal_fee(totalfee);
dto.setspbill_create_ip("127.0.0.1");
// generate signature
dto.setsign(dto.makesign());
logger.info("sign : " + dto.makesign());
logger.info("xml content : " + dto.generatexmlcontent());
try {
httpclient httpclient = httpclientbuilder.create().build(); 
httppost post = new httppost(weixinconstants.unifiedorder_url);
post.addheader("content-type", "text/xml; charset=utf-8");
stringentity xmlentity = new stringentity(dto.generatexmlcontent(), contenttype.text_xml);
post.setentity(xmlentity);
httpresponse httpresponse = httpclient.execute(post);
string responsexml = entityutils.tostring(httpresponse.getentity(), "utf-8");
logger.info("response xml content : " + responsexml);
// parse code_url content
map<string, string=""> resultmap = (map<string, string="">)xmlutil.doxmlparse(responsexml);
logger.info("response code_url : " + resultmap.get("code_url"));
string codeurl = resultmap.get("code_url");
if(codeurl != null && !"".equals(codeurl)) {
string imageurl = generateqrcode(codeurl);
result.put("qrimage", imageurl);
}
post.releaseconnection();
} catch(exception e) {
e.printstacktrace();
}
result.put("success", "1");
return result;</string,></string,></string,object></string,object>

生成二维码的代码如下:

private string generateqrcode(string codeurl) {
file foldler = new file(basepath + "qrcode");
if(!foldler.exists()) {
foldler.mkdirs();
}
string f_name = uuidutil.uuid() + ".png";
try {
file f = new file(basepath + "qrcode", f_name);
fileoutputstream fio = new fileoutputstream(f);
multiformatwriter multiformatwriter = new multiformatwriter();
map hints = new hashmap();
hints.put(encodehinttype.character_set, "utf-8"); //设置字符集编码类型
bitmatrix bitmatrix = null;
bitmatrix = multiformatwriter.encode(codeurl, barcodeformat.qr_code, 300, 300,hints);
bufferedimage image = tobufferedimage(bitmatrix);
//输出二维码图片流
imageio.write(image, "png", fio);
return ("qrcode/" + f_name);
} catch (exception e1) {
e1.printstacktrace();
return null;
} 
}

此时如何客户端微信扫码之后,微信就会通过回调我们制定url返回数据给我们。在回调方法中完成我们自己的处理,这里要特别注意的是你的回调接口必须通过http post方法实现,否则无法接受到xml数据。回调处理的代码如下:

@requestmapping(value = "/your_callback_url", method = requestmethod.post)
@responsebody
public void finishpayment(httpservletrequest request, httpservletresponse response) {
try { logger.info("start to callback from weixin server: " + request.getremotehost());
map<string, string=""> resultmap = new hashmap<string, string="">();
inputstream inputstream = request.getinputstream();
// 读取输入流
saxbuilder saxbuilder= new saxbuilder();
document document = saxbuilder.build(inputstream);
// 得到xml根元素
element root = document.getrootelement();
// 得到根元素的所有子节点
list list = root.getchildren();
iterator it = list.iterator();
while(it.hasnext()) {
element e = (element) it.next();
string k = e.getname();
string v = "";
list children = e.getchildren();
if(children.isempty()) {
v = e.gettextnormalize();
} else {
v = xmlutil.getchildrentext(children);
}
resultmap.put(k, v);
}
// 验证签名!!!
/*
string[] keys = resultmap.keyset().toarray(new string[0]);
arrays.sort(keys);
string kvparams = "";
for(int i=0; i<keys.length; i++)="" {="" if(keys[i].equals("esign"))="" continue;="" }="" 签名算法="" if(i="=" 0)="" kvparams="" +="(keys[i]" "=" + resultmap.get(keys[i]));
} else {
kvparams += (" &"="" keys[i]="" &key=" + weixinconstants.md5_api_key;
string md5esign = weixinpaymentutil.md5encode(esign, " utf-8");="" if(!md5esign.equals(resultmap.get("sign")))="" return;="" }*="" 关闭流="" 释放资源="" inputstream.close();="" inputstream="null;" string="" returncode="resultmap.get("return_code");" outtradeno="resultmap.get("out_trade_no");" 以分为单位="" int="" nfee="integer.parseint(resultmap.get("total_fee"));" logger.info("out="" trade="" no="" :="" outtradeno);="" logger.info("total_fee="" nfee);="" 业务处理流程="" if("success".equals(returncode))="" todo:="" your="" business="" process="" add="" here="" response.getwriter().print(xmlutil.getretresultxml(resultmap.get("return_code"),="" resultmap.get("return_code")));="" else="" resultmap.get("return_msg")));="" catch(ioexception="" ioe)="" ioe.printstacktrace();="" catch="" (jdomexception="" e1)="" e1.printstacktrace();="" }

微信官方java版demo用到的xmlutil和md5util的两个类记得拿过来改一下,演示代码可以在它的官方演示页面找到,相关maven依赖如下:

<dependency>
<groupid>jdom</groupid>
jdom</artifactid>
<version>1.1</version>
</dependency>
<dependency>
<groupid>com.google.zxing</groupid>
core</artifactid>
<version>3.3.0</version>
</dependency>

最后要特别注意的是关于签名,签名生成md5的类我是从微信官网直接下载java版demo程序获取的,建议你也是,因为这个是确保md5签名是一致的最佳选择。具体的生成签名的算法可以查看微信官方文档,这里也强烈建议大家一定要官方api说明,你开发中所遇到各种问题90%都是因为不看官方文档,而是轻信某人博客!这个才是我写这篇文章的真正目的和用意,根据官方文档,用我的java代码实现,微信pc端网页扫码支付必定在你的web应用中飞起来。

以上所述是小编给大家介绍的java springmvc实现pc端网页微信扫码支付(完整版),希望对大家有所帮助

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网