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

java微信扫码支付模式一线下支付功能实现

程序员文章站 2024-03-12 13:39:32
一、准备工作 无数人来追问模式一的开发,所以在这就贴出来,仅供参考。关于模式一和模式二的区别,我有解释过很多次,无非就是模式一的二维码是针对商品的,模式二的二维码是针对订...

一、准备工作

无数人来追问模式一的开发,所以在这就贴出来,仅供参考。关于模式一和模式二的区别,我有解释过很多次,无非就是模式一的二维码是针对商品的,模式二的二维码是针对订单的,其他具体细节我就不费口舌了,各位可以自行去官方查看文档,然后是选模式一还是模式二就得看自己的业务了。

1.1、有关配置参数

还是之前那四样,app_id和app_secret可以在公众平台找着,mch_id和api_key则在商户平台找到,特别是api_key要在商户平台设置好,这个东东关系到参数校验的正确与否,所以一定要设置正确。扫码支付模式一其实与扫码支付模式二类似,实际只会用到app_id、mch_id和api_key,其他的都不用。模式一的官方文档地址在这:

1.2、有关概念

在这里我想先修正一个概念,在之前模式二开发过程中我曾提到了一个“支付回调地址”这样的概念,其作用实际就是客户在扫描完成支付后,微信服务器要访问我们提供的这个地址,给我们发送支付结果,以便我们核实订单进行发货,这是其他支付工具比较普遍的概念和叫法。不过后来我翻了一下微信官网的文档,发现在模式一开发中,他们把这个叫做“异步通知url”而不是什么“支付回调地址”,但本质这指的是一个意思。可是为什么我要在这提到这个东东呢?这是因为在模式一中,实际上还有另外一个所谓的“支付回调”称之为“扫码支付回调url”,这东东与上面的“异步通知url”可就不一样了,简单理解可以认为是咱们的服务器上一个用来辅助完成下单的接口。模式一的开发同时需要“扫码支付回调url”与“异步通知url”两个接口配合才能完成,所以这里大家要辨别好了。

“异步通知url”在调用统一下单接口时进行设置,可以动态设置,只要这个接口按照有关规则接收参数响应参数即可。而“扫码支付回调url”则较为固定,它在微信公众平台设置,设置后需要10分钟左右才能生效,大家登录微信公众平台后,选择微信支付,在开发配置选项卡下面就可以找着:

java微信扫码支付模式一线下支付功能实现

这里咱们要设置一个自己服务器的地址(再说一遍公网地址,就是让微信服务器能找着你)。

1.3、开发环境

我这里以最基本的servlet 3.0作为示例环境。关于引用第三方的jar包,相比较于模式二开发,除了用到了xml操作的jdom,以外就一个google zxing的二维码包和log4j包。如下图:

java微信扫码支付模式一线下支付功能实现

为了方便调试,建议各位先在这个环境下调通了再移植到真实项目当中去。

二、开发实战

在动手之前,我建议大家先去官方文档那好好看看那个时序图,理解了那个时序图,写代码也就不是什么难事了,当然如果看图你没办法理解,也可以结合我下面的代码来试着理解。

2.1、二维码生成

首先是二维码,二维码中的内容为链接,形式为:

weixin://wxpay/bizpayurl?sign=xxxxx&appid=xxxxx&mch_id=xxxxx&product_id=xxxxxx&time_stamp=xxxxxx&nonce_str=xxxxx

具体可以参考官方文档模式一生成二维码规则。接下来我们需要将该链接生成二维码,我这里使用了google zxing来生成二维码。

package com.wqy; 
 
import java.io.ioexception; 
import java.io.outputstream; 
import java.util.hashmap; 
import java.util.iterator; 
import java.util.map; 
import java.util.set; 
import java.util.sortedmap; 
import java.util.treemap; 
 
import javax.servlet.servletexception; 
import javax.servlet.annotation.webservlet; 
import javax.servlet.http.httpservlet; 
import javax.servlet.http.httpservletrequest; 
import javax.servlet.http.httpservletresponse; 
 
import org.apache.log4j.logger; 
 
