微信扫码支付,回调和退款(附带完整代码)

微信扫码支付


  最近重构项目时,负责了支付模块,微信扫码支付(NATIVE)和 支付宝扫码支付,也是第一次接触,虽然根据官方文档和一些博客写出来了,但是遇到的问题却很多,走了很多弯路,浪费了很多精力和时间,抽出时间来记录一下,以后难免还是会用到。

支付宝扫码支付传送门


微信API

SDK与DEMO下载微信扫码支付,回调和退款(附带完整代码)_第1张图片

微信开发流程:


  开发前,商户必须在公众平台后台设置支付回调URL。URL实现的功能:接收用户扫码后微信支付系统回调的productidopenid


官网提供的流程图:
微信扫码支付,回调和退款(附带完整代码)_第2张图片

业务流程说明:

		(1)商户后台系统根据微信支付规定格式生成二维码(规则见下文),展示给用户扫码。
		(2)用户打开微信“扫一扫”扫描二维码,微信客户端将扫码内容发送到微信支付系统。
		(3)微信支付系统收到客户端请求,发起对商户后台系统支付回调URL的调用。调用请求将带productid和用户的openid等参数,并要求商户系统返回交数据包,详细请见"本节3.1回调数据输入参数"
		(4)商户后台系统收到微信支付系统的回调请求,根据productid生成商户系统的订单。
		(5)商户系统调用微信支付【统一下单API】请求下单,获取交易会话标识(prepay_id)
		(6)微信支付系统根据商户系统的请求生成预支付交易,并返回交易会话标识(prepay_id)。
		(7)商户后台系统得到交易会话标识prepay_id(2小时内有效)。
		(8)商户后台系统将prepay_id返回给微信支付系统。返回数据见"本节3.2回调数据输出参数"
		(9)微信支付系统根据交易会话标识,发起用户端授权支付流程。
		(10)用户在微信客户端输入密码,确认支付后,微信客户端提交支付授权。
		(11)微信支付系统验证后扣款,完成支付交易。
		(12)微信支付系统完成支付交易后给微信客户端返回交易结果,并将交易结果通过短信、微信消息提示用户。微信客户端展示支付交易结果页面。
		(13)微信支付系统通过发送异步消息通知商户后台系统支付结果。商户后台系统需回复接收情况,通知微信后台系统不再发送该单的支付通知。
		(14)未收到支付通知的情况,商户后台系统调用【查询订单API】。
		(15)商户确认订单已支付后给用户发货

整个流程看下来其实就是用户通过订单信息和微信提供的统一下单API的接口直接进行http请求,然后微信会生成一个预支付交易,之后后返回一个url,将这里的url埋入二维码就可以了。到这里二维码就生成了,各位就可以消费了,消费之后微信会调支付回调接口,通知支付结果。这里提到的支付接口和回调接口需要在微信商户平台进行配置。

获取二维码:

将二维码返回前端展示 也二维码以文件格式输出到本地

