服务端微信支付相关问题
JSAPI开发文档
统一下单参数
在开发前需要先申请商户平台申请商户号和APP支付(在开放平台申请账号)、JSAPI(在公众平台申请账号)
在开发过程中先后集成了APP支付和公众号支付(JSAPI支付)此两种支付方式统一下单的参数不同,详见下方:
共同参数:
mch_id:商户号;nonce_str:随机字符串;body:商品描述;out_trade_no:商户订单号;total_fee:订单总金额,单位为分;spbill_create_ip:终端IP;notify_url:回调地址(此地址为微信支付成功后会回调请求该地址,商户方再此处理支付成功后的业务逻辑处理)
不同参数:
appid:APP支付(开放平台ID)、JSAPI支付(公众号ID);
trade_type:APP支付(APP)、JSAPI支付(JSAPI);
openid:JSAPI支付必传此参数;
注意事项
1.当支付方式为jsapi时统一下单接口参数返回前端务必注意部分参数的大小写
如appId I是大写,signType T为大写,timeStamp S是大写,nonceStr S是大写,否则前端JS调微信SDK时会一直报验签失败
2.支付方式为jsapi时统一下单请求微信后台的参数必须有openId,每个关注过公众号用户的Openid是唯一的
3.微信授权信息调微信后台接口时需要将获取的access_token和jsapiTicket放至缓存,因为每天请求此接口有次数限制,有效期为两个小时
4.统一下单中生成签名前需要在微信商户平台中设备Key密钥
统一下单JAVA代码如下
@Override
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public Map<Object,Object> saveOrder(String total, String loginUserId, String billsId,String feeType,String openId) {
try {
Map<Object, Object> map = new HashMap<>();
//根据openId是否为空进行判断是app支付还是公众号支付
if(StringUtils.isNotEmpty(openId)){
map.put("appid", WECHAT_OFFICIAL_APPID);
map.put("trade_type", "JSAPI");
map.put("openid", openId);
}else{
map.put("appid", APPID);
map.put("trade_type", TRADE_TYPE);
}
map.put("mch_id", MCHID);
map.put("nonce_str", RandomUtil.getRandomStringByLength(32));
map.put("body", "家半径-微信支付");
map.put("out_trade_no", RandomUtil.getRandomStringByLength(32));
//处理金额去除小数点
DecimalFormat decimalFormat = new DecimalFormat("0");
decimalFormat.setRoundingMode(RoundingMode.HALF_UP);
BigDecimal big = new BigDecimal(total).multiply(new BigDecimal("100"));
map.put("total_fee", Integer.parseInt(decimalFormat.format(big)));
map.put("spbill_create_ip", RandomUtil.getHostIp());
if(billsId==""){
map.put("notify_url", SHOW_NOTIFY_URL);
}else{
map.put("notify_url", NOTIFY_URL);
}
//根据算法生成签名
String sign = Md5EncryptUtil.getWechatSign(map, KEY_APPSECRET);
map.put("sign", sign);
//将参数转换成xml
String paramXML = XMLParser.converterPayPalm(map);
//通过HTTPS请求调用微信的统一下单请求
String responseXML = HttpProtocolUtil.httpPost(PREPAYURL, paramXML);
Map<Object,Object> returnData = new HashMap<>();
//检验API返回的数据里面的签名是否合法,避免数据在传输的过程中被第三方篡改.
Boolean bo = Md5EncryptUtil.checkWechatResponseSign(responseXML,KEY_APPSECRET);
if (!bo) {
returnData.put("status",1);
return returnData;
}
//将返回数据XML转为Map格式
Map<Object, Object> responseMap =XMLParser.xmlString2Map(responseXML);
PaymentTradePO paymentTradePO;
if (responseMap.get("return_code").toString().contains("SUCCESS") && responseMap.get("result_code").toString().contains("SUCCESS")) {
paymentTradePO = new PaymentTradePO();
paymentTradePO.setTradeId(map.get("out_trade_no").toString());
paymentTradePO.setPayType(PaymentTypeEnum.WECHAT_PAY.getType());
paymentTradePO.setPrice(new BigDecimal(total));
paymentTradePO.setUserId(loginUserId);
paymentTradePO.setSuccessReturn(PaySuccessReturnEnum.IS_NOT_RETURN.getType());
paymentTradePO.setTradeStatus(PayStatusEnum.WAIT_PAY.getType());
paymentTradePO.setCreateTime(new Date());
paymentTradePO.setStatus(DataStatusEnum.NORMAL.getType());
paymentTradePO.setFeeType(feeType);
if(StringUtils.isNotEmpty(billsId)){
paymentTradePO.setFeeId(billsId);
}
//统一下单成功后保存商户后台系统的订单表
iWechatPayDao.save(SqlUtil.durableData(paymentTradePO, PlatformConstants.TABLE_SAVE));
//处理返回APP端的参数及生成sign
Map<Object,Object> resultMap = new HashMap<>();
if(StringUtils.isNotEmpty(openId)){
resultMap.put("appId", WECHAT_OFFICIAL_APPID);
resultMap.put("signType", "MD5");
resultMap.put("package",String.format("%s%s","prepay_id=",responseMap.get("prepay_id").toString()));
resultMap.put("timeStamp", DateUtil.getSecondTimestamp(new Date()));
resultMap.put("nonceStr",RandomUtil.getRandomStringByLength(32));
}else{
resultMap.put("appid", APPID);
resultMap.put("partnerid",MCHID);
resultMap.put("prepayid",responseMap.get("prepay_id").toString());
resultMap.put("package",PACKAGE);
resultMap.put("timestamp", DateUtil.getSecondTimestamp(new Date()));
resultMap.put("noncestr",RandomUtil.getRandomStringByLength(32));
}
//根据算法生成签名
String returnSign = Md5EncryptUtil.getWechatSign(resultMap, KEY_APPSECRET);
if(StringUtils.isNotEmpty(openId)){
resultMap.put("paySign",returnSign);
}else{
resultMap.put("sign",returnSign);
}
//status 0代表统一下单成功 1代表微信系统后台返回sign不合法 2代表与微信后台交互其它原因
resultMap.put("status",0);
resultMap.put("tradeId",map.get("out_trade_no").toString());
return resultMap;
}
if(responseMap.get("return_msg")!=null){
Map<Object,Object> returnMsg = new HashMap<>();
returnMsg.put("return_msg",responseMap.get("return_msg"));
returnMsg.put("status",2);
return returnMsg;
}
return null;
} catch (Exception e) {
throw new DataAccessException("【App_微信支付】统一下单失败", e);
}
}
生成签名的方法
public static String getWechatSign(Map<Object,Object> map,String key){
ArrayList<String> list = new ArrayList<String>();
for(Map.Entry<Object,Object> entry:map.entrySet()){
if(entry.getValue()!=""){
list.add(entry.getKey() + "=" + entry.getValue() + "&");
}
}
int size = list.size();
String [] arrayToSort = list.toArray(new String[size]);
Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
StringBuilder sb = new StringBuilder();
for(int i = 0; i < size; i ++) {
sb.append(arrayToSort[i]);
}
String result = sb.toString();
result += "key=" + key;
LOG.error("sign Before MD5:" + result);
result = md5(result).toUpperCase();
LOG.error("sign Result:" + result);
return result;
}
验签工具类
public static boolean checkWechatResponseSign(String responseString,String key) throws DocumentException {
// Map<Object,Object> map = XMLParser.getMapFromXML(responseString, "xml");
Map<Object, Object> map = XMLParser.xmlString2Map(responseString);
String signFromAPIResponse = map.get("sign")==null ? null:map.get("sign").toString();
if(signFromAPIResponse == null || signFromAPIResponse.equals("")){
LOG.error("API返回的数据签名数据不存在,有可能被第三方篡改!!!");
return false;
}
LOG.error("服务器回包里面的签名是:" + signFromAPIResponse);
//清掉返回数据对象里面的Sign数据
map.put("sign","");
//将API返回的数据根据用签名算法进行计算新的签名,用来跟API返回的签名进行比较
String signForAPIResponse = getWechatSign(map, key);
if(!signForAPIResponse.equals(signFromAPIResponse)){
//签名验不过,表示这个API返回的数据有可能已经被篡改了
LOG.error("API返回的数据签名验证不通过,有可能被第三方篡改!!!");
return false;
}
LOG.error("恭喜,API返回的数据签名验证通过!!!");
return true;
}
将map参数转换成XML工具类
public static String converterPayPalm(Map<Object, Object> dataMap) {
StringBuilder strBuilder = new StringBuilder();
try {
strBuilder.append("<xml>");
Set<Object> objSet = dataMap.keySet();
for (Object key : objSet) {
if (key == null) {
continue;
}
//strBuilder.append("\n");
strBuilder.append("<").append(key.toString()).append(">");
Object value = dataMap.get(key);
strBuilder.append(coverter(value).trim());
strBuilder.append("</").append(key.toString()).append(">");
}
strBuilder.append("</xml>");
} catch (Exception e) {
LOG.error("MAP转换XML异常:" + e);
}
return strBuilder.toString();
}
将XML转为MAP
public static Map<Object,Object> xmlString2Map(String xmlStr){
Map<Object,Object> map = new HashMap<Object,Object>();
Document doc;
try {
doc = DocumentHelper.parseText(xmlStr);
Element el = doc.getRootElement();
map = recGetXmlElementValue(el,map);
} catch (DocumentException e) {
e.printStackTrace();
}
return map;
}
JSAPI支付获取openId
public MessageVO getOpenId(String code){
Map<String, Object> map = new HashMap<>();
String status = "1";
String msg = "ok";
String requestUrl = WECHAT_OFFICIAL_REQUEST_URL +"?appid="+WECHAT_OFFICIAL_APPID+"&secret="+WECHAT_OFFICIAL_SECRET+"&code="+code+"&grant_type=authorization_code";
try {
if(StringUtils.isBlank(code)){
status = "0";//失败状态
msg = "code为空";
}else {
System.out.println(requestUrl);
// 发起GET请求获取凭证
JSONObject jsonObject = HttpProtocolUtil.httpsRequest(requestUrl, "GET", null);
if (jsonObject != null) {
try {
map.put("openid", jsonObject.getString("openid"));
} catch (JSONException e) {
// 获取token失败
status = "0";
msg = "code无效";
}
}else {
status = "0";
msg = "code无效";
}
}
map.put("status", status);
map.put("msg", msg);
} catch (Exception e) {
throw new DataAccessException("【微信公众号_停车场】获取openId失败",e);
}
return new MessageVO(BaseErrorCodeEnum.SUCCESS.getErrorCode(),map);
}
JSAPI获取微信授权信息
public MessageVO getJsapiTicket(String url) throws Exception {
url = URLDecoder.decode(url, "UTF-8");
Map<String, String> map = new HashMap<>();
String requestUrl = "https://api.weixin.qq.com/cgi-bin/token"+"?appid="+WECHAT_OFFICIAL_APPID+"&secret="+WECHAT_OFFICIAL_SECRET+"&grant_type=client_credential";
try {
String ticket = "";
//先从缓存中取access_token和ticket其有效期为两个小时,如取不到再调接口获取
ticket = redisService.get(RedisConstant.WECHAT_OFFICIAL_TICKET);
//url = "http://develop.wx.jia-r.com/?code=0018eRlg2PT5xI0oGfmg2DZElg28eRlZ&state=";
if(StringUtils.isEmpty(ticket)){
//发起GET请求获取凭证
JSONObject jsonObject = HttpProtocolUtil.httpsRequest(requestUrl, "GET", null);
if (jsonObject != null) {
String accessToken = jsonObject.getString("access_token");
if(StringUtils.isNotEmpty(accessToken)){
redisService.set(RedisConstant.WECHAT_OFFICIAL_ACCESS_TOKEN,RedisConstant.WECHAT_OFFICIAL_TIME,accessToken);
}
String requestSecondUrl = "https://api.weixin.qq.com/cgi-bin/ticket/getticket"+"?access_token="+accessToken+"&type=JSAPI";
JSONObject jsonObjectSecond = HttpProtocolUtil.httpsRequest(requestSecondUrl, "GET", null);
if(jsonObjectSecond != null){
ticket = jsonObjectSecond.getString("ticket");
if(StringUtils.isNotEmpty(ticket)){
redisService.set(RedisConstant.WECHAT_OFFICIAL_TICKET,RedisConstant.WECHAT_OFFICIAL_TIME,ticket);
}
}
}
}
map = Sha1EncryptUtil.getSign(ticket, url);
} catch (Exception e) {
throw new DataAccessException("【微信公众号_停车场】获取ticket失败",e);
}
return new MessageVO(BaseErrorCodeEnum.SUCCESS.getErrorCode(),map);
}
微信支付成功回调代码
@ResponseBody
@RequestMapping("/payResultNotice")
public String payResultNotice(HttpServletRequest request) {
try {
System.out.print("微信支付回调获取数据开始");
LOG.info("微信支付回调获取数据开始");
LOG.error("【微信支付回调获取数据开始】");
String inputLine;
String notityXml = "";
try {
while ((inputLine = request.getReader().readLine()) != null) {
notityXml += inputLine;
}
request.getReader().close();
} catch (Exception e) {
LOG.info("xml获取失败:" + e);
throw new Exception(e);
}
System.out.println("接收到的报文:" + notityXml);
String projectId = "";
AppPayParkFeeNoticeVO payParkFeeNoticeVO = wechatPayServiceClient.payResultNotice(notityXml);
return payParkFeeNoticeVO.getWechatReturnNotice();
} catch (Exception e) {
throw new DataAccessException("【App_微信支付】API返回的数据异常", e);
}
}