@Author wangwangjie
转载请标明出处~~~
1. 微信扫码支付快速接入
微信支付接入网址:https://pay.weixin.qq.com/index.php/partner/public/home
1. 与支付宝类似,微信扫码支付开发也需要进行申请,审核通过,微信会下发一个公众号appID与一个商户号mchID。
2. 生成一个32位的随机密码作为微信的秘钥,然后登陆微信公众号商户平台(就是上面的链接)设置API密钥。例如:057B07F6A839420C8110D60F728CB92
3. 有了appid,mchID,及key就可以进行开发了。
微信支付开发者文档链接:https://pay.weixin.qq.com/wiki/doc/api/index.html
3.1 下载微信支付的sdk:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=11_1,在上面开发者文档中都能找到.
将sdk加入到自己的项目中。
3.2 下面就可以参照sdk中的demo进行开发
这里主要列出支付下单的流程及代码:
微信支付业务流程时序图:
业务流程说明:
(1)商户后台系统根据用户选购的商品生成订单。
(2)用户确认支付后调用微信支付【统一下单API】生成预支付交易;
(3)微信支付系统收到请求后生成预支付交易单,并返回交易会话的二维码链接code_url。
(4)商户后台系统根据返回的code_url生成二维码。
(5)用户打开微信“扫一扫”扫描二维码,微信客户端将扫码内容发送到微信支付系统。
(6)微信支付系统收到客户端请求,验证链接有效性后发起用户支付,要求用户授权。
(7)用户在微信客户端输入密码,确认支付后,微信客户端提交授权。
(8)微信支付系统根据用户授权完成支付交易。
(9)微信支付系统完成支付交易后给微信客户端返回交易结果,并将交易结果通过短信、微信消息提示用户。微信客户端展示支付交易结果页面。
(10)微信支付系统通过发送异步消息通知商户后台系统支付结果。商户后台系统需回复接收情况,通知微信后台系统不再发送该单的支付通知。
(11)未收到支付通知的情况,商户后台系统调用【查询订单API】。
(12)商户确认订单已支付后给用户发货。
-------------------------------------------------------------------------------------------------------------
具体代码:
application.properties:
#微信配置项
wx_appID=wx123456789
wx_mchID=xxxxxxx
#微信支付秘钥,API根据key默认以MD5生成sign,预创建成功返回return_code为success时以key与默认MD5校验key
wx_key=xxxxxxxxxxxxxxxxxxxxxxxxxx(32位随机数密钥)
wx_notify_url=http://xx.com.cn/pay/wxNotify
wx_spbill_create_ip=127.0.0.1
#微信支付请求的主域名
wx_domain=api.mch.weixin.qq.com
#该域名是否为主域名
wx_primaryDomain=true
自定义WXConfig继承WXPayConfig:
@Component
public class WXconfig extends WXPayConfig {
@Value("${wx_appID}")
private String appID;
@Value("${wx_mchID}")
private String mchID;
@Value("${wx_key}")
private String key;
//这里要设置domain和primaryDomain,不设置跑起来会报空指针,并且对其源码里面的实现要做一部分变动,后面再详细说明
@Value("${wx_domain}")
public String domain;// 支付请求的域名
@Value("${wx_primaryDomain}")
private boolean primaryDomain;// 该支付请求域名是否是主域名
@Override
protected String getAppID() {
// TODO Auto-generated method stub
return appID;
}
@Override
protected String getMchID() {
// TODO Auto-generated method stub
return mchID;
}
@Override
protected String getKey() {
// TODO Auto-generated method stub
return key;
}
public String getDomain() {
return domain;
}
public boolean isPrimaryDomain() {
return primaryDomain;
}
@Override
protected InputStream getCertStream() {
// TODO Auto-generated method stub
return null;
}
@Override
protected IWXPayDomain getWXPayDomain() {
return new WXPayDomain();
}
}
@Autowired
private WXconfig wxconfig;
public DreamResponse unifiedOrder(IPayDTO dto) {
DreamResponse res = new DreamResponse();
CommonDTO real = (CommonDTO) dto;
if (real.getEpcs() == null || real.getEpcs().size() == 0) {
res.setMsg("标签列表不能为空!");
res.setStatus(DreamStatus.FAIL);
return res;
}
if (StringUtils.isEmpty(real.getMac())) {
res.setMsg("设备地址不能为空!");
res.setStatus(DreamStatus.FAIL);
return res;
}
Map<String, String> resmap = new HashMap<String, String>();
===============================================微信支付部分================================================
try {
//构建WXPay的时候调用其构造方法,会将微信配置项wxconfig及签名类型sign_type给赋值,默认本地签名类型sign_type为:HMACSHA256
WXPay wxpay = new WXPay(wxconfig);
//Map集合用于存储请求参数执行微信支付,下面的value根据自己的业务设定,这里就不加以更改了
Map<String, String> data = new HashMap<String, String>();
StockVO stockVo = priceSearchService.getSKUPrice(real);//这里是商品集合
data.put("body", stockVo.getListStock().get(0).getWarehouseName());
data.put("out_trade_no", OrderUtil.generateOrderNo());
data.put("device_info", real.getMac());
data.put("fee_type", "CNY");
// 注意:这里去除小数点,total_fee不支持小数点
Double totalAmt = stockVo.getTotalAmt() * 100;// 订单总金额,单位为分
String temp = totalAmt.toString();
temp = temp.substring(0, temp.indexOf('.'));
data.put("total_fee", temp);
data.put("spbill_create_ip", wxbean.getSpbill_create_ip());
data.put("notify_url", wxbean.getNotify_url());
data.put("trade_type", "NATIVE"); // 此处指定为扫码支付
// data.put("attach",wxbean.getOpenid());
data.put("product_id", OrderUtil.generateOrderNo("", 9));// 生成一个id
data.put("time_start", DateUtils.date2String(new Date(), "yyyyMMddHHmmss"));
data.put("limit_pay", "no_credit");
//data.put(WXPayConstants.FIELD_SIGN_TYPE, WXPayConstants.HMACSHA256);
========================================微信支付请求参数封装结束=========================================
Map<String, Object> mapdto = new HashMap<String, Object>();
mapdto.put("epclist", real.getEpcs());
mapdto.put("out_trade_no", data.get("out_trade_no"));
mapdto.put("device_info", real.getMac());
mapdto.put("payment_type", DreamStatus.WX);
mapdto.put("stockVo", stockVo);
// 记录订单流水
orderService.add(mapdto);
========================================发起微信支付===================================================
logger.info("****微信下订单入参:" + new Gson().toJson(data));
resmap = wxpay.unifiedOrder(data);
resmap.put("out_trade_no", data.get("out_trade_no"));
logger.info("****微信下订单返回结果:" + new Gson().toJson(resmap));
if (DreamStatus.SUCCESS.equals(resmap.get("return_code"))
&& DreamStatus.SUCCESS.equals(resmap.get("result_code"))) {
res.setMsg("微信支付下订单成功");
res.setStatus(DreamStatus.SUCCESS);
......具体业务实现
} else {
res.setExtData(resmap);
res.setMsg("微信支付下订单失败");
res.setStatus(DreamStatus.FAIL);
}
} catch (Exception e) {
e.printStackTrace();
logger.warn("微信支付下订单失败", e);
res.setExtData(resmap);
res.setMsg("下单失败");
res.setStatus(DreamStatus.FAIL);
}
return res;
}
以上,domain与primaryDomain是在程序运行起来发现问题后加入的,查看源码发现,必须给定这两个值。
并且在继承WXPayConfig的WxConfig的类中加入重写的方法:(对于getWXPayDomain还得注意修改其实现)
public String getDomain() {
return domain;
}
public boolean isPrimaryDomain() {
return primaryDomain;
}
@Override
protected InputStream getCertStream() {
// TODO Auto-generated method stub
return null;
}
@Override
protected IWXPayDomain getWXPayDomain() {
return new WXPayDomain();
}
跟入源码发现,wxpay.unifiedOrder(data)底层将我作为构造参数的config内的配置项appid,mchid,key都设置入data了,并且会生成sign存入data,
并且我们无需制定访问路径,调用对应的方法,底层会指定调用的接口。
继续往下走,我们发现这里调用了我们在new WXPay(config)中config.getWXPayDomain().getDomain(config)方法。如果不加以配置就会报空指针。
继续跟入这个get方法,我们在上面return new WXPayDomain();至于为什么要这样,跟入getDomain(config)研究就可以得到。
到了这里,具体的原因就不叙述了,看代码体会,我们需要自定义一个类来继承IWXPayDomain重新其getDomain()方法。
public class WXPayDomain implements IWXPayDomain {
@Override
public void report(String domain, long elapsedTimeMillis, Exception ex) {
// TODO Auto-generated method stub
}
@Override
public DomainInfo getDomain(WXPayConfig config) {
WXconfig wxConfig = (WXconfig)config;
return new DomainInfo(wxConfig.getDomain(), wxConfig.isPrimaryDomain());
}
}
至此,微信扫码支付开发就完成了,最后domain这里是个坑,需要看源码自己去体会。
总结:
1. 自定义WXConfig继承WXPayConfig,读取微信配置项
2. 给定支付接口所需的请求参数,用wxpay.unifiedOrder(data)进行支付
3. 对于domain要做一下几个地方的改动。
1. 配置项的改动:这里为什么这么配通过看源码可以得到
domain=api.mch.weixin.qq.com
primaryDomain=true
2. WXConfig中读取domain,primaryDomain配置项,并重写getDomain(),isPrimaryDomain(),getWXPayDomain()方法
3. 自定义类(我这里叫WXPayDomain继承IWXPayDomain),重写getDomain()方法
2. 微信扫码支付成功回调接口开发
与支付宝支付成功后的异步回调一样,需要登录商户平台找到开发配置,设置notify_url回调ip即可。这里要注意的是:微信支付的notify_url只支持域名。
如:http://wwj.com.cn/pay/wxNotify,不像支付宝ip也可以
注意点:
1. 微信支付回调返回的xml数据,并且是以流的方式返回的,因此我们收取数据的时候也必须以流的方式获取:
2. 微信支付返回的xml数据中没有sign_type这一字段,而在本地验签时,我们需要采用HMACSHA256(与下单时一致)进行验签,因此要手动设置到map中去进行验签。
跟入源码发现,map有sign_type为我们需要采用HMACSHA256时验签类型才为HMACSHA256,否则就是MD5.
但如果只是这样处理,最后会出现验签失败,之前支付宝支付也说明了验签的原理,是通过私钥与request中的参数进行签名后和原本的签名sign进行对比,
如今你手动多加了一个字段,那么签名后肯定不同,因此要在源码中,sign_type确定后,将sign_type再重新移除掉即可(这是我想到的做法,可能不是妥当)
微信支付成功的回调代码:
public String notify() {
Map<String, Object> map = new HashMap<String, Object>();
Map<String, String> res = new HashMap<String, String>();
=======================================微信支付回调======================================
String xml = "";
String notifyXml = "";
// 微信支付结果参数
Map<String, String> paramMap = null;
WxPayDTO real = null;
try {
// 获取微信支付成功后回调POST反馈的信息
logger.info("微信支付回调获取数据开始...");
//以流的方式读取Request中的数据
notifyXml = RequestUtil.getRequestBodyByReader(request);
logger.debug("微信支付回调参数xml格式:" + notifyXml);
if (StringUtils.isEmpty(notifyXml)) {
logger.error("微信支付回调参数xml为空");
}
//下面这一段是将获取到的xml数据封装到实体类中,实体类WxPayDTO中对应的字段与官方API支付成功后的响应参数必须一致
paramMap = WXPayUtil.xmlToMap(notifyXml);
String paramJson = new Gson().toJson(paramMap);
logger.info("微信支付回调参数json格式" + paramJson);
real = new Gson().fromJson(paramJson, WxPayDTO.class);
WXPay wxpay = new WXPay(wxconfig);
logger.info("微信通知成功:" + paramJson);
if (isPay(real.getOut_trade_no()) || null == real) {// 已通知过,无参数无需通知
res.put("return_code", DreamStatus.SUCCESS);
res.put("return_msg", "OK");
xml = WXPayUtil.mapToXml(res);
return xml;
}
//指定验签方式与下单时的提交到微信服务端的sign_type一致,为HMACSHA256
paramMap.put(WXPayConstants.FIELD_SIGN_TYPE, WXPayConstants.HMACSHA256);
if (wxpay.isPayResultNotifySignatureValid(paramMap)) {
=========================微信回调基本代码到此结束,下面是具体业务的实现====================
// 签名正确
logger.info("微信验签成功:" + new Gson().toJson(paramMap));
if (DreamStatus.SUCCESS.equals(real.getResult_code())
&& DreamStatus.SUCCESS.equals(real.getReturn_code())) {
//更新订单状态
map.put("out_trade_no", real.getOut_trade_no());
map.put("status", DreamStatus.SUCCESS);
map.put("type", MessageType.PAY_OVER);
orderService.update(map);
res.put("return_code", DreamStatus.SUCCESS);
res.put("return_msg", "OK");
} else {
res.put("return_code", DreamStatus.FAIL);
res.put("return_msg", real.getErr_code_des());
}
} else {
// 签名失败
logger.info("微信验签失败:" + new Gson().toJson(paramMap));
map.put("out_trade_no", real.getOut_trade_no());
map.put("status", DreamStatus.FAIL);
orderService.update(map);
res.put("return_code", DreamStatus.FAIL);
res.put("return_msg", "fail");
}
} catch (Exception e) {
e.printStackTrace();
res.put("return_code", DreamStatus.FAIL);
res.put("return_msg", "fail");
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
logger.error("微信消息通知异常", e);
}
try {
xml = WXPayUtil.mapToXml(res);
} catch (Exception e) {
e.printStackTrace();
}
return xml;
}
/**
* @Title: getRequestBodyByReader
* @Description: TODO(获取微信回调函数参数,返回xml)
* @param: @param request
* @param: @return
* @param: @throws IOException
* @return: String
* @throws
*/
public static String getRequestBodyByReader(HttpServletRequest request) throws IOException {
String tempLine;
String result = "";
try {
if(request != null) {
while ((tempLine = request.getReader().readLine()) != null) {
result += tempLine;
}
}
} catch (IOException e) {
e.printStackTrace();
throw e;
} finally {
try {
if(request.getReader() != null) {
request.getReader().close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
用于封装Request返回参数的实体类,与API响应参数保持一致:
public class WxPayDTO implements IPayDTO, Serializable {
private String device_info;// 设备号
private String openid;// 用户标识
private String appid;// 公众账号ID
private String mch_id;// 商户号
private String nonce_str;// 随机字符串
private String sign;// 签名
private String sign_type;// 签名类型
private String result_code;// 业务结果
private String err_code;// 错误代码
private String err_code_des;// 错误代码描述
private String is_subscribe;// 是否关注公众账号
private String trade_type;// 交易类型
private String bank_type;// 付款银行
private int total_fee;// 订单金额
private int settlement_total_fee;// 应结订单金额
private int cash_fee;// 现金支付金额
private String transaction_id;// 微信支付订单号
private String out_trade_no;// 商户订单号
private String time_end;// 支付完成时间
private String return_code;// 返回状态码
private String return_msg;// 返回信息
............
}
跟入wxpay.isPayResultNotifySignatureValid(paramMap)验签方法,修改isSignatureValid()方法,移除map中的sign_type字段
/**
* 判断签名是否正确,必须包含sign字段,否则返回false。
*
* @param data
* Map类型数据
* @param key
* API密钥
* @param signType
* 签名方式
* @return 签名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(Map<String, String> data, String key, SignType signType) throws Exception {
if (!data.containsKey(WXPayConstants.FIELD_SIGN)) {
return false;
}
String sign = data.get(WXPayConstants.FIELD_SIGN);
logger.info("原签名为:" + sign + ",本地验签类型为:" + signType);
//为了保证签名类型是HMACSHA256手动设置了sign_type,为了保证验签通过,移除sign_type
data.remove(WXPayConstants.FIELD_SIGN_TYPE);
String generateSignature = generateSignature(data, key, signType);
logger.info("验签后>>>" + generateSignature);
return generateSignature.equals(sign);
}
到此,微信支付成功回调接口开发完成,开发途中会遇到很多坑,慢慢解决就好了~~~