private Map<String, Object> getPayCodeByWeChat(PayCodeRequest payCodeRequest) {
    log.info("获取微信二维码请求*******");
    Map<String, Object> resultMap = new HashMap<String, Object>();
    String currentUserID = null;
    String orderID = null;
    ResultParams makeCode = null;
    BizContentByWeChatPay bizContent = null;
    try {
        orderID = payCodeRequest.getOrderID();
        currentUserID = payCodeRequest.getCurrentUserID();
        SystemConfig queryOneSystemConfig = orderPayService.queryOneSystemConfig("cm2_weChatAccount");

        PayRecord queryOrderPayRecordByOrderID = orderPayService.queryPayRecordByOrderID(orderID);
        if (null != queryOrderPayRecordByOrderID && OrderPayStatusEnum.PAID.getValue() == queryOrderPayRecordByOrderID.getPayStatus()) {
            log.info("支付的类型:" + queryOrderPayRecordByOrderID.getPayType());
            log.debug("订单已存在支付过记录");
            resultMap.put("code", -20003);
            resultMap.put("desc", "订单已支付,无法再次操作");
            return resultMap;
        }
        bizContent = new BizContentByWeChatPay();
        String payAmount = payCodeRequest.getPayAmount().replace(",", "");

        bizContent.setBody(payCodeRequest.getValidityPeriod());
        bizContent.setNonce_str(CommonUtils.genId());
        bizContent.setOut_trade_no(CommonUtils.genPayId());
        bizContent.setSubject(payCodeRequest.getProductName());
        bizContent.setTotal_amount(new BigDecimal(payAmount));

        String configValue = queryOneSystemConfig.getConfigValue();

        makeCode = WeChatPayUtil.makeCode(configValue, bizContent);


        if (null == makeCode || !makeCode.getReturn_code().equals("SUCCESS") || !makeCode.getResult_code().equals("SUCCESS")) {
            resultMap.put("code", -20018);
            resultMap.put("desc", "获取微信支付二维码失败");
            payLogService.createPayLog(currentUserID, new Gson().toJson(bizContent), orderID, new Gson().toJson(makeCode), -1);
            return resultMap;
        }

        queryOneSystemConfig = orderPayService.queryOneSystemConfig("qrCodebyAddress");

        String code_url = makeCode.getCode_url();
        // 生成二维码
        String base64Code = QRCodeUtil.getBase64Code(code_url);
        // 有记录,且非已支付
         
        if (null != queryOrderPayRecordByOrderID) {
            queryOrderPayRecordByOrderID.setOrderID(payCodeRequest.getOrderID());
            queryOrderPayRecordByOrderID.setPayType(OrderPayTypeEnum.WECHAT.getValue());
            queryOrderPayRecordByOrderID.setSign(makeCode.getSign());
            queryOrderPayRecordByOrderID.setEditor(payCodeRequest.getCurrentUserID());
            queryOrderPayRecordByOrderID.setEditTime(new Date());
            queryOrderPayRecordByOrderID.setPayStatus(OrderPayStatusEnum.UNPAID.getValue());
            queryOrderPayRecordByOrderID.setQr_code(code_url);
            queryOrderPayRecordByOrderID.setNonce_str(bizContent.getNonce_str());
            queryOrderPayRecordByOrderID.setReturn_nonce_str(makeCode.getNonce_str());
            long longValue = (new BigDecimal(payAmount).multiply(new BigDecimal("100"))).longValue();
            queryOrderPayRecordByOrderID.setPayAmount(longValue);
            queryOrderPayRecordByOrderID.setPrepay_id(makeCode.getPrepay_id());
            queryOrderPayRecordByOrderID.setTrade_type(makeCode.getTrade_type());
            queryOrderPayRecordByOrderID.setOut_trade_no(bizContent.getOut_trade_no());
            orderPayService.updateOrderPayRecord(queryOrderPayRecordByOrderID);
        } else {
            PayRecord payRecord = new PayRecord();
            payRecord.setOrderID(payCodeRequest.getOrderID());
            payRecord.setPayType(OrderPayTypeEnum.WECHAT.getValue());
            payRecord.setSign(makeCode.getSign());
            payRecord.setOperator(payCodeRequest.getCurrentUserID());
            payRecord.setCreateTime(new Date());
            payRecord.setPayStatus(OrderPayStatusEnum.UNPAID.getValue());
            long longValue = (new BigDecimal(payAmount).multiply(new BigDecimal("100"))).longValue();
            payRecord.setPayAmount(longValue);
            payRecord.setQr_code(code_url);
            payRecord.setNonce_str(bizContent.getNonce_str());
            payRecord.setReturn_nonce_str(makeCode.getNonce_str());
            payRecord.setPrepay_id(makeCode.getPrepay_id());
            payRecord.setTrade_type(makeCode.getTrade_type());
            payRecord.setOut_trade_no(bizContent.getOut_trade_no());
            orderPayService.createOrderPayRecord(payRecord);
        }
        resultMap.put("code", 0);
        resultMap.put("desc", "获取成功");
        resultMap.put("data", base64Code);
        resultMap.put("out_trade_no", bizContent.getOut_trade_no());
        payLogService.createPayLog(currentUserID, new Gson().toJson(bizContent), orderID, new Gson().toJson(makeCode), 0);
        return resultMap;
    } catch (Exception e) {
        log.error("获取微信二维码处理异常*******", e);
        resultMap.put("code", -20018);
        resultMap.put("desc", "获取微信支付二维码失败");
        payLogService.createPayLog(currentUserID, new Gson().toJson(bizContent), orderID, "获取微信二维码处理异常", 1);
        return resultMap;
    }

}

微信支付生成二维码和签名


签名生成的通用步骤如下:

第一步,设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。 
特别注意以下重要规则: 

  ◆ 参数名ASCII码从小到大排序(字典序); 
  ◆ 如果参数的值为空不参与签名; 
  ◆ 参数名区分大小写; 
  ◆ 验证调用返回或微信主动通知签名时,传送的sign参数不参与签名,将生成的签名与该sign值作校验。 
  ◆ 微信接口可能增加字段,验证签名时必须支持增加的扩展字段 

第二步,在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。

 key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置