import com.google.zxing.barcodeformat; 
import com.google.zxing.encodehinttype; 
import com.google.zxing.multiformatwriter; 
import com.google.zxing.writerexception; 
import com.google.zxing.client.j2se.matrixtoimagewriter; 
import com.google.zxing.common.bitmatrix; 
import com.google.zxing.qrcode.decoder.errorcorrectionlevel; 
import com.wqy.util.paycommonutil; 
import com.wqy.util.payconfigutil; 
 
/** 
 * servlet implementation class pay1 
 */ 
@webservlet("/pay1") 
public class pay1 extends httpservlet { 
 private static final long serialversionuid = 1l; 
 private static logger logger = logger.getlogger(pay1.class); 
 
 public static int defaultwidthandheight=200; 
 
 /** 
 * @see httpservlet#httpservlet() 
 */ 
 public pay1() { 
 super(); 
 // todo auto-generated constructor stub 
 } 
 
 /** 
 * @see httpservlet#doget(httpservletrequest request, httpservletresponse 
 * response) 
 */ 
 protected void doget(httpservletrequest request, httpservletresponse response) 
  throws servletexception, ioexception { 
  
 // todo auto-generated method stub 
 string nonce_str = paycommonutil.getnonce_str(); 
 long time_stamp = system.currenttimemillis() / 1000; 
 string product_id = "hd_goodsssss_10"; 
 string key = payconfigutil.api_key; // key 
  
 sortedmap<object, object> packageparams = new treemap<object, object>(); 
 packageparams.put("appid", payconfigutil.app_id); 
 packageparams.put("mch_id", payconfigutil.mch_id); 
 packageparams.put("time_stamp", string.valueof(time_stamp)); 
 packageparams.put("nonce_str", nonce_str); 
 packageparams.put("product_id", product_id); 
 string sign = paycommonutil.createsign("utf-8", packageparams,key);//md5哈希 
 packageparams.put("sign", sign); 
  
 //生成参数 
 string str = tourlparams(packageparams); 
 string payurl = "weixin://wxpay/bizpayurl?" + str; 
 logger.info("payurl:"+payurl); 
  
  
 //生成二维码 
 map<encodehinttype, object> hints=new hashmap<encodehinttype, object>(); 
 // 指定纠错等级 
 hints.put(encodehinttype.error_correction, errorcorrectionlevel.l); 
 // 指定编码格式 
 hints.put(encodehinttype.character_set, "utf-8"); 
 hints.put(encodehinttype.margin, 1); 
 try { 
  bitmatrix bitmatrix = new multiformatwriter().encode(payurl,barcodeformat.qr_code, defaultwidthandheight, defaultwidthandheight, hints); 
  outputstream out = response.getoutputstream(); 
  matrixtoimagewriter.writetostream(bitmatrix, "png", out);//输出二维码 
  out.flush(); 
  out.close(); 
  
 } catch (writerexception e) { 
  // todo auto-generated catch block 
  e.printstacktrace(); 
 } 
 } 
 
 public string tourlparams(sortedmap<object, object> packageparams){ 
 //实际可以不排序 
 stringbuffer sb = new stringbuffer(); 
 set es = packageparams.entryset(); 
 iterator it = es.iterator(); 
 while (it.hasnext()) { 
  map.entry entry = (map.entry) it.next(); 
  string k = (string) entry.getkey(); 
  string v = (string) entry.getvalue(); 
  if (null != v && !"".equals(v)) { 
  sb.append(k + "=" + v + "&"); 
  } 
 } 
  
 sb.deletecharat(sb.length()-1);//删掉最后一个& 
 return sb.tostring(); 
 } 
 
 /** 
 * @see httpservlet#dopost(httpservletrequest request, httpservletresponse 
 * response) 
 */ 
 protected void dopost(httpservletrequest request, httpservletresponse response) 
  throws servletexception, ioexception { 
 // todo auto-generated method stub 
 doget(request, response); 
 } 
 
} 

2.2、扫描支付回调url接口

当客户用微信扫了上面的二位码之后,微信服务器就会访问此接口,在这里我们要完成统一下单获取交易会话标识,处理的主要流程如下:

1)、接收微信服务器发送过来的参数,对参数进行签名校验;

2)、取出参数product_id,这是二维码上唯一能够透传过来的参数,其他参数可参照官方文档模式一3.1 输入参数;

3)、根据product_id处理自己的业务,比如计算支付金额,生成订单号等;

