微信支付实战(完整的代码,复制即可用)

news/2025/2/16 7:08:19/

公众号介绍

https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Overview.html
最重要的一点:
公众平台以access_token为接口调用凭据,来调用接口,所有接口的调用需要先获取access_token,access_token在2小时内有效,过期需要重新获取,但1天内获取次数有限,开发者需自行存储,详见获取接口调用凭据(access_token)文档。

公众号配置

开通<网页授权获取用户基本信息>接口

在微信公众号请求用户网页授权之前,开发者需要先到公众平台官网中的“开发 - 接口权限 - 网页服务 - 网页帐号 - 网页授权获取用户基本信息”的配置选项中,修改授权回调域名。请注意,这里填写的是域名(是一个字符串),而不是URL,因此请勿加 http:// 等协议头;。

授权回调域名配置规范为全域名,比如需要网页授权的域名为:www.qq.com,配置以后此域名下面的页面http://www.qq.com/music.html 、 http://www.qq.com/login.html 都可以进行OAuth2.0鉴权。但http://pay.qq.com 、 http://music.qq.com 、 http://qq.com 无法进行OAuth2.0鉴权

开通<获取access_token>接口

开通<获取jsapi_ticket>接口

以上三个是最核心的接口必须开通,其他接口根据需要开通

配置<JS接口安全域名>

先登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”。

备注:登录后可在“开发者中心”查看对应的接口权限。

配置ip白名单

登录微信公众平台–>开发>基本配置>ip白名单

提前将服务器 IP 地址添加到 IP 白名单中,否则将无法调用成功。小程序无需配置 IP 白名单。

微信支付产品介绍

(1)付款码支付
付款码-线下支付,商户展示一个有金额二维码,用户扫描二维码并输入密码进行支付。

(2)JSAPI支付
JSAPI-公众号支付,商户通过公众号调起微信支付页面,支付页面已固定金额,用户输入密码进行支付。
JSAPI-线下支付,商户展示一个无金额二维码,用户扫描二维码并输入金额和密码进行支付。
JSAPI-PC网站支付,商户展示一个无金额二维码,用户扫描二维码并输入金额和密码进行支付。

(3)小程序支付
小程序支付,商户通过小程序调起微信支付页面,支付页面已固定金额,用户输入密码进行支付。

(4)Native支付
Native-线下支付,商户展示一个有金额二维码,用户扫描二维码并输入密码进行支付。
Native-PC网站支付,商户展示一个有金额二维码,用户扫描二维码并输入密码进行支付。

(5)APP支付
在商户自己开发的手机App(Android、IOS)上调起微信支付页面,支付页面已固定金额,用户输入密码进行支付。

(6)刷脸支付
不清楚,如果需要使用自己去查询官方文档。

以上支付只是客户端的不用应用场景,但对后台开发人员来说都一样,因为微信有统一的支付接口:https://api.mch.weixin.qq.com/pay/unifiedorder
一般来说我们选JSAP支付就行了,
JSAP支付和Native支付的区别就是JSAPI展示的是无金额二维码,Native展示的是有金额二维码。

微信支付接入流程

(1)获取商户号
提交资料=>签署协议=>获取商户号

(2)获取 APPID
步骤:注册服务号=>服务号认证=>获取APPID=>绑定商户号

(3)获取API秘钥
登录商户平台=>选择账户中心=>安全中心=>API安全=>设置API密钥

(4)获取APlv3秘钥
登录商户平台=>选择账户中心=>安全中心=>API安全=>设置APIV3密钥

(5)申请商户API证书
APIv3版本的所有接口都需要;APIv2版本的高级接口需要(如:退款、企业红包、企业付款等)
登录商户平台=>选择账户中心=>安全中心=>API安全=>申请API证书

下载证书工具=>解压(下载的exe其实是个解压工具)证书工具=>运行证书工具=>选择证书保存路径=>点击申请证书=>填写商户号和商户名称=>下一步=>复制请求字符串=>回到商户平台=>粘贴请求字符串=>输入商户平台密码=>复制证书字符串=>回到证书工具=>下一步=>粘贴证书字符串=>下一步=>查看证书文件夹=>解压zip=>完工。

解压后apiclient_cert.p12、apiclient_cert.pem、apiclient_key.pem就是我们的商户API证书,其中apiclient_cert.p12是后台开发调用退款接口需要的文件,其他两个文件我没发现有地方需要使用它们。

附三个文件的介绍:
证书pkcs12格式(apiclient_cert.p12):包含了私钥信息的证书文件,为p12(pfx)格式,由微信支付签发给您用来标识和界定您的身份。部分安全性要求较高的API需要使用该证书来确认您的调用身份。
证书pem格式(apiclient_cert.pem):从apiclient_cert.p12中导出证书部分的文件,为pem格式,证书序列号也可以从这个文件里解析得到
证书密钥pem格式(apiclient_key.pem):从apiclient_cert.p12中导出密钥部分的文件。

(6)获取微信平台证书
可以预先下载,也可以通过编程的方式获取。

商户号配置

配置支付目录

支付授权目录说明:
1、商户最后请求拉起微信支付收银台的页面地址我们称之为“支付目录”,例如:https://www.weixin.com/pay.php。
2、商户实际的支付目录必须和在微信支付商户平台设置的一致,否则会报错“当前页面的URL未注册:”

支付授权目录设置说明:
登录微信支付商户平台(pay.weixin.qq.com)–>产品中心–>开发配置,设置后一般5分钟内生效。