代码展示:

  public static ResultParams makeCode(String weChatAccountData, BizContentByWeChatPay bizContent){
   log.info("获取微信支付二维码********");
log.info("aliPayAccountData:"+weChatAccountData);
   log.info("bizContent:"+new Gson().toJson(bizContent));
   try{
      
      WeChatAccount weChatAccount = new Gson().fromJson(weChatAccountData, WeChatAccount.class);
      String app_id = weChatAccount.getApp_id();
      String merchant_number = weChatAccount.getMerchant_number();
      String notifyUrl = weChatAccount.getNotifyUrl();
      String secret_key = weChatAccount.getSecret_key();
      String pay_path = weChatAccount.getPay_path();
      //商户订单号,也是订单号
      String out_trade_no = bizContent.getOut_trade_no();
      String body = bizContent.getBody();
      BigDecimal total_amount = bizContent.getTotal_amount();
      String nonce_str = bizContent.getNonce_str();
      int intAmount = total_amount.multiply(new BigDecimal("100")).intValue();
      String timeout_express = weChatAccount.getTimeout_express();
      
       //签名所需要的参数
       SortedMap<String,String> parameters = new TreeMap<String,String>();
       parameters.put("appid",app_id);
       //商户号
       parameters.put("mch_id",merchant_number);
       parameters.put("body",body);
       //SAPI -JSAPI支付 NATIVE -Native支付 APP -APP支付
       parameters.put("trade_type","NATIVE");
       //商户订单号
       parameters.put("out_trade_no",out_trade_no);
       //订单总金额(单位分)
       parameters.put("total_fee",String.valueOf(intAmount));
       //终端IP //支持IPV4和IPV6两种格式的IP地址。调用微信支付API的机器IP
       parameters.put("spbill_create_ip",GetIp());
       //商品ID trade_type=NATIVE时,此参数必传。此参数为二维码中包含的商品ID,商户自行定义。
       parameters.put("product_id",out_trade_no);
       //异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数
       parameters.put("notify_url",notifyUrl);
       //随机字符串
       parameters.put("nonce_str",nonce_str);
      
       //接口所需要的参数
       PayParams order = new PayParams();
       order.setAppid(app_id);
       order.setMch_id(merchant_number);
       order.setBody(body);
       order.setTrade_type("NATIVE");
       order.setOut_trade_no(out_trade_no);
       order.setTotal_fee(intAmount);
       order.setSpbill_create_ip(GetIp());
       order.setProduct_id(out_trade_no);
       order.setNotify_url(notifyUrl);
       order.setNonce_str(nonce_str);
       if(null!=timeout_express && !"".equals(timeout_express)){
         Calendar c = Calendar.getInstance();
         c.setTime(new Date());
         int parseTime = Integer.parseInt(timeout_express);
         c.add(Calendar.SECOND, parseTime);
         long time = c.getTime().getTime();
         parameters.put("time_expire", String.valueOf(time));
         order.setTime_expire(String.valueOf(time));
       }
       //签名 
       String sign = WXPayUtil.getPaySign(parameters,secret_key);
   order.setSign(sign);
       
       //调用微信支付统一下单接口,让微信也生成一个预支付订单
       String xmlResult = HttpUtil.sendPost(pay_path,XMLUtil.convertToXml(order));
       log.info("xmlResult:"+xmlResult);
       
       //把返回的xml字符串转成对象
       ResultParams entity = (ResultParams)XMLUtil.convertXmlStrToObject(ResultParams.class,xmlResult);
       log.info("ResultParams:"+new Gson().toJson(entity));
       return entity;
      
   }catch(Exception e){
      log.info("获取微信支付二维码,调用接口异常********",e);
      return null;
   }
  }

微信支付工具类 (微信提供的demo 可以下载使用)

public class WXPayUtil {
    private static final String SYMBOLS = "xxx";

    private static final Random RANDOM = new SecureRandom();

    /**
     * XML格式字符串转换为Map
     *
     * @param strXML XML字符串
     * @return XML数据转换后的Map
     * @throws Exception
     */
    public static Map<String, String> xmlToMap(String strXML) throws Exception {
        try {
            Map<String, String> data = new HashMap<String, String>();
            DocumentBuilder documentBuilder = WXPayXmlUtil.newDocumentBuilder();
            InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
            org.w3c.dom.Document doc = documentBuilder.parse(stream);
            doc.getDocumentElement().normalize();
            NodeList nodeList = doc.getDocumentElement().getChildNodes();
            for (int idx = 0; idx < nodeList.getLength(); ++idx) {
                Node node = nodeList.item(idx);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    org.w3c.dom.Element element = (org.w3c.dom.Element) node;
                    data.put(element.getNodeName(), element.getTextContent());
                }
            }
            try {
                stream.close();
            } catch (Exception ex) {
                // do nothing
            }
            return data;
        } catch (Exception ex) {
           log.error("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
            throw ex;
        }

    }

