下面我们先来了解一下在线支付的方式及其各自的优缺点
在线支付:
和银行对接
和第三方支付对接
银行对接 优点:免费,无需支付任何费用
缺点:接口参数较多,开发成本及维护成本高
第三方支付 优点:第三方提供接口,开发成本及维护成本低
缺点:接口参数较多
1.1、有关配置参数
还是之前那四样,APP_ID和APP_SECRET可以在公众平台找着,MCH_ID和API_KEY则在商户平台找到,特别是API_KEY要在商户平台设置好,这个东东关系到参数校验的正确与否,所以一定要设置正确。扫码支付模式一其实与扫码支付模式二类似,实际只会用到APP_ID、MCH_ID和API_KEY,其他的都不用。模式一的官方文档地址在这:
https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_4
1.2、有关概念
在这里我想先修正一个概念,在之前模式二开发过程中我曾提到了一个“支付回调地址”这样的概念,其作用实际就是客户在扫描完成支付后,微信服务器要访问我们提供的这个地址,给我们发送支付结果,以便我们核实订单进行发货,这是其他支付工具比较普遍的概念和叫法。不过后来我翻了一下微信官网的文档,发现在模式一开发中,他们把这个叫做“异步通知url”而不是什么“支付回调地址”,但本质这指的是一个意思。可是为什么我要在这提到这个东东呢?这是因为在模式一中,实际上还有另外一个所谓的“支付回调”称之为“扫码支付回调URL”,这东东与上面的“异步通知url”可就不一样了,简单理解可以认为是咱们的服务器上一个用来辅助完成下单的接口。模式一的开发同时需要“扫码支付回调URL”与“异步通知url”两个接口配合才能完成,所以这里大家要辨别好了。
“异步通知url”在调用统一下单接口时进行设置,可以动态设置,只要这个接口按照有关规则接收参数响应参数即可。而“扫码支付回调URL”则较为固定,它在微信公众平台设置,设置后需要10分钟左右才能生效,大家登录微信公众平台后,选择微信支付,在开发配置选项卡下面就可以找着:
这里咱们要设置一个自己服务器的地址(再说一遍公网地址,就是让微信服务器能找着你)。
1.3、开发环境
我这里以最基本的Servlet 3.0作为示例环境。关于引用第三方的jar包,相比较于模式二开发,除了用到了xml操作的jdom,以外就一个Google ZXing的二维码包和log4j包。如下图:
为了方便调试,建议各位先在这个环境下调通了再移植到真实项目当中去。
二、开发实战
在动手之前,我建议大家先去官方文档那好好看看那个时序图,理解了那个时序图,写代码也就不是什么难事了,当然如果看图你没办法理解,也可以结合我下面的代码来试着理解。
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;
@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 ;
public Pay1() {
super ();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String nonce_str = PayCommonUtil.getNonce_str();
long time_stamp = System.currentTimeMillis() / 1000 ;
String product_id = "hd_goodsssss_10" ;
String key = PayConfigUtil.API_KEY;
SortedMap packageParams = new TreeMap();
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);
packageParams.put("sign" , sign);
String str = ToUrlParams(packageParams);
String payurl = "weixin://wxpay/bizpayurl?" + str;
logger.info("payurl:" +payurl);
Map hints=new HashMap();
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) {
e.printStackTrace();
}
}
public String ToUrlParams(SortedMap 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();
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
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;
@WebServlet ( "/Notify1" )
public class Notify1 extends HttpServlet {
private static final long serialVersionUID = 1L;
private static Logger logger = Logger.getLogger(Notify1. class );
public Notify1() {
super ();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
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 packageParams = PayCommonUtil.xmlConvertToMap(sb.toString());
logger.info(packageParams);
String key = PayConfigUtil.API_KEY;
String resXml="" ;
if (PayCommonUtil.isTenpaySign( "UTF-8" , packageParams, key)) {
String openid = (String)packageParams.get("openid" );
String product_id = (String)packageParams.get("product_id" );
String out_trade_no = String.valueOf(System.currentTimeMillis());
String order_price = "1" ;
String body = product_id;
String attach = "XXX店" ;
String nonce_str0 = PayCommonUtil.getNonce_str();
String spbill_create_ip = PayConfigUtil.CREATE_IP;
String trade_type = "NATIVE" ;
SortedMap unifiedParams = new TreeMap();
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);
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 reParams = PayCommonUtil.xmlConvertToMap(rXml);
logger.info(reParams);
if (PayCommonUtil.isTenpaySign( "UTF-8" , reParams, key)) {
String prepay_id = (String)reParams.get("prepay_id" );
String nonce_str1 = PayCommonUtil.getNonce_str();
SortedMap resParams = new TreeMap();
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 = "" + " "
+ " " + " " ;
}
}else {
logger.info("签名验证错误" );
resXml = "" + " "
+ " " + " " ;
}
BufferedOutputStream out = new BufferedOutputStream(
response.getOutputStream());
out.write(resXml.getBytes());
out.flush();
out.close();
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
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,单位:秒)
三、测试结果
3.1、生成的支付二维码链接
3.2、支付回调url接口接收到的参数
3.3、发起统一下单请求参数
3.4、统一下单返回参数
3.5、支付回调url接口最终的响应参数
本文选自CSDN第三方支付,收集比较后,感觉本文较详细。觉得对您的学习有帮助的,麻烦添加关注,谢谢!
转载自:http://blog.csdn.net/wangqiuyun/article/details/52679682
源码下载地址:https://github.com/wangqiuyun/wpay