背景
随着微信支付的流行,大多产品都开发了自己的公众号、小程序等,产品的营销需要支付的支撑,最近做了个微信公号号支付,采坑无数,今天给大家分享一下
可以先看一下微信支付API文档
https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419317853&token=&lang=zh_CN
建议先看一下微信的开发者文档,虽然有点坑 。。。。
应用场景
所需jar包
<!-- 微信支付需要的jar包 -->
<dependency>
<groupId>xmlpull</groupId>
<artifactId>xmlpull</artifactId>
<version>1.1.3.1</version>
</dependency>
<dependency>
<groupId>xpp3</groupId>
<artifactId>xpp3</artifactId>
<version>1.1.4c</version>
</dependency>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.7</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>fluent-hc</artifactId>
<version>4.3.5</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.3.5</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient-cache</artifactId>
<version>4.3.5</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.3.5</version>
</dependency>
公众号支付所需参数
public class WeixinMatchPayConfigure {
/**
* 域名 项目域名,根据需求自行配置
*/
public static final String ROOTURL = WeixinPayConfigure.ROOTURL;
/**
* 订单域名 项目域名,根据需求自行配置
*/
public static final String ORDER_ROOTURL = WeixinPayConfigure.ORDER_ROOTURL;
/**
* 赛事 域名 项目域名,根据需求自行配置
*/
public static final String MATCHURL = "http://www.baidu.com";
/**
* 公共账号id 必填 (18位数↓)
*/
public static final String APPID = WeixinPayConfigure.APPID;
/**
* 商户id 商户账号 必填
*/
public static final String MCH_ID = "11111111";
/**
* 应用秘钥 必填(可在微信商户平台上查找)
*/
public static final String APP_SECRET = "fd87878fsf87fsf8cvsd8";
/**API秘钥*/ 必填(可在微信商户平台上查找)
public static final String API_KEY = "fsdfn23482njdvjw23455555";
/**
* 统一下单URL 微信官方提供
*/
public static final String PAY_UNIFIED_ORDER_API = "https://api.mch.weixin.qq.com/pay/unifiedorder";
/**
* 微信公众号交易类型 (扫码支付类型:NATIVE,公众号支付类型:JSAPI)
*/
public static final String TRADE_TYPE = "JSAPI";
/**
* 获取code的回调地址 你项目要展示的首页路径
*/
public static final String REDIRECT_URI = "http://order.uxuexi.com/pay/apply.html";
/**微信H5支付结果通知页*/
public static final String NOTIFY_URL = ROOTURL + "/api/pay/weixin/notify.html";
/**
* 不弹出授权页面,直接跳转,只能获取用户openid
*/
public static final String SCOPE = "snsapi_base";
/**
* 弹出授权页面,需要用户确认,可以获取用户的较多信息
*/
public static final String USERINFOSCOPE = "snsapi_userinfo";
/**
* 获取微信code的url(登录授权)
*/
public static final String GET_CODE_URL = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect";
/**
* 获取用户的OpenId的url(必须先获得code之后调用)
*/
public static final String GET_OPENID_URL = "https://api.weixin.qq.com/sns/oauth2/access_token";
/**
* 微信支付成功之后的回调
*/
public static final String NOTIFY_ACTIVITY_URL = WeixinPayConfigure.ORDER_ROOTURL + "/pay/wxnotify.json";
}
获取微信用户的openId
大致步骤:获取用户授权(获取code)------------->根据code获取openID(用户的基本信息)
1、配置授权域
这个则是在公众号登陆平台上面配置的↓
2、发起API请求获取用户授权
需要拼接三个参数:
APPID、REDIRECT_URI、SCOPE,代码如下:
//项目入口,如果这个项目是在公众号中,那么公众号菜单下面配置的就是这个接口的路径↓
@At
@Ok("jsp:match.entrance")
@NotSso
public Object entrance() {
String codeUrl = WeiXinApiUrlUtil.getMatchUrl();
return codeUrl;
}
public static String getMatchUrl() {
String url = WeixinMatchPayConfigure.GET_CODE_URL;
url = url.replace("APPID", urlEnodeUTF8(WeixinMatchPayConfigure.APPID));
url = url.replace("REDIRECT_URI", WeixinMatchPayConfigure.REDIRECT_URI);
url = url.replace("SCOPE", WeixinMatchPayConfigure.USERINFOSCOPE);
return url;
}
返回前端的是个Url路径,同个这个路径来获取微信用户授权,然后跳转我们自己的首页(REDIRECT_URI)
前端页面
<script type="text/javascript">
$(document).ready(function(){
//其实这个时候跳转的URL它会跟着一连串属性,入下图 ↓
window.location.href='${obj}';
});
</script>
3、微信用户openId是什么?
在微信用户关注公众号时,会对应的产生一个openId(openId:加密后的用户微信号),一个微信用户对应一个公众号产生的openId是唯一,当然,如果该微信号去关注另一个公众号所产生openID肯定和当前这个不一样的,
OpenID 是最对『微信应用』的用户唯一值,同一个『微信开发者账号』下的不同应用中,使用同一个『微信用户』登录,此值会不一样,
废话不说,上代码↓
①、上文表明此接口请求带有参数code,那么需在这里接受code参数
@At
@Ok("jsp:match.apply")
@NotSso
public Object apply(@Param("code") String code) {
return matchPayViewService.apply(code);
}
②、获取openID放到session中去
public Object apply(String code) {
//创建空map
Map<String, Object> map = MapUtil.map();
//获取session
HttpSession session = Mvcs.getReq().getSession();
//获取session中oppenID
String oppendId = ConvertUtil.obj2str(session.getAttribute("oppendId"));
//非空校验oppenID
if (!Util.isEmpty(oppendId)) {
map.put("isWeChat", "yes");
return map;
}
//校验code是否为空,为空说明不是微信公众号支付
if (Util.isEmpty(code)) {
map.put("isWeChat", "no");
return map;
}
//获取访问用户的token,(工具类1)
UserInfoAccessTokenDt accessTokenDt = userInfoAccessTokenDtBaseService.getUserAccessToken(code);
//获取微信用户信息,(工具类2)
UserInfoDt userInfo = userInfoDtBaseService.getUserInfo(accessTokenDt);
ExceptionUtil.checkEmpty(userInfo, "获取微信用户信息失败");
map.put("userInfo", userInfo);
int sessionInactive = 30 * 60;
//把相关数据放到session中去
session.setMaxInactiveInterval(sessionInactive);
session.setAttribute("oppendId", userInfo.getOpenid());
session.setAttribute("userInfo", userInfo);
map.put("isWeChat", "yes");
//获取当前用户
map.put("userId", fetchUser.getCurrentUserId());
//-----------------------------------------------------权限校验,可根据自己的项目进行业务操作
map.put("isgxltUser", gXUnicomBusinessService.isPermission(fetchUser.getCurrentUserId()));
return map;
}
注:上面这些是获取微信用户授权、获取用户的基本信息,公众号支付需要用户的openID,所以。。。。。。
创建预支付Url
1、创建订单
①、此处不解释
@At
@Ok("jsp:match.createorder")
@NotSso
public Object createOrder(@Param("..") final OrderAddForm orderAddForm, final String gradeName) {
return matchPayViewService.createOrder(orderAddForm, gradeName);
}
②、创建订单
public Map<String, Object> createOrder(OrderAddForm orderAddForm, String gradeName) {
ExceptionUtil.checkId(orderAddForm.getMatchId(), "赛事ID不能为空");
Map<String, Object> map = MapUtil.map();
orderAddForm.setCustomPrice(matchBaseService.getPrice(orderAddForm.getMatchId()).getPrice());
OrderMatchEntity order = dbDao.insert(orderAddForm.toEntity());
map.put("order", order);
map.put("gradeName", gradeName);
//拼接预支付订单
String result = sendReqGetPreOrder(order);
//转化为JsApiParam对象,(工具类3)
JsApiParam jap = dowithWxReturn(result);
map.put("jap", jap);
return map;
}
③、拼接预支付订单参数
private String sendReqGetPreOrder(OrderMatchEntity order) {
ExceptionUtil.checkEmpty(order.getId(), "订单id不能为空");
Map<String, Object> params = MapUtil.map();
params.put("appid", WeixinMatchPayConfigure.APPID);
params.put("mch_id", WeixinMatchPayConfigure.MCH_ID);
params.put("notify_url", WeixinMatchPayConfigure.NOTIFY_ACTIVITY_URL);
params.put("trade_type", WeixinMatchPayConfigure.TRADE_TYPE);//单次订单为jsapi方式
int randomNumLength = 32;
params.put("nonce_str", RandomUtil.randomString(randomNumLength));
//获取openID
params.put("openid", getWxUserInfoWithEx().getOpenid());
String body = "赛事报名";
params.put("body", body);
params.put("out_trade_no", order.getId());
long total_fee = AmountUtils.changeY2F(order.getCustomPrice());
params.put("total_fee", total_fee);
params.put("device_info", "WEB");
//加密签名,工具类(微信支付PC端文档中有)
String sign = Signature.getSign(params, WeixinMatchPayConfigure.API_KEY);
params.put("sign", sign);
//HttpRequest 工具类(微信支付PC端文档中有)
return HttpRequest.sendPost(WeixinMatchPayConfigure.PAY_UNIFIED_ORDER_API, params);
}
//获取微信标识
private UserInfoDt getWxUserInfoWithEx() {
Object userInfo = Mvcs.getReq().getSession().getAttribute("userInfo");
if (Util.isEmpty(userInfo)) {
throw ExceptionUtil.bEx("获取你的微信身份的标识失败请重新退出再次进入");
}
//类型转换
return ConvertUtil.cast(userInfo, UserInfoDt.class);
}
④、
处理调用微信预支付订单的返回值
private JsApiParam dowithWxReturn(String result) {
//该类见(工具类3)
JsApiParam jsApiParam = new JsApiParam();
Map<String, Object> weixinPrepayInfo = MapUtil.map();
try {
//------------------解析XML(工具类:微信支付PC端文档中有)
weixinPrepayInfo = XMLParser.getMapFromXML(result);
String return_code = (String) weixinPrepayInfo.get("return_code");
if ("SUCCESS".equals(return_code)) {
String prepay_id = (String) weixinPrepayInfo.get("prepay_id");
//给jsApiParam对象赋值
jsApiParam.setPrepay_id(prepay_id);
jsApiParam.setPackageInfo("prepay_id=" + prepay_id);
jsApiParam.setPaySign(getJsApiPaySign(jsApiParam));
return jsApiParam;
} else {
throw ExceptionUtil.bEx("预支付失败");
}
} catch (Exception e) {
ExceptionUtil.bEx("调用微信预支付接口出错");
}
return jsApiParam;
}
⑤、调用
jsApiParam对象打回前台后,则需要调用微信内部的提供的js方法getBrandWCPayRequest
function onBridgeReady(){
//微信内部提供的js方法
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId":"${obj.jap.appId}", //公众号名称,由商户传入
"timeStamp":"${obj.jap.timeStamp}", //时间戳,自1970年以来的秒数
"nonceStr":"${obj.jap.nonceStr}", //随机串
"package":"${obj.jap.packageInfo}",
"signType":"MD5", //微信签名方式:
"paySign":"${obj.jap.paySign}" //微信签名
},
function(res){
// 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。
if (res.err_msg == "get_brand_wcpay_request:ok") {
//跳转到成功页面
window.location.href = "http://lannong.uxuexi.com/register/success.html";
} else if (res.err_msg == "get_brand_wcpay_request:cancel") {
WeixinJSBridge.call('closeWindow');
} else {
}
}
);
}
⑥、调用⑤后页面则会弹出微信支付框,如图↓
支付成功
1、支付成功后则调用成功后的回调函数
@At
@Filters
public void wxnotify() throws Exception {
matchPayViewService.weChatPayViewService();
}
2、签名校验
public void weChatPayViewService() throws Exception {
HttpServletRequest request = Mvcs.getReq();
HttpServletResponse response = Mvcs.getResp();
//获取微信响应的内容
String responseString = getWeiXinResponseContent(request);
PrintWriter out = response.getWriter();
String resp = "";
String signKey = WeixinPayConfigure.API_KEY;
//------------------------------签名校验↓,(工具类:微信支付PC端文档中有)
boolean verify = Signature.checkIsSignValidFromResponseString(responseString, signKey);
if (!verify) {
logger.error("签名验证失败");
resp = "签名验证失败";
out.write(resp);
out.close();
//签名失败直接返回
return;
}
//解析xml
Map<String, Object> map = XMLParser.getMapFromXML(responseString);
String result_code = ConvertUtil.obj2str(map.get("result_code"));
if (!"SUCCESS".equalsIgnoreCase(result_code)) {
resp = PayCommonUtil.getResponseXML("ERROR", "ERROR");
out.write(resp);
out.close();
//支付失败直接返回
return;
}
//处理订单
resp = handleOrder(map);
out.write(resp);
out.close();
}
//获取微信响应内容
private String getWeiXinResponseContent(HttpServletRequest request) throws IOException,
UnsupportedEncodingException {
InputStream inStream = request.getInputStream();
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inStream.read(buffer)) != -1) {
outStream.write(buffer, 0, len);
}
//获取微信调用我们notify_url的返回信息
String responseString = new String(outStream.toByteArray(), "utf-8");
outStream.close();
inStream.close();
return responseString;
}
3、处理订单,此处业务不再讲解,不懂得可以去看PC端微信支付文档
@Aop("txDb")
private String handleOrder(Map<String, Object> map) throws Exception {
String resp = PayCommonUtil.getResponseXML("SUCCESS", "OK");
String transaction_id = ConvertUtil.obj2str(map.get("transaction_id"));
String time_end = ConvertUtil.obj2str(map.get("time_end"));
String out_trade_no = (String) map.get("out_trade_no");
if (Util.isEmpty(transaction_id) || Util.isEmpty(time_end) || Util.isEmpty(out_trade_no)) {
resp = PayCommonUtil.getResponseXML("ERROR", "参数错误,微信支付订单号、支付完成时间、订单号均不能为空");
return resp;
}
OrderMatchEntity order = dbDao.fetch(OrderMatchEntity.class, ConvertUtil.obj2long(out_trade_no));
if (Util.isEmpty(order)) {
resp = PayCommonUtil.getResponseXML("ERROR", "订单不存在");
return resp;
}
int orderStatus = order.getStatus();
if (OrderStatusEnum.FINISHED.intKey() == orderStatus) {
return resp;
}
if (OrderStatusEnum.WAITING_PAY.intKey() == orderStatus) {
//此处写你所需的业务即可
//更新订单为完成状态
//实际支付金额(分)
//添加支付记录
}
return resp;
}
4、公众号微信支付,支付后微信会返回三种状态,如图↓
那么,关于三种状态我们所需跳转的页面则可以在前台用js来实现
function(res){
// 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。
if (res.err_msg == "get_brand_wcpay_request:ok") {
//跳转到成功页面
window.location.href = "http://lannong.uxuexi.com/register/success.html";
} else if (res.err_msg == "get_brand_wcpay_request:cancel") {
WeixinJSBridge.call('closeWindow');
} else {
}
}
工具类
1、
获取访问用户的token
@IocBean
public class UserInfoAccessTokenDtBaseService {
/**
* 通过code获取用户的openId
*
* @param code 编号
*
* @return 用户的openId
*/
public UserInfoAccessTokenDt getUserAccessToken(String code) {
ExceptionUtil.checkEmpty(code, "用户同意授权,获取的code不能为空");
//获取用户openid的连接
String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + WeixinH5PayConfigure.APPID
+ "&secret=" + WeixinH5PayConfigure.APP_SECRET + "&code=" + code + "&grant_type=authorization_code";
Response res = Http.get(url);
String content = res.getContent();
//----------------------------------从 JSON 字符串中,根据获取某种指定类型的 JSON 对象
UserInfoAccessTokenDt accessTokenDt = JsonUtil.fromJson(content, UserInfoAccessTokenDt.class);
return accessTokenDt;
}
}
2、获取微信用户基本信息
@IocBean
public class UserInfoDtBaseService {
/**
* 获取用户信息
*
* @param accessTokenDt 获取用户信息的token
*
* @return 用户信息对象
*/
public UserInfoDt getUserInfo(UserInfoAccessTokenDt accessTokenDt) {
ExceptionUtil.checkEmpty(accessTokenDt, "访问用户的accessToken不能为空");
String url = "https://api.weixin.qq.com/sns/userinfo?access_token=" + accessTokenDt.getAccess_token()
+ "&openid=" + accessTokenDt.getOpenid() + "&lang=zh_CN";
Response res = Http.get(url);
String content = res.getContent();
//------------------------------------- 从 JSON 字符串中,根据获取某种指定类型的 JSON 对象。↓
UserInfoDt UserInfo = JsonUtil.fromJson(content, UserInfoDt.class);
return UserInfo;
}
}
@Data
public class UserInfoAccessTokenDt {
//TODO(注释去这个链接下找:http://mp.weixin.qq.com/wiki/17/c0f37d5704f0b64713d5d2c37b468d75.html)
private String access_token;
private String expires_in;
private String refresh_token;
private String openid;
private String scope;
private String unionid;
}
3、微信公众号支付参数
@Data
public class JsApiParam {
/**
* 公众号appid
*/
private String appId = WeixinH5PayConfigure.APPID;
/**
* 时间戳
*/
private String timeStamp = System.currentTimeMillis() + "";
/**
* 随机字符串
*/
private String nonceStr = RandomUtil.randIntString(32);
/**
* 签名方式
*/
private String signType = "MD5";
/**
* 预支付id
*/
private String packageInfo;
/**
* 支付签名
*/
private String paySign;
/**
* 订单号
*/
private String orderNo;
/**
* 微信预付单号
*/
private String prepay_id;
}