    /**
     * 将Map转换为XML格式的字符串
     *
     * @param data Map类型数据
     * @return XML格式的字符串
     * @throws Exception
     */
    public static String mapToXml(Map<String, String> data) {
        log.info("mapToXml开始转换**************");
        StringWriter writer = null;
        try {
            org.w3c.dom.Document document = WXPayXmlUtil.newDocument();
            org.w3c.dom.Element root = document.createElement("xml");
            document.appendChild(root);
            for (String key : data.keySet()) {
                String value = data.get(key);
                if (value == null) {
                    value = "";
                }
                value = value.trim();
                org.w3c.dom.Element filed = document.createElement(key);
                filed.appendChild(document.createTextNode(value));
                root.appendChild(filed);
            }
            TransformerFactory tf = TransformerFactory.newInstance();
            Transformer transformer = tf.newTransformer();
            DOMSource source = new DOMSource(document);
            transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
            writer = new StringWriter();
            StreamResult result = new StreamResult(writer);
            transformer.transform(source, result);
            String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
            log.info("转换完:", output);
            writer.flush();
            return output;
        } catch (Exception ex) {
            log.error("mapToXml转换异常", ex);
            return null;
        } finally {
            if (null != writer) {
                try {
                    writer.close();
                } catch (IOException e) {
                    log.error("关闭writer异常", e);
                }
            }
        }

    }


    /**
     * 生成带有 sign 的 XML 格式字符串
     *
     * @param data Map类型数据
     * @param key  API密钥
     * @return 含有sign字段的XML
     */
    public static String generateSignedXml(final Map<String, String> data, String key) throws Exception {
        return generateSignedXml(data, key, WXPayConstants.SignType.MD5);
    }

    /**
     * 生成带有 sign 的 XML 格式字符串
     *
     * @param data     Map类型数据
     * @param key      API密钥
     * @param signType 签名类型
     * @return 含有sign字段的XML
     */
    public static String generateSignedXml(final Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception {
        String sign = generateSignature(data, key, signType);
        data.put(WXPayConstants.FIELD_SIGN, sign);
        return mapToXml(data);
    }


    /**
     * 判断签名是否正确
     *
     * @param xmlStr XML格式数据
     * @param key    API密钥
     * @return 签名是否正确
     * @throws Exception
     */
    public static boolean isSignatureValid(String xmlStr, String key) throws Exception {
        Map<String, String> data = xmlToMap(xmlStr);
        if (!data.containsKey(WXPayConstants.FIELD_SIGN)) {
            return false;
        }
        String sign = data.get(WXPayConstants.FIELD_SIGN);
        return generateSignature(data, key).equals(sign);
    }

    /**
     * 判断签名是否正确,必须包含sign字段,否则返回false。使用MD5签名。
     *
     * @param data Map类型数据
     * @param key  API密钥
     * @return 签名是否正确
     * @throws Exception
     */
    public static boolean isSignatureValid(Map<String, String> data, String key) throws Exception {
        return isSignatureValid(data, key, WXPayConstants.SignType.MD5);
    }

    /**
     * 判断签名是否正确,必须包含sign字段,否则返回false。
     *
     * @param data     Map类型数据
     * @param key      API密钥
     * @param signType 签名类型,默认为MD5,支持HMAC-SHA256和MD5。
     * @return 签名是否正确
     * @throws Exception
     */
    public static boolean isSignatureValid(Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception {
        log.info("开始验证******");
        if (!data.containsKey(WXPayConstants.FIELD_SIGN)) {
            log.debug("验证失败******");
            return false;
        }
        String sign = data.get(WXPayConstants.FIELD_SIGN);
        return generateSignature(data, key, signType).equals(sign);
    }

    /**
     * 生成签名
     *
     * @param data 待签名数据
     * @param key  API密钥
     * @return 签名
     */
    public static String generateSignature(final Map<String, String> data, String key) throws Exception {
        return generateSignature(data, key, WXPayConstants.SignType.MD5);
    }

    /**
     * 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
     *
     * @param data     待签名数据
     * @param key      API密钥
     * @param signType 签名方式
     * @return 签名
     */
    public static String generateSignature(final Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception {
        Set<String> keySet = data.keySet();
        String[] keyArray = keySet.toArray(new String[keySet.size()]);
        Arrays.sort(keyArray);
        StringBuilder sb = new StringBuilder();
        for (String k : keyArray) {
            if (k.equals(WXPayConstants.FIELD_SIGN)) {
                continue;
            }
            if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名
                sb.append(k).append("=").append(data.get(k).trim()).append("&");
        }
        sb.append("key=").append(key);
        if (WXPayConstants.SignType.MD5.equals(signType)) {
            return MD5(sb.toString()).toUpperCase();
        } else if (WXPayConstants.SignType.HMACSHA256.equals(signType)) {
            return HMACSHA256(sb.toString(), key);
        } else {
            throw new Exception(String.format("Invalid sign_type: %s", signType));
        }
    }


    /**
     * 获取随机字符串 Nonce Str
     *
     * @return String 随机字符串
     */
    public static String generateNonceStr() {
        char[] nonceChars = new char[32];
        for (int index = 0; index < nonceChars.length; ++index) {
            nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
        }
        return new String(nonceChars);
    }


    /**
     * 生成 MD5
     *
     * @param data 待处理数据
     * @return MD5结果
     */
    public static String MD5(String data) throws Exception {
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] array = md.digest(data.getBytes("UTF-8"));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
        }
        return sb.toString().toUpperCase();
    }

    /**
     * 生成 HMACSHA256
     *
     * @param data 待处理数据
     * @param key  密钥
     * @return 加密结果
     * @throws Exception
     */
    public static String HMACSHA256(String data, String key) throws Exception {
        Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
        SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
        sha256_HMAC.init(secret_key);
        byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
        }
        return sb.toString().toUpperCase();
    }