4)、调用统一下单接口获取交易会话标识prepay_id;

   4.1)、准备好相关参数(如appid、mch_id、支付金额、订单号、商品描述等),调用微信统一下单接口(与模式二调用统一下单接口类似),留意一下这里要加上上面提到的“异步通知url”,也就是后面会说道的异步通知url接口,具体参数参考官方文档统一下单请求参数;

   4.2)、接收统一下单接口返回的参数,对参数进行验签;  

   4.3)、取出参数prepay_id,这是交易会话标识,极其重要,其他参数可参考官方文档统一下单返回结果;

5)、准备好相关参数(如appid、mch_id、return_code、prepay_id等),响应最开始的支付回调(如果上面步骤如果错误,如验签失败则可以返回错误参数给微信服务器),具体参数可参照官方文档模式一3.2 输出参数。

package com.wqy; 
 
import java.io.bufferedoutputstream; 
import java.io.bufferedreader; 
import java.io.ioexception; 
import java.io.inputstream; 
import java.io.inputstreamreader; 
import java.util.sortedmap; 
import java.util.treemap; 
 
import javax.servlet.servletexception; 
import javax.servlet.annotation.webservlet; 
import javax.servlet.http.httpservlet; 
import javax.servlet.http.httpservletrequest; 
import javax.servlet.http.httpservletresponse; 
 
import org.apache.log4j.logger; 
 
import com.wqy.util.httputil; 
import com.wqy.util.paycommonutil; 
import com.wqy.util.payconfigutil; 
 
/** 
 * servlet implementation class notify1 
 */ 
@webservlet("/notify1") 
public class notify1 extends httpservlet { 
 private static final long serialversionuid = 1l; 
 private static logger logger = logger.getlogger(notify1.class); 
 
 /** 
 * @see httpservlet#httpservlet() 
 */ 
 public notify1() { 
 super(); 
 // todo auto-generated constructor stub 
 } 
 
