服务端微信支付(APP和JSAPI支付)相关问题

服务端微信支付相关问题

JSAPI开发文档

jsapi开发文档链接
app开发文档链接

统一下单参数

在开发前需要先申请商户平台申请商户号和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);
        }
    }

猜你喜欢

转载自blog.csdn.net/songjuguang/article/details/89189049