    /**
     * 日志
     *
     * @return
     */
  /*  public static log getlog() {
        log log = logFactory.getlog("wxpay java sdk");
        return log;
    }*/

    /**
     * 获取当前时间戳,单位秒
     *
     * @return
     */
    public static long getCurrentTimestamp() {
        return System.currentTimeMillis() / 1000;
    }

    /**
     * 获取当前时间戳,单位毫秒
     *
     * @return
     */
    public static long getCurrentTimestampMs() {
        return System.currentTimeMillis();
    }

    /**
     * 获取签名
     *
     * @param obj
     * @return
     * @throws IllegalAccessException
     * @throws IOException
     */
    public static String getPaySign(Object obj, String secret_key) throws Exception {
        StringBuilder sb = new StringBuilder();
        //把对象转为TreeMap集合(按照key的ASCII 码从小到大排序)
        TreeMap<String, Object> map = (TreeMap) obj;
        Set<Map.Entry<String, Object>> entrySet = map.entrySet();
        //遍历键值对
        for (Map.Entry<String, Object> entry : entrySet) {
            //如果值为空,不参与签名
            if (entry.getValue() != null) {
                sb.append(entry.getKey() + "=" + entry.getValue() + "&");
            }
        }
        //最后拼接商户的API密钥
        String stringSignTemp = sb.toString() + "key=" + secret_key;
        return MD5(stringSignTemp);
    }


    /**
     * 处理 HTTPS API返回数据,转换成Map对象。return_code为SUCCESS时,验证签名。
     *
     * @param xmlStr            API返回的XML格式数据
     * @param weChatAccountData 微信账号信息 json
     * @return Map类型数据
     * @throws Exception
     */
    public static Map<String, String> processResponseXml(String weChatAccountData, String xmlStr) throws Exception {
        log.info("开始处理xml文件***转换Map***");
        String RETURN_CODE = "return_code";
        String return_code;
        Map<String, String> respData = WXPayUtil.xmlToMap(xmlStr);
        log.info("转换完的map:" + respData);
        if (respData.containsKey(RETURN_CODE)) {
            return_code = respData.get(RETURN_CODE);
        } else {
            throw new Exception(String.format("No `return_code` in XML: %s", xmlStr));
        }

        if (return_code.equals(WXPayConstants.FAIL)) {
            return respData;
        } else if (return_code.equals(WXPayConstants.SUCCESS)) {
            WeChatAccount weChatAccount = new Gson().fromJson(weChatAccountData, WeChatAccount.class);
            if (isResponseSignatureValid(respData, weChatAccount.getSecret_key())) {
                log.debug("验证成功******");
                return respData;
            } else {
                log.debug("验证异常******");
                throw new Exception(String.format("Invalid sign value in XML: %s", xmlStr));
            }
        } else {
            throw new Exception(String.format("return_code value %s is invalid in XML: %s", return_code, xmlStr));
        }
    }

    /**
     * 判断xml数据的sign是否有效,必须包含sign字段,否则返回false。
     *
     * @param reqData 向wxpay post的请求数据
     * @return 签名是否有效
     * @throws Exception
     */
    public static boolean isResponseSignatureValid(Map<String, String> reqData, String secret_key) throws Exception {
        // 返回数据的签名方式和请求中给定的签名方式是一致的
        return isSignatureValid(reqData, secret_key, WXPayConstants.SignType.MD5);
    }
    