 /** 
 * @see httpservlet#doget(httpservletrequest request, httpservletresponse 
 * response) 
 */ 
 protected void doget(httpservletrequest request, httpservletresponse response) 
  throws servletexception, ioexception { 
 // todo auto-generated method stub 
 
 // 读取xml 
 inputstream inputstream; 
 stringbuffer sb = new stringbuffer(); 
 inputstream = request.getinputstream(); 
 string s; 
 bufferedreader in = new bufferedreader(new inputstreamreader(inputstream, "utf-8")); 
 while ((s = in.readline()) != null) { 
  sb.append(s); 
 } 
 in.close(); 
 inputstream.close(); 
  
 sortedmap<object, object> packageparams = paycommonutil.xmlconverttomap(sb.tostring()); 
 logger.info(packageparams); 
  
 // 账号信息 
 string key = payconfigutil.api_key; // key 
  
 string resxml="";//反馈给微信服务器 
 // 验签 
 if (paycommonutil.istenpaysign("utf-8", packageparams, key)) { 
  //appid openid mch_id is_subscribe nonce_str product_id sign 
  
  //统一下单 
  string openid = (string)packageparams.get("openid"); 
  string product_id = (string)packageparams.get("product_id"); 
  //解析product_id,计算价格等 
  
  string out_trade_no = string.valueof(system.currenttimemillis()); // 订单号 
  string order_price = "1"; // 价格 注意:价格的单位是分 
  string body = product_id; // 商品名称 这里设置为product_id 
  string attach = "xxx店"; //附加数据 
  
  string nonce_str0 = paycommonutil.getnonce_str(); 
  
  // 获取发起电脑 ip 
  string spbill_create_ip = payconfigutil.create_ip; 
  string trade_type = "native"; 
  
  
  sortedmap<object,object> unifiedparams = new treemap<object,object>(); 
  unifiedparams.put("appid", payconfigutil.app_id); // 必须 
  unifiedparams.put("mch_id", payconfigutil.mch_id); // 必须 
  unifiedparams.put("out_trade_no", out_trade_no); // 必须 
  unifiedparams.put("product_id", product_id); 
  unifiedparams.put("body", body); // 必须 
  unifiedparams.put("attach", attach); 
  unifiedparams.put("total_fee", order_price); // 必须 
  unifiedparams.put("nonce_str", nonce_str0); // 必须 
  unifiedparams.put("spbill_create_ip", spbill_create_ip); // 必须 
  unifiedparams.put("trade_type", trade_type); // 必须 
  unifiedparams.put("openid", openid); 
  unifiedparams.put("notify_url", payconfigutil.notify_url);//异步通知url 
  
  string sign0 = paycommonutil.createsign("utf-8", unifiedparams,key); 
  unifiedparams.put("sign", sign0); //签名 
  
  string requestxml = paycommonutil.getrequestxml(unifiedparams); 
  logger.info(requestxml); 
  //统一下单接口 
  string rxml = httputil.postdata(payconfigutil.ufdoder_url, requestxml); 
  
  //统一下单响应 
  sortedmap<object, object> reparams = paycommonutil.xmlconverttomap(rxml); 
  logger.info(reparams); 
  
  //验签 
  if (paycommonutil.istenpaysign("utf-8", reparams, key)) { 
  // 统一下单返回的参数 
  string prepay_id = (string)reparams.get("prepay_id");//交易会话标识 2小时内有效 
   
  string nonce_str1 = paycommonutil.getnonce_str(); 
   
  sortedmap<object,object> resparams = new treemap<object,object>(); 
  resparams.put("return_code", "success"); // 必须 
  resparams.put("return_msg", "ok"); 
  resparams.put("appid", payconfigutil.app_id); // 必须 
  resparams.put("mch_id", payconfigutil.mch_id); 
  resparams.put("nonce_str", nonce_str1); // 必须 
  resparams.put("prepay_id", prepay_id); // 必须 
  resparams.put("result_code", "success"); // 必须 
  resparams.put("err_code_des", "ok"); 
   
  string sign1 = paycommonutil.createsign("utf-8", resparams,key); 
  resparams.put("sign", sign1); //签名 
   
  resxml = paycommonutil.getrequestxml(resparams); 
  logger.info(resxml); 
   
  }else{ 
  logger.info("签名验证错误"); 
  resxml = "<xml>" + "<return_code><![cdata[fail]]></return_code>" 
   + "<return_msg><![cdata[签名验证错误]]></return_msg>" + "</xml> "; 
  } 
  
 }else{ 
  logger.info("签名验证错误"); 
  resxml = "<xml>" + "<return_code><![cdata[fail]]></return_code>" 
   + "<return_msg><![cdata[签名验证错误]]></return_msg>" + "</xml> "; 
 } 
 
 //------------------------------ 
 //处理业务完毕 
 //------------------------------ 
 bufferedoutputstream out = new bufferedoutputstream( 
  response.getoutputstream()); 
 out.write(resxml.getbytes()); 
 out.flush(); 
 out.close(); 
  
 } 
 
 /** 
 * @see httpservlet#dopost(httpservletrequest request, httpservletresponse 
 * response) 
 */ 
 protected void dopost(httpservletrequest request, httpservletresponse response) 
  throws servletexception, ioexception { 
 // todo auto-generated method stub 
 doget(request, response); 
 } 
 
} 

至此,用户的微信单就会显示出要支付的金额及商品描述等,接下来就是等待客户完成支付。

2.3、异步通知url接口

当用户在微信上完成支付操作后,微信服务器就会异步通知这个接口,给我们发送最终的支付结果,以便我们核实订单进行发货等操作,注意这个接口和模式二的开发是一模一样的。大概流程如下:

1)、接收微信服务器发送过来的参数,对参数进行签名校验;

2)、取出参数result_code、订单号out_trade_no、订单金额total_fee及其他业务相关的参数,具体参数可参照官方文档支付结果通用通知的通知参数;

3)、处理业务,如校验订单号及订单金额、修改订单状态等;

4)、准备好相关参数(return_code和return_msg),应答微信服务器。

注意,如果微信收到商户的应答不是成功或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功。 (通知频率为15/15/30/180/1800/1800/1800/1800/3600,单位:秒)

package com.wqy; 
 
import java.io.bufferedoutputstream; 
import java.io.bufferedreader; 
import java.io.ioexception; 
import java.io.inputstream; 
import java.io.inputstreamreader; 
import java.util.sortedmap; 
 
import javax.servlet.servletexception; 
import javax.servlet.annotation.webservlet; 
import javax.servlet.http.httpservlet; 
import javax.servlet.http.httpservletrequest; 
import javax.servlet.http.httpservletresponse; 
 
import org.apache.log4j.logger; 
 