支付授权目录校验规则说明:
1、如果支付授权目录设置为顶级域名(例如:https://www.weixin.com/ ),那么只校验顶级域名,不校验后缀;
2、如果支付授权目录设置为多级目录,就会进行全匹配,例如设置支付授权目录为https://www.weixin.com/abc/123/,则实际请求页面目录不能为https://www.weixin.com/abc/,也不能为https://www.weixin.com/abc/123/pay/,必须为https://www.weixin.com/abc/123/

开发介绍

纯手写,没有使用微信提供的JavaSDK(因为也没几句代码)。
使用JSAPI-公众号支付支付方式进行开发。
项目技术选型:
非前后端分离的传统SSM架构,非微服务,非分布式,单服务器部署。

支付流程

假设我们的公众号已经开发好了,上面有个商品,用户点击购买进行下单,OK,这是前端支付事件的最源头。
从这个最源头出发,实际上的流程是:

用户点击下单=>调用后台系统生成订单=>后台调用微信统一支付接口并获取prepay_id=>返回公众号页面,使用JSAPI调起微信支付页面,并传入prepay_id=>用户输入支付密码完成支付=>微信支付成功回调后台系统=>后台系统修改订单的支付状态。

OpenID

OpenID是微信公众号中为了鉴别用户而设计的唯一标识,每个用户针对每个公众号会产生一个安全的OpenID。

我们后台如果要设计一个微信用户表用来储存微信用户信息,那这个表和我们系统原来的用户表一定是多对一的,因为我们可能会有多个公众号,这样一来一个用户就有多个OpenID。

一般来说,在用户第一次访问公众号时(即从公众号菜单点进来的)获取OpenID,获取一次后返回给公众号前端页面,前端每次调用后台接口时,都传入这个OpenID,后台接口就知道是哪个用户在操作了,相当于PC网站的Session。

OpenID一旦拿到手,需要在页面上和后台之间反复传输已避免丢失(因为你不保存在前端页面,就需要后台接口每次都需要调用微信以获取OpenID,很浪费时间的,而且OpenID又不会变化)。

不想传来传去的话,可以将OpenID存到session中,也行,但是session一失效又得重新获取,而且重新获取时重定向不知道原页面了,只能重定向到首页。只能说和页面传值的方式对比,各有利弊。
以前的微信公众号不能使用session,现在的微信公众号是可以使用session的,和pc浏览器的session无异。

看这里,很重要:
微信获取OpenID需要通过重定向https://open.weixin.qq.com/connect/oauth2/authorize这个链接,然后再定义一个接口接收微信的回调请求,然后在回调接口里再去调用https://api.weixin.qq.com/sns/oauth2/access_token接口获取OpenID。

获取OpenID的微信接口1:

定义goods/menu接口(公众号菜单配的链接对应的后台接口):

@Controller
@RequestMapping("/goods")
public class GoodsController extends WechatController {// 用户通过微信内置浏览器访问此接口// 如果用户在微信客户端中访问第三方网页,公众号可以通过微信网页授权机制,来获取用户基本信息(目前我们只需要openid),进而实现业务逻辑。@RequestMapping(value = "/menu", method = RequestMethod.GET)public String menu(HttpServletRequest request, HttpServletResponse response) throws Exception {if (Utils.isEmptyTrim(request.getSession().getAttribute("openId"))) {//如果没有获取openId,那么去获取完openId再来String redirectUrl="http://www.xuexibisai.com/wechat/code/1";WechatUtil.redirectGetOpenId(request, response, redirectUrl);return null;}else {return "forward:/goods/list.do";//转发至goods/list.do}}
}
public class WechatUtil {public static final String redirect_openId_url = "https://open.weixin.qq.com/connect/oauth2/authorize?";// 重定向获取openidpublic static void redirectGetOpenId(HttpServletRequest request, HttpServletResponse response, String url) {try {LOG.debug("微信认证后待访问url:" + url);// scope参数只有两种应用授权作用域,snsapi_base// (不弹出授权页面,直接跳转,只能获取用户openid),snsapi_userinfo// (弹出授权页面,可通过openid拿到昵称、性别、所在地。并且, 即使在未关注的情况下,只要用户授权,也能获取其信息 )// state参数是开发者自定义的参数,重定向后会带上state参数,开发者可以填写a-zA-Z0-9的参数值,最多128字节response.sendRedirect(redirect_openId_url +"appid=" + WechatUtil.appId + "&redirect_uri=" + URLEncoder.encode(url, "utf-8")+ "&response_type=code&scope=snsapi_base&state=1#wechat_redirect");// 如果用户同意授权,页面将跳转至 redirect_uri/?code=CODE&state=STATE。} catch (Exception e) {LOG.error("异常", e);}}
}

获取OpenID的微信接口2:

定义wechat/code接口:

@Controller
@RequestMapping("/wechat")
public class WechatController{private static final Logger LOG = LoggerFactory.getLogger(WechatController.class);@Autowiredprivate WxUserInfoMapper wxUserInfoMapper;// 经过微信重定向后,会返回code,然后根据code获取openid,并将openid返回给页面@RequestMapping(value = "/code/{menuType}", method = RequestMethod.GET)public ModelAndView code(HttpServletRequest request, HttpServletResponse response, String code,@PathVariable(value="menuType") Integer menuType) throws Exception {//通过code查openidString openId = WechatUtil.getOpenId(code);if (Utils.isEmptyTrim(openId)) {return WebUtil.getErrorPage("获取openId失败", LOG, "");}// 通过openid查用户信息JSONObject jsonObject = WechatUtil.getUserInfo(openId,0);LOG.debug("最新的微信用户信息:" + jsonObject.toJSONString());if (Utils.isEmpty(jsonObject, "openid")) {return WebUtil.getErrorPage("查询微信用户信息失败", LOG, "");}// 查数据库用户信息WxUserInfo wxUserInfo = wxUserInfoMapper.selectByOpenId(openId);if (!Utils.isEmpty(jsonObject, "openid")) {if (wxUserInfo == null) {// 如果数据库没有就插入用户信息wxUserInfo = new WxUserInfo();wxUserInfo.setAppId(WechatUtil.appId);wxUserInfo.setOpenid(openId);wxUserInfo.setCreateTime(new Date());}wxUserInfo.setNickname(jsonObject.getString("nickname"));wxUserInfo.setHeadimgurl(jsonObject.getString("headimgurl"));wxUserInfo.setSex(jsonObject.getInteger("sex"));wxUserInfo.setCity(jsonObject.getString("city"));wxUserInfo.setProvince(jsonObject.getString("province"));wxUserInfo.setCountry(jsonObject.getString("country"));wxUserInfo.setSubscribe(jsonObject.getInteger("subscribe"));wxUserInfo.setSubscribeTime(jsonObject.getLong("subscribe_time"));wxUserInfo.setGroupid(jsonObject.getInteger("groupid"));wxUserInfo.setRemark(jsonObject.getString("remark"));if (!Utils.isEmpty(jsonObject.getString("unionid"))) {wxUserInfo.setUnionid(jsonObject.getString("unionid"));}wxUserInfo.setUpdateTime(new Date());if (Utils.isEmpty(wxUserInfo.getUserId())) {wxUserInfoMapper.insertSelective(wxUserInfo);LOG.debug("添加微信用户信息成功");} else {wxUserInfoMapper.updateByPrimaryKeySelective(wxUserInfo);LOG.debug("更新微信用户信息成功");}request.getSession().setAttribute("openId", openId);if (menuType==0) {return new ModelAndView("forward:goods_list");//跳转至商品首页}else if(menuType==1) {return new ModelAndView("forward:user_list");}}return WebUtil.getErrorPage("查询微信用户信息失败", LOG, "");}
}
public class WechatUtil {public static final String get_openId_url = "https://api.weixin.qq.com/sns/oauth2/access_token?";/*** 通过授权令牌获取用户openId*/public static String getOpenId(String code) {String openid = null;StringBuilder url = new StringBuilder();url.append(get_openId_url);url.append("&appid=" + appId);url.append("&secret=" + appSecret);url.append("&code=").append(code);url.append("&grant_type=authorization_code");log.debug("url=" + url.toString());String ret = sendDataHttpsViaGet(url.toString());log.debug("获取openId" + ret);JSONObject obj = JSONObject.parseObject(ret);String errcode = obj.getString("errcode");if (StringUtils.isBlank(errcode)) {openid = obj.getString("openid");}return openid;}
}

假如用户

微信的接口鉴权机制

获取openId其实只是获取用户相关信息,我们要想调用后续的其他接口,还必须获取,access_token和jsapi_ticket。

获取jsapi_ticket之前需要获取access_token,access_token是微信公众号开发所有后台接口都需要传入的参数。

jsapi_ticket

jsapi_ticket是前端js接口声明(wx.config)时所需的信息要用到。

$(function() {$.ajax({url : '${ctx}/goods/sign.do',async : false,data : {'url' : window.location.href},dataType : 'json',type : 'post',success : function(result) {var data = result.data;if (data.appid == null || data.appid == "") {return;}wx.config({debug : false,appId : data.appid,timestamp : data.timestamp,nonceStr : data.nonceStr,signature : data.signature,//这个signature就是jsapi_ticket通过加密解密转化来的。jsApiList : [ "chooseWXPay" ]});}});wx.error(function(res) {alert("页面鉴权失败:"+res);});wx.ready(function() {});
});
@Controller
@RequestMapping("/goods")
public class GoodsController extends WechatController {private static final Logger LOG = LoggerFactory.getLogger(GoodsController.class);@RequestMapping(value = "/sign.do")@ResponseBodypublic Map<String, Object> sign(HttpServletRequest request, HttpServletResponse response, String url) {if (StringUtils.isBlank(url)) {LOG.info("url传递失败");return PortalUtil.fail("传递失败");}Map<String, String> ret = WechatUtil.sign(url);LOG.debug("ret:=" + ret);ret.put("appid", WechatUtil.appId);return PortalUtil.success(ret);}
}
public class WechatUtil {public static Map<String, String> sign(String url) {Map<String, String> ret = new HashMap<String, String>();String nonceStr = createNonceStr();String timestamp = createTimestamp();String string1;String signature = "";// 注意这里参数名必须全部小写,且必须有序string1 = "jsapi_ticket=" + getJsapiTicket() + "&noncestr=" + nonceStr + "&timestamp=" + timestamp + "&url=" + url;try {MessageDigest crypt = MessageDigest.getInstance("SHA-1");crypt.reset();crypt.update(string1.getBytes("UTF-8"));signature = byteToHex(crypt.digest());} catch (NoSuchAlgorithmException e) {e.printStackTrace();} catch (UnsupportedEncodingException e) {e.printStackTrace();}ret.put("url", url);ret.put("nonceStr", nonceStr);ret.put("timestamp", timestamp);ret.put("signature", signature);return ret;}public static final String get_jsapi_ticket_url="https://api.weixin.qq.com/cgi-bin/ticket/getticket?";private static String jsapiTicket;private static long jsapiTicketTime;// 获取凭证时的时间,单位:毫秒数,System.currentTimeMillisprivate static int jsapiTicketExpireTime;// 凭证有效时间,单位:秒private static void clearJsapiTicket() {jsapiTicket = "";jsapiTicketExpireTime = 0;jsapiTicketTime=0;}public static String getJsapiTicket() {if (!Utils.isEmptyTrim(jsapiTicket) && jsapiTicketTime > 0 && jsapiTicketExpireTime > 0) {long extime = jsapiTicketTime + (jsapiTicketExpireTime * 1000);long nowTime = System.currentTimeMillis();if (extime - nowTime > 1000) {// 仍然有效return jsapiTicket;}}try {String url = get_jsapi_ticket_url+"access_token=" + getAccessToken()+ "&type=jsapi";String response = sendDataHttpsViaGet(url);JSONObject json = JSONObject.parseObject(response);log.info(json.toJSONString());String errmsg=json.getString("errmsg");String errcode=json.getString("errcode");if(!errmsg.equals("ok")&&!errcode.equals("0")){return null;}log.debug("获取到新的JsapiTicket:"+json.toJSONString());jsapiTicketExpireTime = json.getIntValue("expires_in");jsapiTicket = json.getString("ticket");jsapiTicketTime=System.currentTimeMillis();} catch (Exception e) {LOG.error("getjsapiTicket-error", e);clearJsapiTicket();}LOG.debug("jsapiTicket===" + jsapiTicket);System.out.println("jsapiTicket===" + jsapiTicket);return jsapiTicket;}
}

access_token

要想获取jsapi_ticket,就要先获取access_token。
不要频繁去调用微信接口获取access_token,每天调用获取access_token接口的次数微信有限制,只需要判断过期时再去获取access_token。

public class WechatUtil {public static final String get_access_token_url="https://api.weixin.qq.com/cgi-bin/token?";private static String accessToken;private static long accessTokenTime;// 获取凭证时的时间,单位:毫秒数,System.currentTimeMillisprivate static int accessTokenExpireTime;// 凭证有效时间,单位:秒private static void clearAccessToken() {accessToken = "";accessTokenExpireTime = 0;accessTokenTime=0;}public static String getAccessToken() {if (!Utils.isEmptyTrim(accessToken) && accessTokenTime > 0 && accessTokenExpireTime > 0) {long extime = accessTokenTime + (accessTokenExpireTime * 1000);long nowTime = System.currentTimeMillis();if (extime - nowTime > 1000) {// 仍然有效return accessToken;}}try {String url = get_access_token_url + "&grant_type=client_credential&appid=" + appId + "&secret="+ appSecret;log.debug("进来url:="+url);String response = sendDataHttpsViaGet(url);if(StringUtils.isBlank(response)){log.debug("response为空:="+response);clearAccessToken();return null;}JSONObject json = JSONObject.parseObject(response);if(json==null){log.debug("json为空:="+json);clearAccessToken();return null;}if(StringUtils.isNotBlank(json.getString("errcode"))){clearAccessToken();return null;}log.debug("获取到新的JsapiTicket:"+json.toJSONString());accessTokenExpireTime = json.getIntValue("expires_in");accessToken = json.getString("access_token");accessTokenTime=System.currentTimeMillis();} catch (Exception e) {LOG.error("getAccessToken-error", e);clearAccessToken();}LOG.debug("accessToken===" + accessToken);System.out.println("accessToken===" + accessToken);return accessToken;}
}

准备好固定的资源

	//公众号appidpublic static final String appId = "wxf546a5afce347110";//公众号appSecretpublic static final String appSecret = "wxf546a5afce347xxx";//商户号public static final String wxMerchantNo = "1272569000";//商户签名加密key key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置public static final String wxMerchantApiKey = "xxxx";//私钥信息的证书文件private static final String certFilePath = "C:\\Users\\Public\\Downloads\\apiclient_cert.p12";

设计我们的表结构

这里只列出最核心的用户表、微信用户表、订单表,其他的表跟微信支付的业务关系不大。

用户表

public class UserInfo {/*** 用户ID*/private Long id;/*** 用户名称*/private String username;/*** 手机号*/private String phoneNumber;/*** 用户密码*/private String password;/*** 有效性,-1删除,0禁用,1有效*/private Integer valid;/*** 创建时间*/private Date createTime;/*** 更新时间*/private Date updateTime;
}

微信用户表

public class WxUserInfo {/*** 微信用户ID,*/private Long id;/*** 用户ID,关联用户表的id,可以为空(除非你的公众号是不注册就不让用。。。)*/private Long userId;/*** 是否关注,0=用户未关注公众号,1=用户关注了公众号*/private Integer subscribe;/*** 微信appid*/private String appId;/*** 微信openId*/private String openid;/*** 昵称*/private String nickname;/*** 用户的性别,值为1时是男性,值为2时是女性,值为0时是未知*/private Integer sex;/*** 城市*/private String city;/*** 国家*/private String country;/*** 省份*/private String province;/*** 头像*/private String headimgurl;/*** 订阅时间*/private Long subscribeTime;/*** 微信unionid,用来关联小程序*/private String unionid;/*** 备注*/private String remark;/*** 用户所在的分组ID*/private Integer groupid;/*** 创建时间*/private Date createTime;/*** 更新时间*/private Date updateTime;
}

订单表

public class OrderInfo {/*** 主键 数据库列名:<order_id> 数据库类型:<bigint> 内容长度:<19> 默认值:<null> 是否允许空值:<false>*/private Long orderId;/*** 商户订单号,也就是我们自己的订单号,需要唯一,微信要求最长不超过32个字符 数据库列名:<out_trade_no>* 数据库类型:<varchar> 内容长度:<32> 默认值:<null> 是否允许空值:<false>*/private String outTradeNo;/*** appid 数据库列名:<app_id> 数据库类型:<varchar> 内容长度:<50> 默认值:<null> 是否允许空值:<false>*/private String appId;/*** 支付金额(微信要求单位:分) 数据库列名:<total_fee> 数据库类型:<varchar> 内容长度:<100> 默认值:<null>* 是否允许空值:<false>*/private String totalFee;/*** 请求ip 数据库列名:<spbill_create_ip> 数据库类型:<varchar> 内容长度:<512> 默认值:<null>* 是否允许空值:<true>*/private String spbillCreateIp;/*** 订单交易类型 取值如下:JSAPI,NATIVE,APP,WAP 数据库列名:<trade_type> 数据库类型:<varchar>* 内容长度:<30> 默认值:<null> 是否允许空值:<false>*/private String tradeType;/*** 商户号,微信支付商户唯一标识 数据库列名:<mch_id> 数据库类型:<varchar> 内容长度:<30> 默认值:<null>* 是否允许空值:<false>*/private String mchId;/*** openid 数据库列名:<open_id> 数据库类型:<varchar> 内容长度:<100> 默认值:<null>* 是否允许空值:<true>*/private String openId;/*** 微信订单号 数据库列名:<transaction_id> 数据库类型:<varchar> 内容长度:<64> 默认值:<null>* 是否允许空值:<true>*/private String transactionId;/*** 订单状态,1=生成待提交,2=生成已提交,3=支付完成,4=支付已取消 数据库列名:<status> 数据库类型:<tinyint>* 内容长度:<3> 默认值:<null> 是否允许空值:<false>*/private Integer status;public static final Integer status_1 = 1;public static final Integer status_2 = 2;public static final Integer status_3 = 3;public static final Integer status_4 = 4;public static final Map<Integer, String> status_map = new LinkedHashMap<Integer, String>(4);static {status_map.put(status_1, "待生成");status_map.put(status_2, "待支付");status_map.put(status_3, "支付成功");status_map.put(status_4, "支付失败");}private String statusDetail;public String getStatusDetail() {statusDetail = status_map.get(status);return statusDetail;}public void setStatusDetail(String statusDetail) {this.statusDetail = statusDetail;}/*** 退款状态 0:未退款 1:退款成功 2:退款失败 3:退款中*/private Integer refundStatus;private String refundStatusDesc;public String getRefundStatusDesc() {this.refundStatusDesc = REFUND_STATUS_MAP.get(this.refundStatus);return this.refundStatusDesc;}public static Map<Integer, String> REFUND_STATUS_MAP = new HashMap<Integer, String>();static {REFUND_STATUS_MAP.put(Constant.PAY_ORDER_FLOW_REFUND_STATUS_NOT, "待审核未退款");REFUND_STATUS_MAP.put(Constant.PAY_ORDER_FLOW_REFUND_STATUS_SUCCESS, "退款成功");REFUND_STATUS_MAP.put(Constant.PAY_ORDER_FLOW_REFUND_STATUS_FAIL, "退款失败");REFUND_STATUS_MAP.put(Constant.PAY_ORDER_FLOW_REFUND_STATUS_ING, "退款中");REFUND_STATUS_MAP.put(Constant.PAY_ORDER_FLOW_REFUND_STATUS_APPROVAL_AGREE, "审核通过待退款");REFUND_STATUS_MAP.put(Constant.PAY_ORDER_FLOW_REFUND_STATUS_APPROVAL_REJECT, "审核拒绝");}/*** 退款时间*/private Date refundTime;/*** 退款失败原因*/private String refundReason;private String refundOrderNo;/*** 备注 数据库列名:<remark> 数据库类型:<varchar> 内容长度:<128> 默认值:<null> 是否允许空值:<true>*/private String remark;/*** 数据创建时间 数据库列名:<create_date> 数据库类型:<timestamp> 内容长度:<0>* 默认值:<CURRENT_TIMESTAMP> 是否允许空值:<false>*/private Date createDate;/*** 数据修改时间 数据库列名:<update_date> 数据库类型:<timestamp> 内容长度:<0> 默认值:<null>* 是否允许空值:<true>*/private Date updateDate;/*** 微信昵称*/private String nickname;/*** 回调时间*/private Date callbackDate;
}

开发

编写支付页面,使用wx.chooseWXPay调起微信支付界面

<html>
<script type="text/javascript">
$.ajax({url : '${ctx}/goods/sign.do',async : false,data : {'url' : window.location.href},dataType : 'json',type : 'post',success : function(result) {var data = result.data;if (data.appid == null || data.appid == "") {return;}wx.config({debug : false,appId : data.appid,timestamp : data.timestamp,nonceStr : data.nonceStr,signature : data.signature,jsApiList : [ "chooseWXPay" ]});}
});
wx.error(function(res) {alert("页面鉴权失败:"+res);
});
wx.ready(function() {
});var flag = false;
function pay(btn) {if (flag == true) {alert("您已经支付过了,请不要重复支付哦!");return;}$.ajax({url : '${ctx}/wechat/pay.do',//后端提供wx.chooseWXPay的参数type : 'post','data' : {"productId" : $("#productId").val(),"openId" : $("#openId").val()},dataType : 'json',aysnc : false,success : function(res) {if (res && res.code) {if(res.code == "200"){var data = res.data;wx.chooseWXPay({'debug' : true,'timestamp': data.timeStamp,'nonceStr': data.nonceStr,'package': 'prepay_id=' + data.prepayId, 'signType': 'MD5','paySign': data.sign,success : function(res) {flag = true;alert("支付成功");$(btn).prop("disabled", "disabled");},fail:function(err) {alert("系统错误:"+err);}});}else{alert("系统错误:"+res.msg);}} else {alert("系统错误:"+res);}}});
}
</script>
展示商品信息
<input id="productId" name="productId" value="${(product.productId)!''}" type="hidden"/>
<input id="openId" name="openId" value="${openId!''}" type="hidden"/>
<input type="button" id="payBtn" value="支付" onclick="pay(this)"/>
</html>

wx.chooseWXPay详解:

wx.chooseWXPay({timeStamp: 0, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符nonceStr: '', // 支付签名随机串,不长于 32 位package: '', // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=***)signType: '', // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'paySign: '', // 支付签名success: function (res) {// 支付成功后的回调函数}
});

wx.chooseWXPay有一个替代品:

WeixinJSBridge.invoke('getBrandWCPayRequest', {"appId" : "wx2421b1c4370ec43b",     //公众号名称,由商户传入     "timeStamp":" 1395712654",         //时间戳,自1970年以来的秒数     "nonceStr" : "e61463f8efa94090b1f366cccfbbb444", //随机串     "package" : "prepay_id=u802345jgfjsdfgsdg888",     "signType" : "MD5",         //微信签名方式:     "paySign" : "70EA570631E4BB79628FBCA90534C63FF7FADD89" //微信签名 },function(res){     if(res.err_msg == "get_brand_wcpay_request:ok" ) {}     // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回    ok,但并不保证它绝对可靠。 }); 

编写后台支付接口/wechat/pay.do

	//订单商品清单public static class ItemDTO{//示例伪代码private long productId;private int count;}// 下单,生成prepay_id给前端H5,然后通过jsapi调用wx.chooseWXPay@RequestMapping(value = "/pay.do", method = RequestMethod.POST)@ResponseBodypublic Object pay(HttpServletRequest request, HttpServletResponse response, List<ItemDTO> items,String openId) throws IOException {String logPrefix = "支付>>>";LOG.debug(logPrefix + "参数,"+items);if (Utils.isEmpty(openId)) {return JsonResult.getFailResultAndLog("openId为空", LOG, logPrefix);}WxUserInfo wxUser = wxUserInfoMapper.selectByOpenId(openId);if (wxUser == null) {return JsonResult.getFailResultAndLog("微信用户未录入", LOG, logPrefix);}WeixinResponseDTO  weixinResponse= WechatUtil.pay(request, items, openId);if (weixinResponse == null) {return JsonResult.getFailResultAndLog("发起支付失败", LOG, logPrefix);}// LOG.debug(logPrefix + "前端需要的支付参数config=" + config);Map<String, String> wxResp = WechatUtil.getPayConfig(weixinResponse.getPrepay_id());return JsonResult.getSuccessResultByData(wxResp);}
public class WechatUtil {public static Map<String, String> getPayConfig(String prepay_id) {SortedMap<String, String> json = new TreeMap<String, String>();json.put("appId", appId);json.put("timeStamp", System.currentTimeMillis() + "");json.put("nonceStr", UUID.randomUUID().toString().replaceAll("-", ""));json.put("package", "prepay_id=" + prepay_id);json.put("signType", "MD5");String sign = getSign(json, wxMerchantApiKey);json.put("paySign", sign);log.debug("支付json:" + JSONObject.toJSONString(json));return json;}
}

调用微信支付统一接口unifiedorder

微信统一下单参数封装

public class WeixinOrderDTO {/*** appId 必填*/private String appId;/*** 商户号  必填*/private String machId;/*** 设备号*/private String deviceInfo;/*** 随机字符串   必填*/private String nonceStr;/*** 签名  必填*/private String sign;/*** 商品描述  必填*/private String body;/*** 商品详情*/private String detail;/*** 附加数据*/private String attach;/*** 商户订单号   必填*/private String outTradeNo;/***货币类型*/private String feeType;/*** 总金额   必填*/private int totalFee;/*** 终端Ip   必填*/private String spbillCreateIp;/*** 交易起始时间*/private String timeStart;/*** 交易结束时间*/private String timeExpire;/*** 商品标记*/private String goodsTag;/*** 通知地址  必填*/private String notifyUrl;/*** 交易类型  必填*/private String tradeType;/*** 商品ID*/private String productId;/*** 指定支付方式*/private String limitPay;/*** 用户标识*/private String openId;/*** 微信退款商户唯一订单号*/private String outRefundNo;public String getAppId() {return appId;}public void setAppId(String appId) {this.appId = appId;}public String getMachId() {return machId;}public void setMachId(String machId) {this.machId = machId;}public String getDeviceInfo() {return deviceInfo;}public void setDeviceInfo(String deviceInfo) {this.deviceInfo = deviceInfo;}public String getNonceStr() {return nonceStr;}public void setNonceStr(String nonceStr) {this.nonceStr = nonceStr;}public String getSign() {return sign;}public void setSign(String sign) {this.sign = sign;}public String getBody() {return body;}public void setBody(String body) {this.body = body;}public String getDetail() {return detail;}public void setDetail(String detail) {this.detail = detail;}public String getAttach() {return attach;}public void setAttach(String attach) {this.attach = attach;}public String getOutTradeNo() {return outTradeNo;}public void setOutTradeNo(String outTradeNo) {this.outTradeNo = outTradeNo;}public String getFeeType() {return feeType;}public void setFeeType(String feeType) {this.feeType = feeType;}public int getTotalFee() {return totalFee;}public void setTotalFee(int totalFee) {this.totalFee = totalFee;}public String getSpbillCreateIp() {return spbillCreateIp;}public void setSpbillCreateIp(String spbillCreateIp) {this.spbillCreateIp = spbillCreateIp;}public String getTimeStart() {return timeStart;}public void setTimeStart(String timeStart) {this.timeStart = timeStart;}public String getTimeExpire() {return timeExpire;}public void setTimeExpire(String timeExpire) {this.timeExpire = timeExpire;}public String getGoodsTag() {return goodsTag;}public void setGoodsTag(String goodsTag) {this.goodsTag = goodsTag;}public String getNotifyUrl() {return notifyUrl;}public void setNotifyUrl(String notifyUrl) {this.notifyUrl = notifyUrl;}public String getTradeType() {return tradeType;}public void setTradeType(String tradeType) {this.tradeType = tradeType;}public String getProductId() {return productId;}public void setProductId(String productId) {this.productId = productId;}public String getLimitPay() {return limitPay;}public void setLimitPay(String limitPay) {this.limitPay = limitPay;}public String getOpenId() {return openId;}public void setOpenId(String openId) {this.openId = openId;}public String getOutRefundNo() {return outRefundNo;}public void setOutRefundNo(String outRefundNo) {this.outRefundNo = outRefundNo;}}

微信统一下单接口调用

public class WechatUtil {// 商户号public static final String wxMerchantNo = "1272569000";/*** 商户签名加密key key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置*/public static final String wxMerchantApiKey = "xxxx";/*** 微信二维码支付-微信统一下单*/public static WeixinResponseDTO pay(HttpServletRequest request, List<ItemDTO> products, String openId) {String outTradeNo = genOutTradeNo();String wxOrderShowName = "赶快付款,来不及解释了!";LOG.debug("微信统一下单>>微信支付流水单号:" + outTradeNo + ":==" + products);OrderInfo orderInfo = new OrderInfo();orderInfo.setStatus(OrderInfo.status_1);orderInfo.setOutTradeNo(outTradeNo);orderInfo.setOpenId(openId);// 根据商品和数量计算出金额// ...// 生成订单插入数据库orderInfoMapper.insert(orderInfo);WeixinOrderDTO wxOrder = new WeixinOrderDTO();wxOrder.setAppId(appId);wxOrder.setBody(wxOrderShowName);// 支付界面显示的标题wxOrder.setOutTradeNo(outTradeNo);// 我们自己的订单号// 商户号 必填wxOrder.setMachId(wxMerchantNo);wxOrder.setNonceStr(UUID.randomUUID().toString().replaceAll("-", ""));String notifyUrl = "http://www.xuexibisai.com/wechat/callback.do";wxOrder.setNotifyUrl(notifyUrl);wxOrder.setOpenId(openId);wxOrder.setTotalFee(new BigDecimal(orderInfo.getTotalFee()).multiply(new BigDecimal(100)).intValue());// 金额单位元转分wxOrder.setTradeType("JSAPI");wxOrder.setSpbillCreateIp("127.0.0.1");String str = unifiedorder(wxOrder, wxMerchantApiKey);LOG.debug("微信统一下单>>微信返回数据:" + str);if (StringUtils.isBlank(str)) {LOG.error("微信统一下单>>微信统一下单失败,微信未返回数据");return null;}XStream xs = new XStream(new DomDriver());xs.alias("xml", WeixinResponseDTO.class);WeixinResponseDTO weixinResponse = (WeixinResponseDTO) xs.fromXML(str);if (weixinResponse == null) {LOG.error("微信统一下单>>解析微信返回数据失败");return weixinResponse;}if (StringUtils.isBlank(weixinResponse.getResult_code()) || StringUtils.isBlank(weixinResponse.getReturn_code()) || !weixinResponse.getResult_code().equals("SUCCESS")|| !weixinResponse.getReturn_code().equals("SUCCESS")) {LOG.error("微信统一下单>>微信返回数据return_code为失败");return weixinResponse;}boolean flag = validateResponseSign(weixinResponse, wxMerchantApiKey);if (!flag) {LOG.error("微信统一下单>>微信返回数据apikey校验失败");return weixinResponse;}String prepay_id = weixinResponse.getPrepay_id();LOG.debug("微信统一下单>>prepay_id:" + prepay_id);if (!Utils.isEmptyTrim(prepay_id)) {orderInfo.setStatus(OrderInfo.status_2);orderInfoMapper.update(orderInfo);}return weixinResponse;}public static boolean validateResponseSign(WeixinResponseDTO weixinResponse, String apiKey) {Map<String, String> map = new TreeMap<String, String>();if (StringUtils.isNotBlank(weixinResponse.getAppid())) {map.put("appid", weixinResponse.getAppid());}if (StringUtils.isNotBlank(weixinResponse.getMch_id())) {map.put("mch_id", weixinResponse.getMch_id());}if (StringUtils.isNotBlank(weixinResponse.getNonce_str())) {map.put("nonce_str", weixinResponse.getNonce_str());}if (StringUtils.isNotBlank(weixinResponse.getPrepay_id())) {map.put("prepay_id", weixinResponse.getPrepay_id());}if (StringUtils.isNotBlank(weixinResponse.getResult_code())) {map.put("result_code", weixinResponse.getResult_code());}if (StringUtils.isNotBlank(weixinResponse.getReturn_code())) {map.put("return_code", weixinResponse.getReturn_code());}if (StringUtils.isNotBlank(weixinResponse.getTrade_type())) {map.put("trade_type", weixinResponse.getTrade_type());}if (StringUtils.isNotBlank(weixinResponse.getReturn_msg())) {map.put("return_msg", weixinResponse.getReturn_msg());}if (StringUtils.isNotBlank(weixinResponse.getCode_url())) {map.put("code_url", weixinResponse.getCode_url());}if (StringUtils.isNotBlank(weixinResponse.getMweb_url())) {map.put("mweb_url", weixinResponse.getMweb_url());}if (StringUtils.isBlank(weixinResponse.getSign())) {return false;}String sign = getSign(map, apiKey);log.debug("-----------对比后的sign1111" + sign);return sign.equals(weixinResponse.getSign());}public static final String unifiedorder_url = "https://api.mch.weixin.qq.com/pay/unifiedorder";/*** 微信支付下单*/public static String unifiedorder(WeixinOrderDTO weixinOrder, String key) {Map<String, String> map = new TreeMap<String, String>();StringBuffer sb = new StringBuffer();sb.append("<xml>");sb.append("<appid><![CDATA[" + weixinOrder.getAppId() + "]]></appid>");if (StringUtils.isNotBlank(weixinOrder.getBody())) {sb.append("<body><![CDATA[" + weixinOrder.getBody() + "]]></body>");map.put("body", weixinOrder.getBody());}sb.append("<mch_id><![CDATA[" + weixinOrder.getMachId() + "]]></mch_id>");sb.append("<nonce_str><![CDATA[" + weixinOrder.getNonceStr() + "]]></nonce_str>");sb.append("<notify_url><![CDATA[" + weixinOrder.getNotifyUrl() + "]]></notify_url>");if (!StringUtils.isEmpty(weixinOrder.getOpenId())) {sb.append("<openid><![CDATA[" + weixinOrder.getOpenId() + "]]></openid>");}sb.append("<out_trade_no><![CDATA[" + weixinOrder.getOutTradeNo() + "]]></out_trade_no>");sb.append("<spbill_create_ip><![CDATA[" + weixinOrder.getSpbillCreateIp() + "]]></spbill_create_ip>");sb.append("<total_fee><![CDATA[" + weixinOrder.getTotalFee() + "]]></total_fee>");// 单位是分,不是元sb.append("<trade_type><![CDATA[" + weixinOrder.getTradeType() + "]]></trade_type>");map.put("appid", weixinOrder.getAppId());map.put("mch_id", weixinOrder.getMachId());map.put("nonce_str", weixinOrder.getNonceStr());map.put("notify_url", weixinOrder.getNotifyUrl());if (!StringUtils.isEmpty(weixinOrder.getOpenId())) {map.put("openid", weixinOrder.getOpenId());}map.put("out_trade_no", weixinOrder.getOutTradeNo());map.put("spbill_create_ip", weixinOrder.getSpbillCreateIp() + "");map.put("total_fee", weixinOrder.getTotalFee() + "");map.put("trade_type", weixinOrder.getTradeType());String sign = getSign(map, key);sb.append("<sign><![CDATA[" + sign + "]]></sign>");sb.append("</xml>");String str = null;log.debug("url:" + unifiedorder_url + "xml:" + sb.toString());try {str = postByBody(unifiedorder_url, sb.toString(), "text/xml;charset=utf-8");} catch (HttpException e) {log.error(e.getMessage(), e);e.printStackTrace();} catch (IOException e) {log.error(e.getMessage(), e);e.printStackTrace();}return str;}
}

统一下单返回结果示例:

"<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg>"+"<appid><![CDATA[wxf546a5afce347110]]></appid>"+"<mch_id><![CDATA[1272569110]]></mch_id>"+"<nonce_str><![CDATA[LHxao3Tj84t1xsKi]]></nonce_str>"+"<sign><![CDATA[358FD39B7F0B7333666CA55BDBC1C88F]]></sign>"+"<result_code><![CDATA[SUCCESS]]></result_code>"+"<transaction_id><![CDATA[4200000167201808189301147765]]></transaction_id>"+"<out_trade_no><![CDATA[180818456205632937]]></out_trade_no>"+"<out_refund_no><![CDATA[180818457380003106]]></out_refund_no>"+"<refund_id><![CDATA[50000507922018081805532655669]]></refund_id>"+"<refund_channel><![CDATA[]]></refund_channel>"+"<refund_fee>6000</refund_fee>"+"<coupon_refund_fee>12</coupon_refund_fee>"+"<total_fee>6000</total_fee>"+"<cash_fee>5988</cash_fee>"+"<coupon_refund_count>1</coupon_refund_count>"+"<coupon_refund_fee_0>12</coupon_refund_fee_0>"+"<coupon_refund_id_0><![CDATA[2000000042245560146]]></coupon_refund_id_0>"+"<cash_refund_fee>5988</cash_refund_fee>"+
"</xml>"

统一下单返回结果封装

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.io.xml.DomDriver;@XStreamAlias("xml")
public class WeixinResponseDTO {@XStreamAlias("return_code")private String return_code;@XStreamAlias("return_msg")private String return_msg;@XStreamAlias("appid")private String appid;@XStreamAlias("mch_appid")private String mch_appid;@XStreamAlias("mchid")private String mchid;@XStreamAlias("device_info")private String device_info;@XStreamAlias("partner_trade_no")private String partner_trade_no;@XStreamAlias("payment_no")private String payment_no;@XStreamAlias("payment_time")private String payment_time;@XStreamAlias("mch_id")private String mch_id;@XStreamAlias("nonce_str")private String nonce_str;@XStreamAlias("sign")private String sign;@XStreamAlias("result_code")private String result_code;@XStreamAlias("prepay_id")private String prepay_id;@XStreamAlias("trade_type")private String trade_type;//二维码@XStreamAlias("code_url")private String code_url;//app支付@XStreamAlias("mweb_url")	private String mweb_url;//退款参数@XStreamAlias("err_code")private String err_code;	//错误@XStreamAlias("err_code_des")private String err_code_des;	//错误信息@XStreamAlias("transaction_id")private String transaction_id;	//微信订单号@XStreamAlias("out_trade_no")private String out_trade_no;	//商户系统内部的订单号@XStreamAlias("out_refund_no")private String out_refund_no;	//商户退款单号@XStreamAlias("refund_id")private String refund_id;		//微信退款单号@XStreamAlias("refund_channel")private String refund_channel;	//退款渠道@XStreamAlias("refund_fee")private int refund_fee;			//退款金额@XStreamAlias("coupon_refund_fee")private int coupon_refund_fee;	//代金券或立减优惠退款金额=订单金额-现金退款金额,注意:立减优惠金额不会退回@XStreamAlias("total_fee")private int total_fee;			//订单总金额@XStreamAlias("cash_fee")private int cash_fee;			//现金支付金额@XStreamAlias("coupon_refund_count")private int coupon_refund_count;	//代金券或立减优惠使用数量	0@XStreamAlias("cash_refund_fee")private int cash_refund_fee;		//代金券或立减优惠退款金额	0//微信使用代金券购买退款时返回参数会增加,导致xml解析出错,退款失败 zw 2018年8月23日@XStreamAlias("coupon_refund_fee_0")private int coupon_refund_fee_0;@XStreamAlias("coupon_refund_id_0")private String coupon_refund_id_0;public String getReturn_code() {return return_code;}public void setReturn_code(String return_code) {this.return_code = return_code;}public String getReturn_msg() {return return_msg;}public void setReturn_msg(String return_msg) {this.return_msg = return_msg;}public String getAppid() {return appid;}public void setAppid(String appid) {this.appid = appid;}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 getSign() {return sign;}public void setSign(String sign) {this.sign = sign;}public String getResult_code() {return result_code;}public void setResult_code(String result_code) {this.result_code = result_code;}public String getPrepay_id() {return prepay_id;}public void setPrepay_id(String prepay_id) {this.prepay_id = prepay_id;}public String getTrade_type() {return trade_type;}public void setTrade_type(String trade_type) {this.trade_type = trade_type;}public String getTransaction_id() {return transaction_id;}public void setTransaction_id(String transaction_id) {this.transaction_id = transaction_id;}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 getOut_refund_no() {return out_refund_no;}public void setOut_refund_no(String out_refund_no) {this.out_refund_no = out_refund_no;}public String getRefund_id() {return refund_id;}public void setRefund_id(String refund_id) {this.refund_id = refund_id;}public String getRefund_channel() {return refund_channel;}public void setRefund_channel(String refund_channel) {this.refund_channel = refund_channel;}public int getRefund_fee() {return refund_fee;}public void setRefund_fee(int refund_fee) {this.refund_fee = refund_fee;}public int getCoupon_refund_fee() {return coupon_refund_fee;}public void setCoupon_refund_fee(int coupon_refund_fee) {this.coupon_refund_fee = coupon_refund_fee;}public int getTotal_fee() {return total_fee;}public void setTotal_fee(int total_fee) {this.total_fee = total_fee;}public int getCash_fee() {return cash_fee;}public void setCash_fee(int cash_fee) {this.cash_fee = cash_fee;}public int getCoupon_refund_count() {return coupon_refund_count;}public void setCoupon_refund_count(int coupon_refund_count) {this.coupon_refund_count = coupon_refund_count;}public int getCash_refund_fee() {return cash_refund_fee;}public void setCash_refund_fee(int cash_refund_fee) {this.cash_refund_fee = cash_refund_fee;}public String getCode_url() {return code_url;}public void setCode_url(String code_url) {this.code_url = code_url;}public boolean isSuccess(){return "SUCCESS".equalsIgnoreCase(this.getReturn_code()) && "SUCCESS".equalsIgnoreCase(this.getResult_code());}public String getErr_code() {return err_code;}public void setErr_code(String err_code) {this.err_code = err_code;}public String getErr_code_des() {return err_code_des;}public void setErr_code_des(String err_code_des) {this.err_code_des = err_code_des;}public int getCoupon_refund_fee_0() {return coupon_refund_fee_0;}public void setCoupon_refund_fee_0(int coupon_refund_fee_0) {this.coupon_refund_fee_0 = coupon_refund_fee_0;}public String getCoupon_refund_id_0() {return coupon_refund_id_0;}public void setCoupon_refund_id_0(String coupon_refund_id_0) {this.coupon_refund_id_0 = coupon_refund_id_0;}public String getMweb_url() {return mweb_url;}public void setMweb_url(String mweb_url) {this.mweb_url = mweb_url;}public String getMch_appid() {return mch_appid;}public void setMch_appid(String mch_appid) {this.mch_appid = mch_appid;}public String getMchid() {return mchid;}public void setMchid(String mchid) {this.mchid = mchid;}public String getDevice_info() {return device_info;}public void setDevice_info(String device_info) {this.device_info = device_info;}public String getPartner_trade_no() {return partner_trade_no;}public void setPartner_trade_no(String partner_trade_no) {this.partner_trade_no = partner_trade_no;}public String getPayment_no() {return payment_no;}public void setPayment_no(String payment_no) {this.payment_no = payment_no;}public String getPayment_time() {return payment_time;}public void setPayment_time(String payment_time) {this.payment_time = payment_time;}
}

回调接口/wechat/callback.do

	/*** 账单结算微信支付-支付回调* * @param body*            微信传递的消息体* @author tangzhichao 20200922 新增*/@RequestMapping(value = "/callback.do")public void balancePayNotify(HttpServletRequest request, HttpServletResponse resp) throws IOException {LOG.debug("账单结算微信二维码支付---支付回调...");try {String param = WebUtil.getParams(request);if (StringUtils.isBlank(param)) {LOG.error("支付回调>>>回调信息为空");WechatUtil.fail(resp);return;}XStream xs = new XStream(new DomDriver());xs.alias("xml", WeChatBuyPostDTO.class);WeChatBuyPostDTO weChatBuyPost = (WeChatBuyPostDTO) xs.fromXML(param);if (weChatBuyPost == null) {LOG.error("支付回调>>>回调信息解析失败");WechatUtil.fail(resp);return;}String outTradeNo = weChatBuyPost.getOut_trade_no();if (Utils.isEmptyTrim(outTradeNo)) {LOG.error("支付回调>>>回调信息out_trade_no为空");WechatUtil.fail(resp);return;}LOG.debug("支付回调>>>订单Id:{}", outTradeNo);OrderInfo wxPayMonthBalanceOrder = orderInfoMapper.selectByOutTradeNo(outTradeNo);// 根据我们的订单号去数据库查出对应的订单if (wxPayMonthBalanceOrder == null) {LOG.error("支付回调>>>未找到订单号" + outTradeNo);WechatUtil.fail(resp);return;}if (StringUtils.isBlank(weChatBuyPost.getResult_code()) || StringUtils.isBlank(weChatBuyPost.getReturn_code()) || !weChatBuyPost.getResult_code().equals("SUCCESS")) {LOG.error("支付回调>>>回调信息result_code返回失败,具体信息:{}", JSONObject.toJSONString(weChatBuyPost));failAndUp(resp, wxPayMonthBalanceOrder, weChatBuyPost);return;}if (!WechatUtil.verifySign(param, WechatUtil.wxMerchantApiKey)) {LOG.error("支付回调>>>回调信息apikey校验失败");failAndUp(resp, wxPayMonthBalanceOrder, weChatBuyPost);return;}LOG.debug("支付回调>>>debug-判断status>>>" + wxPayMonthBalanceOrder.getStatus());if (WxPayMonthBalanceOrder.status_2.equals(wxPayMonthBalanceOrder.getStatus())) {// ...你的业务逻辑} else {LOG.debug("支付回调>>>重复回调...");}LOG.debug("支付回调>>>微信回调成功");} catch (Exception e) {LOG.error("支付回调>>>异常", e);failByCallback(resp);return;}LOG.debug("支付回调>>>debug-响应微信文件流>>>");WechatUtil.outText(resp, "text/xml", "utf-8", "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>");}private void failAndUp(HttpServletResponse resp, OrderInfo orderInfo, WeChatBuyPostDTO weChatBuyPost) throws IOException {orderInfo.setStatus(OrderInfo.status_4);orderInfoMapper.update(orderInfo);WechatUtil.fail(resp);}
public class WechatUtil {public static boolean verifySign(String xml, String key){Map<String,String> params = new TreeMap<String,String>();String primtiveSign = "";try {Document doc = DocumentHelper.parseText(xml);Element root = doc.getRootElement();Iterator<Element> it = root.elementIterator();while(it.hasNext()){Element e = it.next();if("sign".equals(e.getName().trim())){primtiveSign = e.getTextTrim();continue;}if(StringUtils.isNotBlank(e.getText())){params.put(e.getName(), e.getTextTrim());}}String newSign = getSign(params,key);boolean res =  newSign.equals(primtiveSign);log.debug("WeiXinManager:verifySign---->支付成功检查是否合法sign:" + res);return res;} catch (DocumentException e) {log.error("WeiXinManager:verifySign--->message = {}, trace = {}", e.getMessage(), e);}return false;}
}

退款WechatUtil .refund(OrderInfo orderInfo)

public class WechatUtil {private static final String certFilePath = "C:\\Users\\Public\\Downloads\\apiclient_cert.p12";public static void refund(OrderInfo orderInfo){if (StringUtils.isBlank(orderInfo.getRefundOrderNo())) {orderInfo.setRefundOrderNo(genRefundOrderNo());}orderInfo.setRefundStatus(Constant.PAY_ORDER_FLOW_REFUND_STATUS_ING);int row = orderInfoMapper.updateByPrimaryKey(orderInfo);if (row == 0) {LOG.error("更新退款 商户订单号失败:{}", orderInfo.getOrderId());return;}LOG.debug("修改退款操作,将订单改为退款中:" + orderInfo.getOrderId());WeixinOrderDTO order = new WeixinOrderDTO();order.setAppId(appId);order.setMachId(wxMerchantNo);order.setOutTradeNo(orderInfo.getOutTradeNo());order.setNonceStr(UUID.randomUUID().toString().replaceAll("-", ""));order.setTotalFee(orderInfo.getTotalFee().multiply(new BigDecimal(100)).intValue());//微信对此退款要求使用同一个商户退款订单号order.setOutRefundNo(orderInfo.getRefundOrderNo());String str = refund(order, wxMerchantApiKey, certFilePath);if (StringUtils.isEmpty(str)) {LOG.error("退款失败,请求退款返回异常");orderInfo.setRefundReason("退款失败,请求退款返回异常");}LOG.debug("退款请求返回:" + str);XStream xs = new XStream(new DomDriver());xs.alias("xml", WeixinResponseDTO.class);WeixinResponseDTO weixinResponse = (WeixinResponseDTO) xs.fromXML(str);if (weixinResponse == null) {LOG.error("解析xml失败");orderInfo.setRefundReason("退款失败,退款结果解析失败");// return;}boolean flag = validateResponseSignByRefund(weixinResponse, wxMerchantApiKey);if (!flag) {LOG.error("签名错误");/*** 注释return,此处会造成微信返回失败时直接return,出现退款失败的订单一直处于退款中状态,无法重新退款*/// return;orderInfo.setRefundReason("签名错误");}// 微信退款单号String refundId = weixinResponse.getRefund_id();LOG.debug("微信退款单号:" + refundId);if (weixinResponse.isSuccess()) {orderInfo.setRefundStatus(Constant.PAY_ORDER_FLOW_REFUND_STATUS_SUCCESS);orderInfo.setRefundReason(weixinResponse.getReturn_msg());LOG.debug("微信退款成功" + orderInfo.getOrderId());//...你的业务逻辑}else {orderInfo.setRefundStatus(Constant.PAY_ORDER_FLOW_REFUND_STATUS_FAIL);//注释,此处微信返回的return_msg只是通信的结果信息,并不是退款失败的原因// orderInfo.setRefundReason(weixinResponse.getReturn_msg());if (weixinResponse.getResult_code().equalsIgnoreCase("FAIL")) {orderInfo.setRefundReason(weixinResponse.getErr_code_des());}}orderInfo.setRefundTime(new Date());orderInfoMapper.updateByPrimaryKey(orderInfo);}private static int se = 0;public static synchronized String genRefundOrderNo() {String nextFrozeId = "";if (se > 99)se = 0;nextFrozeId = new SimpleDateFormat("yyyyMMdd").format(new java.util.Date()).substring(2, 8)+ ("" + new java.util.Date().getTime()).substring(3, 13) + createSerial("" + se, 2);se++;if (nextFrozeId.length() != 18)throw new RuntimeException("申请号长度错误[" + nextFrozeId + "]");return nextFrozeId;}public static String createSerial(String src, int len) {String dest = "";if (src.length() >= len) {dest = src.substring(0, len);} else {dest = createSameChar("0", len - src.length()) + src;}return dest;}public static String createSameChar(String src, int len) {StringBuffer sb = new StringBuffer();for (int i = 0; i < len; i++) {sb.append(src);}return sb.toString();}/*** 微信退款校验Sign* @return*/public static boolean validateResponseSignByRefund(WeixinResponseDTO weixinResponse, String apiKey){Map<String, String> map = new TreeMap<String, String>();if (StringUtils.isNotBlank(weixinResponse.getAppid())) {map.put("appid", weixinResponse.getAppid());}if (StringUtils.isNotBlank(weixinResponse.getMch_id())) {map.put("mch_id", weixinResponse.getMch_id());}if (StringUtils.isNotBlank(weixinResponse.getNonce_str())) {map.put("nonce_str", weixinResponse.getNonce_str());}if (StringUtils.isNotBlank(weixinResponse.getResult_code())) {map.put("result_code", weixinResponse.getResult_code());}if (StringUtils.isNotBlank(weixinResponse.getReturn_code())) {map.put("return_code", weixinResponse.getReturn_code());}if (StringUtils.isNotBlank(weixinResponse.getReturn_msg())) {map.put("return_msg", weixinResponse.getReturn_msg());}if (StringUtils.isNotBlank(weixinResponse.getTransaction_id())) {map.put("transaction_id", weixinResponse.getTransaction_id());}if (StringUtils.isNotBlank(weixinResponse.getOut_trade_no())) {map.put("out_trade_no", weixinResponse.getOut_trade_no());}if (StringUtils.isNotBlank(weixinResponse.getOut_refund_no())) {map.put("out_refund_no", weixinResponse.getOut_refund_no());}if (StringUtils.isNotBlank(weixinResponse.getRefund_id())) {map.put("refund_id", weixinResponse.getRefund_id());}if (StringUtils.isNotBlank(weixinResponse.getRefund_channel())) {map.put("refund_channel", weixinResponse.getRefund_channel());}if (weixinResponse.getRefund_fee() >= 0) {map.put("refund_fee", weixinResponse.getRefund_fee()+"");}if (weixinResponse.getCoupon_refund_fee() >= 0) {map.put("coupon_refund_fee", weixinResponse.getCoupon_refund_fee()+"");}if (weixinResponse.getTotal_fee() >= 0) {map.put("total_fee", weixinResponse.getTotal_fee()+"");}if (weixinResponse.getCash_fee() >= 0) {map.put("cash_fee", weixinResponse.getCash_fee()+"");}if (weixinResponse.getCoupon_refund_count() >= 0) {map.put("coupon_refund_count", weixinResponse.getCoupon_refund_count()+"");}if (weixinResponse.getCash_refund_fee() >= 0) {map.put("cash_refund_fee", weixinResponse.getCash_refund_fee()+"");}if (StringUtils.isBlank(weixinResponse.getSign())) {return false;}String sign = getSign(map, apiKey);LOG.debug("sign:"+sign);LOG.debug("requestSign:"+weixinResponse.getSign());return sign.equals(weixinResponse.getSign());}public static final String refund_url = "https://api.mch.weixin.qq.com/secapi/pay/refund";/*** 微信退款资金来源:refund_account* REFUND_SOURCE_UNSETTLED_FUNDS:未结算资金退款(默认使用未结算资金退款),这种资金来源,只能使用订单收入的金额,不确定性较大,容易出现退款失败* REFUND_SOURCE_RECHARGE_FUNDS:可用余额退款,这种方式稳定,账户资金不够时直接充值就可以了*/public static final String REFUND_SOURCE_UNSETTLED_FUNDS = "REFUND_SOURCE_UNSETTLED_FUNDS";public static final String REFUND_SOURCE_RECHARGE_FUNDS = "REFUND_SOURCE_RECHARGE_FUNDS";/*** 退款请求* * @param weixinOrder*            退款参数* @param key*            签名key* @param path*            签权路径* @return*/public static String refund(WeixinOrderDTO weixinOrder, String key, String path) {Map<String, String> map = new TreeMap<String, String>();StringBuffer sb = new StringBuffer();sb.append("<xml>");sb.append("<appid><![CDATA[" + weixinOrder.getAppId() + "]]></appid>");sb.append("<mch_id><![CDATA[" + weixinOrder.getMachId() + "]]></mch_id>");sb.append("<nonce_str><![CDATA[" + weixinOrder.getNonceStr() + "]]></nonce_str>");sb.append("<op_user_id><![CDATA[" + weixinOrder.getMachId() + "]]></op_user_id>");sb.append("<out_refund_no><![CDATA[" + weixinOrder.getOutRefundNo() + "]]></out_refund_no>");sb.append("<out_trade_no><![CDATA[" + weixinOrder.getOutTradeNo() + "]]></out_trade_no>");sb.append("<refund_fee><![CDATA[" + weixinOrder.getTotalFee() + "]]></refund_fee>");// 退款资金来源sb.append("<refund_account><![CDATA[" + REFUND_SOURCE_RECHARGE_FUNDS + "]]></refund_account>");sb.append("<total_fee><![CDATA[" + weixinOrder.getTotalFee() + "]]></total_fee>");sb.append("<transaction_id><![CDATA[]]></transaction_id>");map.put("appid", weixinOrder.getAppId());map.put("mch_id", weixinOrder.getMachId());map.put("nonce_str", weixinOrder.getNonceStr());map.put("op_user_id", weixinOrder.getMachId());map.put("out_refund_no", weixinOrder.getOutRefundNo());map.put("out_trade_no", weixinOrder.getOutTradeNo());map.put("refund_account", REFUND_SOURCE_RECHARGE_FUNDS + "");map.put("refund_fee", weixinOrder.getTotalFee() + "");map.put("total_fee", weixinOrder.getTotalFee() + "");map.put("transaction_id", "");String sign = getSign(map, key);sb.append("<sign><![CDATA[" + sign + "]]></sign>");sb.append("</xml>");String str = null;LOG.debug("url:" + refund_url + "xml:" + sb.toString());try {str = postByBodyWithCert(refund_url, sb.toString(), "application/xml; charset=UTF-8", path);} catch (Exception e) {LOG.error(e.getMessage(), e);}return str;}public static String postByBodyWithCert(String url, String body, String contentType, String certPath) throws HttpException, Exception {KeyStore keyStore = KeyStore.getInstance("PKCS12");File file = new File(certPath);String fileName = file.getName();fileName = fileName.substring(0, fileName.lastIndexOf("."));FileInputStream instream = new FileInputStream(file);try {keyStore.load(instream, fileName.toCharArray());} finally {instream.close();}// Trust own CA and all self-signed certsSSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, fileName.toCharArray()).build();// Allow TLSv1 protocol onlySSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[] { "TLSv1" }, null,SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(TIMEOUT_SEC * 1000).setSocketTimeout(TIMEOUT_SEC * 1000).setConnectTimeout(1000).build();CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();StringBuilder sb = new StringBuilder();try {HttpPost httpPost = new HttpPost(url);httpPost.setConfig(requestConfig);httpPost.addHeader("Content-Type", contentType);/** if (StringUtils.isNotBlank(body)) { body =* URLEncoder.encode(body, "UTF-8"); }*/HttpEntity se = new StringEntity(body, "UTF-8");httpPost.setEntity(se);CloseableHttpResponse response = httpclient.execute(httpPost);try {HttpEntity entity = response.getEntity();if (entity != null) {BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(entity.getContent()));String text;while ((text = bufferedReader.readLine()) != null) {sb.append(text);}}EntityUtils.consume(entity);} finally {response.close();}} finally {httpclient.close();}return sb.toString();}public static class MySSLProtocolSocketFactory implements ProtocolSocketFactory {private SSLContext sslcontext = null;public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) throws IOException, UnknownHostException {return getSSLContext().getSocketFactory().createSocket(host, port, clientHost, clientPort);}public Socket createSocket(String host, int port, InetAddress localAddress, int localPort, HttpConnectionParams params)throws IOException, UnknownHostException, ConnectTimeoutException {if (params == null) {throw new IllegalArgumentException("Parameters may not be null");}int timeout = params.getConnectionTimeout();SocketFactory socketfactory = getSSLContext().getSocketFactory();if (timeout == 0) {return socketfactory.createSocket(host, port, localAddress, localPort);} else {Socket socket = socketfactory.createSocket();SocketAddress localaddr = new InetSocketAddress(localAddress, localPort);SocketAddress remoteaddr = new InetSocketAddress(host, port);socket.bind(localaddr);socket.connect(remoteaddr, timeout);return socket;}}public Socket createSocket(String host, int port) throws IOException, UnknownHostException {return getSSLContext().getSocketFactory().createSocket(host, port);}private SSLContext createSSLContext() {SSLContext sslcontext = null;try {sslcontext = SSLContext.getInstance("SSL");sslcontext.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom());} catch (Exception e) {e.printStackTrace();}return sslcontext;}private SSLContext getSSLContext() {if (this.sslcontext == null) {this.sslcontext = createSSLContext();}return this.sslcontext;}// 自定义私有类private static class TrustAnyTrustManager implements X509TrustManager {public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {// TODO Auto-generated method stub}public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {// TODO Auto-generated method stub}public X509Certificate[] getAcceptedIssuers() {// TODO Auto-generated method stubreturn null;}}}
}

http://www.ppmy.cn/news/701034.html

相关文章

微信camera拍照组件的使用(uniapp小程序)代码可直接复制看效果

微信camera官方文档&#xff1a;https://developers.weixin.qq.com/miniprogram/dev/component/camera.html html 整体效果 样式可以自行定义的一个拍照组件 未找到摄像头是因为台式机电脑没有摄像头 真机测试可以使用 <view class"tackpic"><view class&qu…

520微信代码轰炸

写一个脚本&#xff0c;在520那天发给你的小可爱。 # -*- coding : utf-8 -*- # Time : 2022/5/19 13:36 # Author : wkb import time,os import pyautogui,pyperclip time.sleep(5) for i in range(10):#pyautogui.click(662,748)pyperclip.copy("代码轰炸&#…

Python趣味代码(一):微信信息轰炸

1.安装模块 首先需要在电脑上安装好pyautogui、pyperclip两个模块 # Windos系统安装命令 pip install pyautogui pip install pyperclip# Mac系统安装命令 pip3 install pyautogui pip3 install pyperclip 2.功能实现 将整个流程分为三大块&#xff1a; 一、获取发信内容 二、…

uniapp微信小程序授权登录流程(代码直接复制可用)

1.写一个点击登录的按钮 <view tap"login">一键登录</view> 2.写点击事件 login() {wx.getUserInfo({success: (res) > {console.log(获取到openId, res);}})let that this;uni.getUserProfile({desc: 用于完善用户资料,lang: zh_CN,success: (res) …

爱心代码--C语言特供(可直接复制,亲测有效)

情人节到了&#xff0c;作为一名程序员&#xff0c;我们拥有属于我们的浪漫。 这里我总结了几种常见的爱心代码&#xff0c;简单易上手。 一.这是一种最为常见的爱心代码 #include<stdio.h> #include<Windows.h>int main() {float x, y, a;for (y 1.5; y > -1.…

微信小程序页面跳转方式+跳转小程序(直接复制代码可用)

一. 微信小程序跳转页面方法 1.跳转到 tabBar 页面 wx.switchTab({url: /index }) 2.跳转到其他页面&#xff08;非tabBar页&#xff09; //redirectTo方法&#xff08;会关闭当前页面&#xff09; wx.redirectTo({url: /page//页面路径 })//navigateTo方法&#xff08;不会…

微信8.0自动发送炸弹python脚本

import itchat import argparsedef get_arguments():parser argparse.ArgumentParser(description文献表情包发送)parser.add_argument(--name, typestr, default"xxx",help要发送的人的微信备注)parser.add_argument(--group, typestr, default"person",…

NumPy实现逻辑回归

说明&#xff1a;数据集 ex2data1.txt是吴恩达机器学习的作业的数据集。 # -*-coding:utf-8-*- import matplotlib.pyplot as plt import numpy as np import pandas as pdclass Logitstic_Regression:def __init__(self, learning_rate0.01, num_iterations75000, threshold0.…