   /* public static String genSandBoxSign(String mch_id,String nonce_str,String key){
       Map param = new HashMap();
       
       try {
          param.put("mch_id", mch_id);// 商户号
           param.put("nonce_str", nonce_str);// 随机字符串
           param.put("sign", WXPayUtil.generateSignature(param, key,WXPayConstants.SignType.MD5));// 沙盒测试貌似只支持MD5加密
           //String xml = mapToXml(param);
          TestM m = new TestM();
          m.setMch_id(mch_id);
          m.setNonce_str(nonce_str);
          m.setSign(param.get("sign"));
           String xmlResult = requestWithoutCert("/sandboxnew/pay/getsignkey",param,1000,1000);
           return xmlResult;
       } catch (Exception e) {
           e.printStackTrace();
           return null;
       }
    }
    
    public static String requestWithoutCert(String urlSuffix, Map reqData,
            int connectTimeoutMs, int readTimeoutMs) throws Exception {
      String msgUUID = reqData.get("nonce_str");
      String reqBody = WXPayUtil.mapToXml(reqData);
      
      String resp = requestWithoutCert(urlSuffix, msgUUID, reqBody, connectTimeoutMs, readTimeoutMs, autoReport);
      return resp;
    }
    
    */

    /**
     * 可重试的,非双向认证的请求
     *
     * @return
     *//*
    public static String requestWithoutCert(String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs,  boolean autoReport) throws Exception {
        return request(urlSuffix, uuid, data, connectTimeoutMs, readTimeoutMs, false, autoReport);
    }
    
    private static String request(String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean useCert, boolean autoReport) throws Exception {
        Exception exception = null;
        long elapsedTimeMillis = 0;
        long startTimestampMs = WXPayUtil.getCurrentTimestampMs();
        boolean firstHasDnsErr = false;
        boolean firstHasConnectTimeout = false;
        boolean firstHasReadTimeout = false;
        DomainInfo domainInfo = config.getWXPayDomain().getDomain(config);
        if(domainInfo == null){
            throw new Exception("WXPayConfig.getWXPayDomain().getDomain() is empty or null");
        }
        try {
            String result = requestOnce(domainInfo.domain, urlSuffix, uuid, data, connectTimeoutMs, readTimeoutMs, useCert);
            elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
            config.getWXPayDomain().report(domainInfo.domain, elapsedTimeMillis, null);
            WXPayReport.getInstance(config).report(
                    uuid,
                    elapsedTimeMillis,
                    domainInfo.domain,
                    domainInfo.primaryDomain,
                    connectTimeoutMs,
                    readTimeoutMs,
                    firstHasDnsErr,
                    firstHasConnectTimeout,
                    firstHasReadTimeout);
            return result;
        }
        catch (UnknownHostException ex) {  // dns 解析错误,或域名不存在
            exception = ex;
            firstHasDnsErr = true;
            elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
            WXPayUtil.getlog().warn("UnknownHostException for domainInfo {}", domainInfo);
            WXPayReport.getInstance(config).report(
                    uuid,
                    elapsedTimeMillis,
                    domainInfo.domain,
                    domainInfo.primaryDomain,
                    connectTimeoutMs,
                    readTimeoutMs,
                    firstHasDnsErr,
                    firstHasConnectTimeout,
                    firstHasReadTimeout
            );
        }
        catch (ConnectTimeoutException ex) {
            exception = ex;
            firstHasConnectTimeout = true;
            elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
            WXPayUtil.getlog().warn("connect timeout happened for domainInfo {}", domainInfo);
            WXPayReport.getInstance(config).report(
                    uuid,
                    elapsedTimeMillis,
                    domainInfo.domain,
                    domainInfo.primaryDomain,
                    connectTimeoutMs,
                    readTimeoutMs,
                    firstHasDnsErr,
                    firstHasConnectTimeout,
                    firstHasReadTimeout
            );
        }
        catch (SocketTimeoutException ex) {
            exception = ex;
            firstHasReadTimeout = true;
            elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
            WXPayUtil.getlog().warn("timeout happened for domainInfo {}", domainInfo);
            WXPayReport.getInstance(config).report(
                    uuid,
                    elapsedTimeMillis,
                    domainInfo.domain,
                    domainInfo.primaryDomain,
                    connectTimeoutMs,
                    readTimeoutMs,
                    firstHasDnsErr,
                    firstHasConnectTimeout,
                    firstHasReadTimeout);
        }
        catch (Exception ex) {
            exception = ex;
            elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
            WXPayReport.getInstance(config).report(
                    uuid,
                    elapsedTimeMillis,
                    domainInfo.domain,
                    domainInfo.primaryDomain,
                    connectTimeoutMs,
                    readTimeoutMs,
                    firstHasDnsErr,
                    firstHasConnectTimeout,
                    firstHasReadTimeout);
        }
        config.getWXPayDomain().report(domainInfo.domain, elapsedTimeMillis, exception);
        throw exception;
    }
    */
    }