import com.wqy.util.paycommonutil; 
import com.wqy.util.payconfigutil; 
 
/** 
 * servlet implementation class re_notify 
 */ 
@webservlet("/re_notify") 
public class re_notify extends httpservlet { 
 private static final long serialversionuid = 1l; 
 private static logger logger = logger.getlogger(re_notify.class); 
 
 /** 
 * @see httpservlet#httpservlet() 
 */ 
 public re_notify() { 
 super(); 
 // todo auto-generated constructor stub 
 } 
 
 /** 
 * @see httpservlet#doget(httpservletrequest request, httpservletresponse 
 * response) 
 */ 
 protected void doget(httpservletrequest request, httpservletresponse response) 
  throws servletexception, ioexception { 
 // todo auto-generated method stub 
 // 读取参数 
 inputstream inputstream; 
 stringbuffer sb = new stringbuffer(); 
 inputstream = request.getinputstream(); 
 string s; 
 bufferedreader in = new bufferedreader(new inputstreamreader(inputstream, "utf-8")); 
 while ((s = in.readline()) != null) { 
  sb.append(s); 
 } 
 in.close(); 
 inputstream.close(); 
 
 sortedmap<object, object> packageparams = paycommonutil.xmlconverttomap(sb.tostring()); 
 logger.info(packageparams); 
 
 // 账号信息 
 string key = payconfigutil.api_key; // key 
 
 string resxml = ""; // 反馈给微信服务器 
 // 判断签名是否正确 
 if (paycommonutil.istenpaysign("utf-8", packageparams, key)) { 
  // ------------------------------ 
  // 处理业务开始 
  // ------------------------------ 
  if ("success".equals((string) packageparams.get("result_code"))) { 
  // 这里是支付成功 
  ////////// 执行自己的业务逻辑//////////////// 
  string mch_id = (string) packageparams.get("mch_id"); 
  string openid = (string) packageparams.get("openid"); 
  string is_subscribe = (string) packageparams.get("is_subscribe"); 
  string out_trade_no = (string) packageparams.get("out_trade_no"); 
 
  string total_fee = (string) packageparams.get("total_fee"); 
 
  logger.info("mch_id:" + mch_id); 
  logger.info("openid:" + openid); 
  logger.info("is_subscribe:" + is_subscribe); 
  logger.info("out_trade_no:" + out_trade_no); 
  logger.info("total_fee:" + total_fee); 
 
  ////////// 执行自己的业务逻辑//////////////// 
 
  logger.info("支付成功"); 
  // 通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了. 
  resxml = "<xml>" + "<return_code><![cdata[success]]></return_code>" 
   + "<return_msg><![cdata[ok]]></return_msg>" + "</xml> "; 
 
  } else { 
  logger.info("支付失败,错误信息:" + packageparams.get("err_code")); 
  resxml = "<xml>" + "<return_code><![cdata[fail]]></return_code>" 
   + "<return_msg><![cdata[报文为空]]></return_msg>" + "</xml> "; 
  } 
 
 } else { 
  logger.info("签名验证错误"); 
  resxml = "<xml>" + "<return_code><![cdata[fail]]></return_code>" 
   + "<return_msg><![cdata[签名验证错误]]></return_msg>" + "</xml> "; 
 } 
 
 // ------------------------------ 
 // 处理业务完毕 
 // ------------------------------ 
 bufferedoutputstream out = new bufferedoutputstream(response.getoutputstream()); 
 out.write(resxml.getbytes()); 
 out.flush(); 
 out.close(); 
 } 
 
 /** 
 * @see httpservlet#dopost(httpservletrequest request, httpservletresponse 
 * response) 
 */ 
 protected void dopost(httpservletrequest request, httpservletresponse response) 
  throws servletexception, ioexception { 
 // todo auto-generated method stub 
 doget(request, response); 
 } 
 
} 

三、测试结果

3.1、生成的支付二维码链接

java微信扫码支付模式一线下支付功能实现

3.2、支付回调url接口接收到的参数

java微信扫码支付模式一线下支付功能实现

3.3、发起统一下单请求参数

java微信扫码支付模式一线下支付功能实现

3.4、统一下单返回参数

java微信扫码支付模式一线下支付功能实现

3.5、支付回调url接口最终的响应参数

java微信扫码支付模式一线下支付功能实现

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。