class TestM {
    private String mch_id;
    private String nonce_str;
    private String sign;

    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;
    }

}

class DomainInfo {
    public String domain;       //域名
    public boolean primaryDomain;     //该域名是否为主域名。例如:api.mch.weixin.qq.com为主域名

    public DomainInfo(String domain, boolean primaryDomain) {
        this.domain = domain;
        this.primaryDomain = primaryDomain;
    }

    @Override
    public String toString() {
        return "DomainInfo{" +
                "domain='" + domain + '\'' +
                ", primaryDomain=" + primaryDomain +
                '}';
    }
}

扫码支付完成后需要微信回调:
微信扫码支付,回调和退款(附带完整代码)_第3张图片
回调参数:微信扫码支付,回调和退款(附带完整代码)_第4张图片
回调返回:微信扫码支付,回调和退款(附带完整代码)_第5张图片
代码展示:

public Map<String, String> doWeChatNotify() {
    log.info("微信回调支付返回结果:");
    Map<String, String> resultMap = new HashMap<String, String>();
    Map<String, String> params = new HashMap<String, String>();
    InputStream inStream = null;
    ByteArrayOutputStream outSteam = null;
    String orderID = null;
    try {
        inStream = request.getInputStream();
        outSteam = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len = 0;
        while ((len = inStream.read(buffer)) != -1) {
            outSteam.write(buffer, 0, len);
        }
        String resultxml = new String(outSteam.toByteArray(), "utf-8");
        log.info("获取的xml:" + resultxml);
        outSteam.flush();
        SystemConfig queryOneSystemConfig = orderPayService.queryOneSystemConfig("cm2_weChatAccount");
        params = WXPayUtil.processResponseXml(queryOneSystemConfig.getConfigValue(), resultxml);
        log.info("xml转换map结果:" + params);

        /**
         * 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一
         */
        String out_trade_no = params.get("out_trade_no");
        PayRecord queryOrderPayRecordByOrderID = this.orderPayService.queryOrderPayRecordByOut_Trade_NoAndType(out_trade_no, OrderPayTypeEnum.WECHAT.getValue());
        if (null == queryOrderPayRecordByOrderID) {
            resultMap.put("return_code", "FAIL");
            resultMap.put("return_msg", "当前订单未找微信支付记录");
            return resultMap;
        }
        orderID = queryOrderPayRecordByOrderID.getOrderID();
        String return_code = params.get("return_code");
        String return_msg = params.get("return_msg");
        if ("FAIL".equals(return_code)) {
            resultMap.put("return_code", "FAIL");
            resultMap.put("return_msg", return_msg);
            this.payLogService.createPayLogByReceive(orderID, new Gson().toJson(params), 1, "微信异步调用,返回失败");
            return resultMap;
        }
        /**
         * 订单金额(分)
         */
        String total_fee = params.get("total_fee");
        /**
         * 商户号
         */
        //String mch_id = params.get("mch_id");
        String nonce_str = params.get("nonce_str");
        String sign = params.get("sign");
        /**
         * SUCCESS/FAIL
         */
        String result_code = params.get("result_code");
        /**
         * 用户在商户appid下的唯一标识
         */
        String openid = params.get("openid");
        /**
         * 微信支付订单号
         */
        String transaction_id = params.get("transaction_id");
        /**
         * 支付完成的时间
         */
        String time_end = params.get("time_end");
        String trade_type = params.get("trade_type");


        queryOrderPayRecordByOrderID.setPayType(OrderPayTypeEnum.WECHAT.getValue());
        queryOrderPayRecordByOrderID.setComment(queryOrderPayRecordByOrderID.getComment() + "时间:" + new Date() + "==>微信异步数据");
        queryOrderPayRecordByOrderID.setEditTime(new Date());
        Date payTime = null;
        if (null != time_end && !"".equals(time_end)) {
            SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyyMMddhhmm");
            payTime = sDateFormat.parse(time_end);
        }

        queryOrderPayRecordByOrderID.setPayTime(payTime);
        queryOrderPayRecordByOrderID.setPayAmount(Long.parseLong(total_fee));
        queryOrderPayRecordByOrderID.setSign(sign);
        queryOrderPayRecordByOrderID.setBuyer_logon_id(openid);
        queryOrderPayRecordByOrderID.setTrade_type(trade_type);
        /**
         * 交易状态:WAIT_BUYER_PAY(交易创建,等待买家付款)、TRADE_CLO
         */
        if ("SUCCESS".equals(result_code)) {
            queryOrderPayRecordByOrderID.setNonce_str(nonce_str);
            queryOrderPayRecordByOrderID.setPayStatus(OrderPayStatusEnum.PAID.getValue());
            queryOrderPayRecordByOrderID.setTrade_no(transaction_id);
            this.orderPayService.updateOrderPayRecord(queryOrderPayRecordByOrderID);
            resultMap.put("return_code", "SUCCESS");
            resultMap.put("return_msg", "OK");
            this.payLogService.createPayLogByReceive(orderID, new Gson().toJson(params), 0, "微信异步调用成功");
            return resultMap;
        }
        queryOrderPayRecordByOrderID.setPayStatus(OrderPayStatusEnum.PAYMENT_FAILED.getValue());
        this.orderPayService.updateOrderPayRecord(queryOrderPayRecordByOrderID);
        resultMap.put("return_code", "FAIL");
        resultMap.put("return_msg", "微信支付回调失败,返回result_code不为SUCCESS");
        this.payLogService.createPayLogByReceive(orderID, new Gson().toJson(params), 1, "微信异步调用失败");
        return resultMap;
    } catch (Exception e) {
        log.error("微信支付回调返回异常:+", e);
        resultMap.put("return_code", "FAIL");
        resultMap.put("return_msg", "微信支付回调失败");
        this.payLogService.createPayLogByReceive(orderID, new Gson().toJson(params), 1, "微信异步调用异常");
        return resultMap;
    } finally {

        if (null != inStream) {
            try {
                inStream.close();
            } catch (IOException e) {
                log.error("关闭inStream异常");
            }
        }
        if (null != outSteam) {
            try {
                outSteam.close();
            } catch (IOException e) {
                log.error("关闭outSteam异常");
            }
        }
    }
}

微信退款:


退款需要证书的,这个上边提到过,去微信商户平台生成下载,微信是通过微信订单号(transaction_id)来退款的,transaction_id这个参数是支付回调时候返回的,注意保存

private Map<String, Object> getPayCodeByWeChat(PayRecord queryPayRecordByOrderID, long transactionPrice, TradeRefundRequest tradeRefundRequest) {
    log.info("微信退款请求*******");
    Map<String, Object> resultMap = new HashMap<>(16);
    try {
        SystemConfig queryOneSystemConfig = orderPayService.queryOneSystemConfig("cm2_weChatAccount");
        RefundParams refundParams = new RefundParams();
        refundParams.setNonce_str(CommonUtils.genId());
        refundParams.setOut_refund_no(CommonUtils.genId());
        String refund_amount = tradeRefundRequest.getRefund_amount().replace(",", "");
        int intValue = new BigDecimal(refund_amount).multiply(new BigDecimal("100")).intValue();

        refundParams.setRefund_fee(intValue);
        refundParams.setTotal_fee(new BigDecimal(transactionPrice).intValue());
        refundParams.setOut_trade_no(tradeRefundRequest.getOrderID());

        RefundResultParams refundResponse = WeChatPayUtil.refund(queryOneSystemConfig.getConfigValue(), refundParams);
        log.debug("微信退款结果:" + new Gson().toJson(refundResponse));
        if (null == refundResponse || !refundResponse.getReturn_code().equals("SUCCESS") || !refundResponse.getResult_code().equals("SUCCESS")) {
            log.error("微信退款失败*******");
            resultMap.put("code", -20010);
            resultMap.put("desc", "退款失败");
            return resultMap;
        }

        queryPayRecordByOrderID.setRefund_request_no(refundParams.getOut_refund_no());
        queryPayRecordByOrderID.setRefund_amount(new BigDecimal(refund_amount).multiply(new BigDecimal("100")).longValue());
        queryPayRecordByOrderID.setRefundTime(new Date());
        queryPayRecordByOrderID.setRefund_operator(tradeRefundRequest.getCurrentUserID());
        queryPayRecordByOrderID.setRefund_reason(tradeRefundRequest.getRefund_reason());
        queryPayRecordByOrderID.setPayStatus(OrderPayStatusEnum.REFUNDING.getValue());
        orderPayService.updateOrderPayRecord(queryPayRecordByOrderID);
        resultMap.put("code", 0);
        resultMap.put("desc", "操作成功");
        resultMap.put("request_no", queryPayRecordByOrderID.getRefund_request_no());
        resultMap.put("refund_amount", queryPayRecordByOrderID.getRefund_amount());
        return resultMap;
    } catch (Exception e) {
        log.error("微信退款处理异常*******", e);
        resultMap.put("code", -20010);
        resultMap.put("desc", "退款失败");
        return resultMap;
    }

}

你可能感兴趣的:(java,openid)