微信开发生成退款申请_微信统一下单回调函数和微信申请退款回调函数可以写成一个吗 - CSDN
  • 微信公众号退款开发

    2019-06-26 00:23:11
    博主是小菜鸟,这篇文章仅是自己开发的随笔记录,不足博友可以指出来,一起进步 1、【微信支付】公众号支付开发者文档链接地址 ...2、微信申请退款需要双向证书, JAVA只需要使用apiclient_cert...

    博主是小菜鸟,这篇文章仅是自己开发的随笔记录,不足博友可以指出来,一起进步

    1、【微信支付】公众号支付开发者文档链接地址

    https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4

    调用微信退款接口,需要发送特定的xml格式字符串到到微信退款接口;

    2、微信申请退款需要双向证书

    JAVA只需要使用apiclient_cert.p12即可,证书从

    https://pay.weixin.qq.com/index.php/core/home/login?return_url=%2F

    微信商户平台-》账户设置-》 API安全 中下载的,下载后解压到本地一个英文命名的文件夹下;

    3、证书解压之后

    如下图,安装证书,双击apiclient_cert.p12,一直下一步到如下页面

    密码为商户号(mch_id),一直下一步,直至提示导入成功,至此证书安装成功。

    4、代码

     4.1工具类(xml、map格式转换以及签名)

    import java.io.ByteArrayInputStream;
    import java.io.InputStream;
    import java.io.StringWriter;
    import java.security.MessageDigest;
    import java.util.Arrays;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Set;
    import java.util.UUID;

    import javax.crypto.Mac;
    import javax.crypto.spec.SecretKeySpec;
    import javax.xml.parsers.DocumentBuilder;
    import javax.xml.parsers.DocumentBuilderFactory;
    import javax.xml.transform.OutputKeys;
    import javax.xml.transform.Transformer;
    import javax.xml.transform.TransformerFactory;
    import javax.xml.transform.dom.DOMSource;
    import javax.xml.transform.stream.StreamResult;

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.w3c.dom.Node;
    import org.w3c.dom.NodeList;

    import com.etom.itoilet.constants.WXPayConstants;
    import com.etom.itoilet.constants.WXPayConstants.SignType;

    /**
    * 微信支付工具类
    *
    * @author hongzm
    *
    * @date 2017年7月17日 上午10:30:00
    */
    public class WXPayUtil {
    /**
    * 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>();
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder documentBuilder = documentBuilderFactory.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) {
        WXPayUtil.getLogger().warn("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) throws Exception {
      DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
      DocumentBuilder documentBuilder= documentBuilderFactory.newDocumentBuilder();
      org.w3c.dom.Document document = documentBuilder.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");
      StringWriter writer = new StringWriter();
      StreamResult result = new StreamResult(writer);
      transformer.transform(source, result);
      String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
      try {
        writer.close();
      }catch (Exception ex) {
      }
      return output;
    }


    /**
    * 生成带有 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, "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, SignType signType) throws Exception {
      String sign = generateSignature(data, key, "MD5");
      data.put("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("sign") ) {
        return false;
      }
      String sign = data.get("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,"MD5");
    }

    /**
    * 判断签名是否正确,必须包含sign字段,否则返回false。
    *
    * @param data Map类型数据
    * @param key API密钥
    * @param signType 签名方式
    * @return 签名是否正确
    * @throws Exception
    */
    public static boolean isSignatureValid(Map<String, String> data, String key, SignType signType) throws Exception {
      if (!data.containsKey("sign") ) {
        return false;
      }
      String sign = data.get("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, "MD5");
    }

    /**
    * 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
    *
    * @param data 待签名数据
    * @param key API密钥
    * @param signType 签名方式
    * @return 签名
    */
    public static String generateSignature(final Map<String, String> data, String key, 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("sign")) {
          continue;
        }
        if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名
        sb.append(k).append("=").append(data.get(k).trim()).append("&");
      }
      sb.append("key=").append(key);
      return MD5(sb.toString()).toUpperCase();
    }


    /**
    * 获取随机字符串 Nonce Str
    *
    * @return String 随机字符串
    */
    public static String generateNonceStr() {
      return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
    }


    /**
    * 生成 MD5
    *
    * @param data 待处理数据
    * @return MD5结果
    */
    public static String MD5(String data) throws Exception {
      java.security.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();
    }

    /**
    * 日志
    * @return
    */
    public static Logger getLogger() {
      Logger logger = LoggerFactory.getLogger("wxpay java sdk");
      return logger;
    }

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

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

    /**
    * 生成 uuid, 即用来标识一笔单,也用做 nonce_str
    * @return
    */
    public static String generateUUID() {
      return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
    }

    }

    4.2 微信退款(参数根据开发文档,代码里红色是必需)

    微信退款的,maven还有导入两个包

    网址:http://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient

    /***
    * 提交退款处理
    *
    * @param request
    * @param response
    * @return
    */
    @RequestMapping(value = "/submitrefund.json")
    @ResponseBody
    public Map<String, Object> applyForRefun(HttpServletRequest request, HttpServletResponse response) {
      // 订单的主键
      String pk_easyhouse_salelog = "";
      // 退款处理原因(不是必须,若传入,则会在下发给用户的退款中显示)
      String dispose_reason = "";
      // 销售状态:同意退款:3,拒绝退款:4
      Integer sale_type = "";
      // 根据订单pk获取订单VO
      EasyhouseSalelogVO sale = "";
      String xmlStr = "";
      String resultXml = "";
      Map<String, String> resultMap = new HashMap<String, String>();
      // 同意退款
      if (sale_type == 3) {
        // 公众账号ID:登陆微信公众号后台-开发-基本配置
        String appid = "";
        // 微信支付商户号: mch_id-登陆微信支付后台,即可看到
        String mch_id = "";
        // 随机字符串,长度要求在32位以内,调用工具类中的随机数生成方法
        String nonce_str = WXPayUtil.generateNonceStr();
        // 微信订单号 或者商户订单号,二选一,这里用微信订单号
        String transaction_id = sale.getWx_order_num();
        // 商户退款单号,同一单号多次请求,只退款一次
        String out_refund_no = WXPayUtil.generateUUID();
        String price = sale.getProduct_saleprice().toString();
        Double total_price = Double.valueOf(price);
        // 订单总金额
        String total_fee = Integer.toString((int) (total_price * 100));
        // 退款总金额
        String refund_fee = Integer.toString((int) (total_price * 100));
        // 退款原因,会在下发给用户的退款消息中体现(可不传入)
        String refund_desc = sale.getRefund_reason();
        // API密钥(设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置)
        String key = WXPayConstants.KEY;
        // 将获得的信息存入Map集合中
        Map<String, String> map = new HashMap<String, String>();
        map.put("appid", appid);
        map.put("mch_id", mch_id);
        map.put("nonce_str", nonce_str);
        map.put("transaction_id", transaction_id);
        map.put("out_refund_no", out_refund_no);
        map.put("total_fee", total_fee);
        map.put("refund_fee", refund_fee);
        map.put("refund_desc", refund_desc);
        try {
          // 调用工具类,将Map集合转化为带签名sign的XML格式字符串
          xmlStr = WXPayUtil.generateSignedXml(map, key);
          // 调用微信退款接口地址
          String url = "https://api.mch.weixin.qq.com/secapi/pay/refund";
          // 调用双向证书,返回xml格式状态码
          resultXml = ClientCustomSSL.doRefund(url, xmlStr);
          // 将返回结果转换成Map集合
          resultMap = WXPayUtil.xmlToMap(resultXml);
        } catch (Exception e) {
          logger.debug("调用退款接口失败");
        }
      // 微信端返回字符串为成功时,退款成功,更新数据
      if (resultMap.get("return_code").equals(WXPayConstants.SUCCESS)&& resultMap.get("result_code").equals(WXPayConstants.SUCCESS)) {

        // 退款成功时,在此处更改订单的状态,并更新数据库对应信息

        sale.setSale_type(Integer.valueOf(BasicConstants.NUMBER_SALE_TYPE_REFUNDED));
        logger.debug("退款成功");
        //更新公众号粉丝表的退款总额、消费总额
        FansInfoVO fansInfoVO = userCenterService.getUserInfo(sale.getOpenid());
        fansInfoVO.setStatus(VOStatus.UPDATED);
        fansInfoVO.setRefund_sum(fansInfoVO.getRefund_sum().add(new UFDouble(refund_fee.toString())));
        fansInfoVO.setConsume_sum(fansInfoVO.getConsume_sum().sub(new UFDouble(refund_fee.toString())));
        salelogService.saveOrUpdate(fansInfoVO);
      } else {

        // 退款失败时,在此处设置相应信息,更新相应记录
        sale.setSale_type(Integer.valueOf(BasicConstants.NUMBER_SALE_TYPE_REFUNDFAIL));
        logger.debug("退款失败");
        // 记录退款失败原因
        dispose_reason += "," + resultMap.get("err_code_des");
        }
      } else {
        // 退款失败
        sale.setSale_type(Integer.valueOf(BasicConstants.NUMBER_SALE_TYPE_REFUNDFAIL));
        logger.debug("拒绝退款,退款失败");
    }

      //执行数据库更新动作
      sale.setStatus(VOStatus.UPDATED);
      sale.setDispose_reason(dispose_reason);
      // 处理人
      sale.setPk_user(WebUtilsFactory.getInstance().getLoginInfo().getPk_user());
      // 保存退款处理时间
      sale.setDispose_time(new UFDateTime(System.currentTimeMillis()));
      int resultVO = salelogService.saveOrUpdate(sale);
      if (resultVO == 0) {
        return this.genAjaxResponse(false, "处理失败!", null);
      }
      return this.genAjaxResponse(true, "处理成功!", null);
    }

    4.3 调用证书类,类里面需要指向证书安装的路径

    import java.io.File;
    import java.io.FileInputStream;
    import java.security.KeyStore;
    import javax.net.ssl.SSLContext;
    import org.apache.http.HttpEntity;
    import org.apache.http.client.methods.CloseableHttpResponse;
    import org.apache.http.client.methods.HttpPost;
    import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
    import org.apache.http.conn.ssl.SSLContexts;
    import org.apache.http.entity.StringEntity;
    import org.apache.http.impl.client.CloseableHttpClient;
    import org.apache.http.impl.client.HttpClients;
    import org.apache.http.util.EntityUtils;
    import org.nw.web.utils.WebUtilsFactory;

    import com.etom.itoilet.constants.WXGlobal;


    /**
    * 微信退款
    * 创建一个自定义的SSLContext安全连接
    *
    */
    public class ClientCustomSSL {

    public static String doRefund(String url,String data) throws Exception {
      //指定读取证书格式为PKCS12(注意PKCS12证书 是从微信商户平台-》账户设置-》 API安全 中下载的)
      KeyStore keyStore = KeyStore.getInstance("PKCS12");
      String fileName = "/cert/apiclient_cert.p12"; //文件名
      // 指定证书路径
      String path = "";
      //读取本机存放的PKCS12证书文件
      FileInputStream instream = new FileInputStream(new File(path));

      //比如安装在D:/pkcs12/apiclient_cert.p12情况下,就可以写成如下语句
      //FileInputStream instream = new FileInputStream(new File("D:/pkcs12/apiclient_cert.p12"));
      try {
        //指定PKCS12的密码(商户ID)
        keyStore.load(instream, WXGlobal.getMch_id().toCharArray());
      } finally {
        instream.close();
      }
      SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, WXGlobal.getMch_id().toCharArray()).build();
      //指定TLS版本
      SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory( sslcontext,new String[] { "TLSv1" },null,SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
      //设置httpclient的SSLSocketFactory
      CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
      try {
        HttpPost httpost = new HttpPost(url); // 设置响应头信息
        httpost.addHeader("Connection", "keep-alive");
        httpost.addHeader("Accept", "*/*");
        httpost.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
        httpost.addHeader("Host", "api.mch.weixin.qq.com");
        httpost.addHeader("X-Requested-With", "XMLHttpRequest");
        httpost.addHeader("Cache-Control", "max-age=0");
        httpost.addHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) ");
        httpost.setEntity(new StringEntity(data, "UTF-8"));
        CloseableHttpResponse response = httpclient.execute(httpost);
      try {
        HttpEntity entity = response.getEntity();
        String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8");
        EntityUtils.consume(entity);
        return jsonStr;
      } finally {
        response.close();
      }
      } finally {
        httpclient.close();
      }
      }

    }

    以上内容只是小编给大家列出开发项目中实现公众号退款的核心代码,大家根据需求适当的添加,修改,删除代码。如果大家在参考本段代码的过程中发现有任何疑问欢迎给我留言,小编会看到会及时回复大家的

    转载于:https://www.cnblogs.com/hongzm/p/7366510.html

    展开全文
  • 微信申请退款API~~开发

    万次阅读 热门讨论 2019-10-16 17:49:30
    之前有过微信开发的经验,但是第一次接触“微信退款“这一块的业务,查询了很多的博客资料以及走了很多的弯路。也发现“微信退款”分享的博客并不多。特地写了该博客,希望对你们有帮助。个人浅薄的见解 【稍微提...

    近日,在开发“微信申请退款”的功能。之前有过微信开发的经验,但是第一次接触“微信退款“这一块的业务,查询了很多的博客资料以及走了很多的弯路。也发现“微信退款”分享的博客并不多。特地写了该博客,希望对你们有帮助。个人浅薄的见解

    代码下载地址如下:
    链接:https://pan.baidu.com/s/1h6_ZSz5RbFARlY9yxkvj5w
    提取码:weft
    有任何关于微信开发的问题可以相互交流, 推荐QQ:2172931891 , 另外有微商城、微分销、微信小游戏等系统源码,有需要可以联系免费提供。



    一、微信退款Api


    https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4

    二、开发准备
    (1)证书的准备:java开发需要用到:apiclient_cert.p12证书的,在微信公众号上下载–注意PKCS12证书 是从微信商户平台-》账户设置-》 API安全 中下载的 。
    这里写图片描述
    【何时用到证书?】与支付不一样,企业支付功能在发送post请求的时候,需要加载自己的一个证书之后,带着证书去请求退款才可以。这里使用到证书–很多人不知道证书在哪里使用
    (2)了解好数字签名 — 简单来解释,就是对自己要发送的数据进行加密处理、换句话说假如说你要传递A/B/C,就对这三者进行加密。初开发者的误区:不知道该加密什么数据、观看网上的博客胡乱进行签名,导致签名错误
    【温馨提示:】数字签名是一般开发人员容易遇到的错误,记住“你没遇到数字签名错误,都不好意思说自己做过微信退款支付订单查询等功能”。
    耐心解决就行
    (3)熟悉 从xml–》map,以及map—》xml。因为微信只接受xml数据,java写一个xml不简单,但是写map集合非常简单。而且返回的数据是xml格式。需要转化成开发熟知的map集合
    不懂的可以看以下这篇博客
    http://blog.csdn.net/xiaozhegaa/article/details/79127283

    三、退款API截图解释
    ~~ 接口说明 + 是否需要证书
    这里写图片描述
    数字签名说明 – 记住一句话:对要传递的数据进行加密,你要传递什么就要签名什么
    第一次开发遇到的坑就是:不了解数字签名、花费了自己很长的时间,特地出来强调一下
    证书说明
    这里写图片描述

    四、开发步骤如下
    1.拼凑所需要传递的参数 map集合
    2.根据要传递的参数生成自己的签名
    3.把签名放到map集合中【因为签名也要传递过去】
    4.将当前的map结合转化成xml格式
    5.发送请求到微信退款Api。发送请求是一个方法来的
    6.解析返回的xml数据===》map集合
    7.根据map中的result_code/return_code来判断是否成功与失败

    不得不再啰嗦一下。下面设计到签名、mapToXml转化、xmlToMap转化、发送请求到API。这些方法都可以在下面网址看到。这里我Xml转化成Map。我是习惯用一个Bean接收,也有范例的代码,大家模仿能力要强一点。这次贴出来给大家看看吧
    http://blog.csdn.net/xiaozhegaa/article/details/79127283

    /** 
    	* 解析退款申请 
    	* 解析的时候自动去掉CDMA 
    	* @param xml 
    	*/ 
    	@SuppressWarnings("unchecked") 
    	public static RefundResult getUnifiedorderResult(String xml){ 
    				RefundResult unifieorderResult = new RefundResult(); 
    			try { 
    					StringReader read = new StringReader(xml); 
    					// 创建新的输入源SAX 解析器将使用 InputSource 对象来确定如何读取 XML 输入 
    					InputSource source = new InputSource(read); 
    					// 创建一个新的SAXBuilder 
    					SAXBuilder sb = new SAXBuilder(); 
    					// 通过输入源构造一个Document 
    					Document doc; 
    					doc = (Document) sb.build(source); 
    				
    					Element root = doc.getRootElement();// 指向根节点 
    					List<Element> list = root.getChildren(); 
    				
    					if(list!=null&&list.size()>0){ 
    					for (Element element : list) { 
    						System.out.println("key是:"+element.getName()+",值是:"+element.getText()); 
    						if("return_code".equals(element.getName())){ 
    								unifieorderResult.setResult_code(element.getText()); 
    							} 
    					
    						if("return_msg".equals(element.getName())){ 
    							unifieorderResult.setReturn_msg(element.getText()); 
    							} 
    					
    						if("result_code".equals(element.getName())){ 
    							unifieorderResult.setResult_code(element.getText()); 
    							} 
    						
    						
    						if("out_refund_no".equals(element.getName())){ 
    							unifieorderResult.setOut_refund_no(element.getText()); 
    							} 
    						
    						if("refund_id".equals(element.getName())){ 
    							unifieorderResult.setRefund_id(element.getText()); 
    							} 
    						
    						if("refund_fee".equals(element.getName())){ 
    							unifieorderResult.setRefund_fee(element.getText()); 
    							} 
    						
    						if("coupon_refund_fee".equals(element.getName())){ 
    							unifieorderResult.setCoupon_refund_fee(element.getText()); 
    							} 
    						
    						if("total_fee".equals(element.getName())){ 
    							unifieorderResult.setTotal_fee(element.getText()); 
    							} 
    						
    						if("cash_fee".equals(element.getName())){ 
    							unifieorderResult.setCash_fee(element.getText()); 
    							} 
    						if("err_code_des".equals(element.getName())){ 
    							unifieorderResult.setErr_code_des(element.getText()); 
    							} 
    						}
    					}
    				
    
    			} catch (JDOMException e) { 
    			e.printStackTrace(); 
    			} catch (IOException e) { 
    			e.printStackTrace(); 
    			}catch (Exception e) { 
    			e.printStackTrace(); 
    			} 
    				
    			return unifieorderResult; 
    		} 
    
    
    @Test
    	public void test2(){
    		SortedMap<Object, Object> packageParams = new TreeMap<Object, Object>(); 
    		
    		packageParams.put("appid", wxconfig.AppID);
    		packageParams.put("mch_id", wxconfig.mch_id);
    		
    		String s = UUID.randomUUID().toString().replace("-", "");
    		packageParams.put("nonce_str",s);
    		
    		String s1 = UUID.randomUUID().toString().replace("-", "");
    		packageParams.put("out_refund_no",s1);
    		packageParams.put("out_trade_no","ozb5fjjxbwag1akdy0vm108makqhsdmx");
    	
    		packageParams.put("refund_fee","100");
    		packageParams.put("total_fee","100");
    		
    		packageParams.put("op_user_id",wxconfig.mch_id);
    		
    		String sign  = WeixinPayBack.createSign("utf-8",packageParams);
    		
    		packageParams.put("sign", sign);
    		
    		String reuqestXml = WXPayUtil.getRequestXml(packageParams);
    		 
    		System.out.println("-----------------"); 
    		System.out.println(reuqestXml);
    		System.out.println("-----------------"); 
    		//发送请求到后台了
    		String wxUrl = WeixinPayBack.getRefundURL; 
    		try {
    			String weixinPost = ClientCustomSSL.doRefund(wxUrl, reuqestXml).toString();
    			System.out.println(weixinPost);
    			
    			RefundResult refundResult = WeixinPayBack.getUnifiedorderResult(weixinPost);  
    			    
    		    String result =null;
    		    if("SUCCESS".equalsIgnoreCase(refundResult.getResult_code())){ 
    		    	result = "200"; 
    		    	System.out.println("==========处理退款成功=========="); 
    	    	}else{ 
    	    		result = refundResult.getReturn_msg(); 
    	    	} 
    		    
    		    System.out.println(result);
    		} catch (Exception e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}  
    		
    		
    		
    	}
    

    这里写图片描述
    五、结果演示
    这里写图片描述
    自此,我们就学会了“微信申请退款”的开发,希望对你们有帮助
    具体的、在调用改方法自己加上自己的业务逻辑就行了。希望对大家有帮助

    //TODO 9.0 操作支付表,把当前的支付的状态变成 退款状态   state 1 ---> 2
    //TODO 10 操作预约表,可以把当前的预约状态取消  已支付--->退款
    //TODO 11 操作用户表,如果是充值退款的话,把用户的现金 - 当前退款的money
    //TODO 12其他等等的操作,生成记录单号之类的
    
    展开全文
  • 微信小程序支付及退款流程详解

    万次阅读 2019-03-21 11:10:45
    首先说明一下,微信小程序支付的主要逻辑集中在后端,前端只需携带支付所需的数据请求后端接口然后根据返回结果做相应成功失败处理即可。我在后端使用的是php,当然在这篇博客里我不打算贴一堆代码来说明支付的具体...

    首先说明一下,微信小程序支付的主要逻辑集中在后端,前端只需携带支付所需的数据请求后端接口然后根据返回结果做相应成功失败处理即可。我在后端使用的是php,当然在这篇博客里我不打算贴一堆代码来说明支付的具体实现,而主要会侧重于整个支付的流程和一些细节方面的东西。所以使用其他后端语言的朋友有需要也是可以看一下的。很多时候开发的需求和相应问题的解决真的要跳出语言语法层面,去从系统和流程的角度考虑。好的,也不说什么废话了。进入正题。

    一. 支付

    支付主要分为几个步骤:

    • 前端携带支付需要的数据(商品id,购买数量等)发起支付请求
    • 后端在接收到支付请求后,处理支付数据,然后携带处理后的数据请求 微信服务器 的 支付统一下单接口
    • 后端接收到上一步请求微信服务器的返回数据,再次处理,然后返回前端让前端可以开始支付。
    • 前端进行支付动作
    • 前端支付完成后,微信服务器会向后端发送支付通知(也就是微信要告诉你客户已经付过钱了),后端根据这个通知确定支付完成,然后就去做支付完成后的相应动作,比如修改订单状态,添加交易日志啊等等。

        从这几个步骤可以看出,后端主要的作用就是将支付需要的数据传给微信服务器,再根据微信服务器的响应确定支付是否完成。

        这个流程还是蛮容易理解的。形象的说,前端就是个顾客,后端就是店家,微信服务器的统一下单接口就像收银员。顾客跟店家说,我是谁谁谁,现在我要付多少多少钱给你买什么什么。店家就跟收银员说,那个谁谁谁要付多少钱,你准备收钱吧。收银员收到钱后,就去告诉店家,我已经收到钱了,你给他东西吧。
    下面就详细的说明一下各个步骤的具体实现。

    1. 前端请求支付

        前端请求支付,就是简单的携带支付需要的数据,例如用户标识,支付金额,支付订单 ID 等等跟 **你的业务逻辑有关** 或者跟 **下一步请求微信服务器支付统一下单接口需要的数据有关** 的相关数据,使用微信小程序的 wx.request( ) 去请求后端的支付接口。

    2. 后端请求微信服务器

        后端接收到前端发送的支付请求后,可以进行一下相关验证,例如判断一下用户有没有问题,支付金额对不对等等。

        在验证没什么问题,可以向微信服务器申请支付之后,后端需要使用 微信规定的数据格式 去请求微信的支付统一下单接口。

    微信规定的请求数据:

    这需要较多代码实现。因为需要的数据个数较多,而且还需要加密并以 XML 格式发送。
    首先,有以下数据是使用小程序支付必须提供给微信服务器的参数。

    • 小程序 appid。写小程序的大概没有不知道这个的。。。
    • 用户标识 openid。也就是用户的小程序标识,在我上篇博客中说明了如何获取。
    • 商户号 mch_id 。申请开通微信支付商户认证成功后微信发给你的邮件里有
    • 商户订单号 out_trade_no 。商户为这次支付生成的订单号
    • 总金额 total_fee 。订单总金额,很重要的一点是单位是分,要特别注意。
    • 微信服务器回调通知接口地址 notify_url。微信确认钱已经到账后,会往这个地址多次发送消息,告诉你顾客已经付完钱了,你需要返回消息给微信表示你已经收到了通知。。这个地址不能有端口号,同时要能直接接受POST方法请求。
    • 交易类型 trade_type 。微信小程序支付此值统一为 JSAPI
    • 商品信息 Body。类似"腾讯-游戏"这种格式
    • 终端IP地址 spbill_create_ip 。终端地址IP,也就是请求支付的 IP 地址。
    • 随机字符串 nonce_str 。需要后端随机生成的字符串用于保证数据安全。微信要求不长于32位。
    • 签名 sign 。使用上面的所有参数进行相应处理加密生成签名。(具体处理方式可见下文代码,可直接复用。)

    在处理好以上所有数据后,将这些数据以 XML 格式整理并以 POST 方法发送到 微信支付统一下单接口 https://api.mch.weixin.qq.com/pay/unifiedorder 。

    3.后端接受微信服务器返回数据

    微信服务器在接收到支付数据之后,如果数据没有问题,其会返回用于支付的相应数据,其中非常重要的是 名称为 prepay_id 的数据字段,需要将此数据返回前端,前端才能继续支付。

    因此,在后端接收到微信服务器的返回数据后,需要进行相应的处理,最终返回到前端如下数据:

    1. appid 不需多说
    2. timeStamp 当前时间戳
    3. nonceStr 随机字符串
    4. package 就是上面提到的 prepay_id,不过切记格式如 “prepay_id= prepay_id_item“。否则会导致错误。
    5. signType 加密方式,一般应该是 MD5
    6. paySign 对以上数据进行相应处理并加密。
    7.  

    到这里,后端的支付接口已经完成了接收前端支付请求,并返回了前端支付所需数据的功能。

    4. 前端发起支付

    ​ 前端在接收到返回数据后,使用 wx.requestPayment() 来请求发起支付。此 API 需要的对象参数各项值就是我们上一步返回的各个数据。

    5.后端接受微信服务器回调

    ​ 前端完成支付后,微信服务器确认支付已经完成。就会向第一步中设置的回调地址发送通知。后端的接收回调接口在接收到通知后,就可以判断支付是否完成,从而决定后续动作。

    ​ 需要注意的是,在接收到微信服务器的回调通知后,根据通知的result_code字段判断支付是否成功。在接受到成功的通知后,后端需要返回success数据向微信服务器告知已得到回调通知。否则微信服务器会不停的向后端发送消息。另外微信的通知是以XML格式发送的,在接受处理时需要注意。

    ​ 微信的大概支付流程就是这样。以下是PHP语法的微信支付类,可以比照上面的步骤介绍,加深理解。在需要支付时,直接传入参数实例化此类再调用类的 pay 方法即可。

    ?

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    32

    33

    34

    35

    36

    37

    38

    39

    40

    41

    42

    43

    44

    45

    46

    47

    48

    49

    50

    51

    52

    53

    54

    55

    56

    57

    58

    59

    60

    61

    62

    63

    64

    65

    66

    67

    68

    69

    70

    71

    72

    73

    74

    75

    76

    77

    78

    79

    80

    81

    82

    83

    84

    85

    86

    87

    88

    89

    90

    91

    92

    93

    94

    95

    96

    97

    98

    99

    100

    101

    102

    103

    104

    105

    106

    107

    108

    109

    110

    111

    112

    113

    114

    115

    116

    117

    118

    119

    120

    121

    122

    123

    124

    125

    126

    127

    128

    129

    130

    131

    132

    133

    134

    135

    136

    137

    138

    139

    140

    141

    142

    143

    144

    145

    146

    147

    148

    149

    150

    151

    152

    153

    154

    155

    156

    157

    158

    159

    160

    161

    162

    163

    164

    //微信支付类

    class WeiXinPay{

      //=======【基本信息设置】=====================================

      //微信公众号身份的唯一标识

      protected $APPID = appid;//填写您的appid。微信公众平台里的

      protected $APPSECRET = secret;

      //受理商ID,身份标识

      protected $MCHID = '11111111';//商户id

      //商户支付密钥Key

      protected $KEY = '192006250b4c09247ec02edce69f6a2d';

      //回调通知接口

      protected $APPURL =   'https://smart.afei.com/receivesuc';

      //交易类型

      protected $TRADETYPE = 'JSAPI';

      //商品类型信息

      protected $BODY = 'wx/book';

      //微信支付类的构造函数

      function __construct($openid,$outTradeNo,$totalFee){

        $this->openid = $openid; //用户唯一标识

        $this->outTradeNo = $outTradeNo; //商品编号

        $this->totalFee = $totalFee; //总价

      }

      //微信支付类向外暴露的支付接口

      public function pay(){

        $result = $this->weixinapp();

        return $result;

      }

       //对微信统一下单接口返回的支付相关数据进行处理

       private function weixinapp(){

         $unifiedorder=$this->unifiedorder();

         $parameters=array(

         'appId'=>$this->APPID,//小程序ID

         'timeStamp'=>''.time().'',//时间戳

         'nonceStr'=>$this->createNoncestr(),//随机串

         'package'=>'prepay_id='.$unifiedorder['prepay_id'],//数据包

         'signType'=>'MD5'//签名方式

           );

         $parameters['paySign']=$this->getSign($parameters);

         return $parameters;

       }

      /*

       *请求微信统一下单接口

       */

      private function unifiedorder(){

        $parameters = array(

          'appid' => $this->APPID,//小程序id

          'mch_id'=> $this->MCHID,//商户id

          'spbill_create_ip'=>$_SERVER['REMOTE_ADDR'],//终端ip

          'notify_url'=>$this->APPURL, //通知地址

          'nonce_str'=> $this->createNoncestr(),//随机字符串

          'out_trade_no'=>$this->outTradeNo,//商户订单编号

          'total_fee'=>floatval($this->totalFee), //总金额

          'open_id'=>$this->openid,//用户openid

          'trade_type'=>$this->TRADETYPE,//交易类型

          'body' =>$this->BODY, //商品信息

        );

        $parameters['sign'] = $this->getSign($parameters);

        $xmlData = $this->arrayToXml($parameters);

        $xml_result = $this->postXmlCurl($xmlData,'https://api.mch.weixin.qq.com/pay/unifiedorder',60);

        $result = $this->xmlToArray($xml_result);

        return $result;

      }

      //数组转字符串方法

      protected function arrayToXml($arr){

        $xml = "<xml>";

        foreach ($arr as $key=>$val)

        {

          if (is_numeric($val)){

            $xml.="<".$key.">".$val."</".$key.">";

          }else{

             $xml.="<".$key."><![CDATA[".$val."]]></".$key.">";

          }

        }

        $xml.="</xml>";

        return $xml;

      }

      protected function xmlToArray($xml){

        $array_data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);

        return $array_data;

      }

      //发送xml请求方法

      private static function postXmlCurl($xml, $url, $second = 30)

      {

        $ch = curl_init();

        //设置超时

        curl_setopt($ch, CURLOPT_TIMEOUT, $second);

        curl_setopt($ch, CURLOPT_URL, $url);

        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);

        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); //严格校验

        //设置header

        curl_setopt($ch, CURLOPT_HEADER, FALSE);

        //要求结果为字符串且输出到屏幕上

        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);

        //post提交方式

        curl_setopt($ch, CURLOPT_POST, TRUE);

        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);

        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 20);

        curl_setopt($ch, CURLOPT_TIMEOUT, 40);

        set_time_limit(0);

        //运行curl

        $data = curl_exec($ch);

        //返回结果

        if ($data) {

          curl_close($ch);

          return $data;

        } else {

          $error = curl_errno($ch);

          curl_close($ch);

          throw new WxPayException("curl出错,错误码:$error");

        }

      }

      /*

       * 对要发送到微信统一下单接口的数据进行签名

       */

      protected function getSign($Obj){

         foreach ($Obj as $k => $v){

         $Parameters[$k] = $v;

         }

         //签名步骤一:按字典序排序参数

         ksort($Parameters);

         $String = $this->formatBizQueryParaMap($Parameters, false);

         //签名步骤二:在string后加入KEY

         $String = $String."&key=".$this->KEY;

         //签名步骤三:MD5加密

         $String = md5($String);

         //签名步骤四:所有字符转为大写

         $result_ = strtoupper($String);

         return $result_;

       }

      /*

       *排序并格式化参数方法,签名时需要使用

       */

      protected function formatBizQueryParaMap($paraMap, $urlencode)

      {

        $buff = "";

        ksort($paraMap);

        foreach ($paraMap as $k => $v)

        {

          if($urlencode)

          {

            $v = urlencode($v);

          }

          //$buff .= strtolower($k) . "=" . $v . "&";

          $buff .= $k . "=" . $v . "&";

        }

        $reqPar;

        if (strlen($buff) > 0)

        {

          $reqPar = substr($buff, 0, strlen($buff)-1);

        }

        return $reqPar;

      }

      /*

       * 生成随机字符串方法

       */

      protected function createNoncestr($length = 32 ){

         $chars = "abcdefghijklmnopqrstuvwxyz0123456789";

         $str ="";

         for ( $i = 0; $i < $length; $i++ ) {

         $str.= substr($chars, mt_rand(0, strlen($chars)-1), 1);

         }

         return $str;

         }

    }

    以上就是微信支付的相关流程。在理清思路后,流程还是比较清晰和简单的。重点在于需要注意一些细节问题,例如数据格式,加密方法等。

    下面说一下微信小程序退款的具体实现

    二.退款

    小程序退款的流程和付款相似,但有一些细节上的不同。

    首先退款的步骤通常如下:

    • 用户前端点击退款按钮后,后端接收到用户的退款请求通过商城后台呈现给商户,商户确定允许退款后,后端再发起向微信退款接口的请求来请求退款。
    • 后端向微信退款接口发送请求后,得到响应信息,确定退款是否完成,根据退款是否完成再去进行改变订单状态等业务逻辑。
    •  

    退款的步骤相对微信支付来说比较简单。

    值得注意的有以下两点:

    1.向微信退款接口请求退款后,根据得到的响应是可以直接确定退款是否完成的。不再需要设置专门的回调接口等待微信通知。当然如果需要也是可以在微信商户平台设置回调接口接受从而接受微信回调的,但并不是必须的。

    2.退款请求需要在请求服务器安装微信提供的安全证书,也就是说,发起退款请求相比较支付请求在请求时请求方法不能复用,因为微信退款需要携带证书的请求,此证书可在申请微信商户号成功后从微信商户平台自行下载,Linux下的PHP开发环境的证书只需要放在网站根目录的cert文件夹中即可。其他开发环境可能需要导入操作。

    下面讲解一下退款的具体步骤

    一. 用户发起退款请求

        用户在前端发起退款请求,后端接收到退款请求,将相应订单标记为申请退款,展示在后台.商户查看后,如果同意退款再进行相应操作.此后才进入真正的退款流程.

    二. 商户发起退款请求

        商户同意退款后,后端即向微信提供的退款 API 发起请求.

        同请求微信支付API一样.退款请求也需要将需要的参数进行签名后以XML发送到微信的退款API [https://api.mch.weixin.qq.com/pay/refund](https://api.mch.weixin.qq.com/pay/refund)
    退款请求需要的参数如下(多个参数在支付API请求时也有使用):

    • 小程序 appid。
    • 商户号 mch_id 。申请开通微信支付商户认证成功后微信发给你的邮件里有
    • 商户订单号 out_trade_no 。退款订单在支付时生成的订单号
    • 退款订单号 out_refund_no 。由后端生成的退款单号,需要保证唯一,因为多个同样的退款单号只会退款一次。
    • 总金额 total_fee 。订单总金额,单位为分。
    • 退款金额 refund_fee 需要退款的金额,单位同样为分
    • 操作员 op_user_id .与商户号相同即可
    • 随机字符串 nonce_str 。同支付请求
    • 签名 sign 。使用上面的所有参数进行相应处理加密生成签名。(具体处理方式与支付相同,可直接复用。)

    三. 退款完成

        在发起退款请求后,就可以直接根据请求的响应XML中的  result_code字段来判断退款是否成功,从而对订单状态进行处理和后续操作。不需要像支付那样等待另一个接口的通知来确定请求状态。当然如上文所说,如果需要微信服务器发送通知到后端的话,可以到微信商户平台进行设置。

    退款因为流程与支付大同小异,因此退款的PHP类我选择了直接继承支付类,

    代码如下,注意区分退款请求方法postXmlSSLCurl和支付请求方法postXmlCurl的区别,这也就是上文提到的退款需要的双向证书的使用。

    ?

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    32

    33

    34

    35

    36

    37

    38

    39

    40

    41

    42

    43

    44

    45

    46

    47

    48

    49

    50

    51

    52

    53

    54

    55

    56

    57

    58

    59

    60

    61

    62

    63

    64

    65

    66

    67

    68

    69

    70

    71

    72

    73

    74

    75

    76

    ````

     class WinXinRefund extends WeiXinPay{

      protected \$SSLCERT_PATH = 'cert/apiclient_cert.pem';//证书路径

      protected \$SSLKEY_PATH = 'cert/apiclient_key.pem';//证书路径

      protected \$opUserId = '1234567899';//商户号

    function __construct($openid,$outTradeNo,$totalFee,$outRefundNo,$refundFee){

      //初始化退款类需要的变量

      $this->openid = $openid;

      $this->outTradeNo = $outTradeNo;

      $this->totalFee = $totalFee;

      $this->outRefundNo = $outRefundNo;

      $this->refundFee = $refundFee;

    }

    public function refund(){

      //对外暴露的退款接口

      $result = $this->wxrefundapi();

      return $result;

    }

    private function wxrefundapi(){

      //通过微信api进行退款流程

      $parma = array(

        'appid'=> $this->APPID,

        'mch_id'=> $this->MCHID,

        'nonce_str'=> $this->createNoncestr(),

        'out_refund_no'=> $this->outRefundNo,

        'out_trade_no'=> $this->outTradeNo,

        'total_fee'=> $this->totalFee,

        'refund_fee'=> $this->refundFee,

        'op_user_id' => $this->opUserId,

      );

      $parma['sign'] = $this->getSign($parma);

      $xmldata = $this->arrayToXml($parma);

      $xmlresult = $this->postXmlSSLCurl($xmldata,'https://api.mch.weixin.qq.com/secapi/pay/refund');

      $result = $this->xmlToArray($xmlresult);

      return $result;

    }

    //需要使用证书的请求

    function postXmlSSLCurl($xml,$url,$second=30)

    {

      $ch = curl_init();

      //超时时间

      curl_setopt($ch,CURLOPT_TIMEOUT,$second);

      //这里设置代理,如果有的话

      //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');

      //curl_setopt($ch,CURLOPT_PROXYPORT, 8080);

      curl_setopt($ch,CURLOPT_URL, $url);

      curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,FALSE);

      curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,FALSE);

      //设置header

      curl_setopt($ch,CURLOPT_HEADER,FALSE);

      //要求结果为字符串且输出到屏幕上

      curl_setopt($ch,CURLOPT_RETURNTRANSFER,TRUE);

      //设置证书

      //使用证书:cert 与 key 分别属于两个.pem文件

      //默认格式为PEM,可以注释

      curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');

      curl_setopt($ch,CURLOPT_SSLCERT, $this->SSLCERT_PATH);

      //默认格式为PEM,可以注释

      curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');

      curl_setopt($ch,CURLOPT_SSLKEY, $this->SSLKEY_PATH);

      //post提交方式

      curl_setopt($ch,CURLOPT_POST, true);

      curl_setopt($ch,CURLOPT_POSTFIELDS,$xml);

      $data = curl_exec($ch);

      //返回结果

      if($data){

        curl_close($ch);

        return $data;

      }

      else {

        $error = curl_errno($ch);

        echo "curl出错,错误码:$error"."<br>";

        curl_close($ch);

        return false;

      }

    }}

    三. 总结

    以上就是关于微信支付和退款的流程及相关知识的介绍。文中的 PHP类 均封装直接可用。

    因为微信支付和退款涉及的东西较为繁杂,很多人直接看官方文档可能会一头雾水,所以看过此文了解流程和要点后,再去看微信官方文档。一方面可以更清晰的了解小程序的支付和退款流程。另一方面,本文因为篇幅有限及作者能力有限,肯定有无暇顾及或有所纰漏之处。为求稳妥,还是需要多看看官方开发文档。毕竟事涉支付,出个BUG可不是小事。
    最后扯点闲话吧。这篇博客本来应该在三个月前就发表的,也算当时我从一无所知到独立完成微信小程序商城前后端的总结系列的第一篇。但是公司突然出现人员和项目的变动,导致管理和项目上都混乱不堪,再加上个人的惰性,导致此篇博客一直拖到三个月后的今天才断断续续写完。这三个月我的心态因为各种事起起伏伏,也颇有一番风味。

    以上所述是小编给大家介绍的微信小程序支付及退款流程详解,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!

    展开全文
  • 微信公众号之微信退款

    千次阅读 2020-04-06 16:48:47
    一、前言 这次的项目主要是关于微信公众号...二、微信申请退款接口微信退款接口文档:微信公众号退款申请接口开发文档退款申请流程:前端调用微信退款申请接口,退款申请需要双向的证书验证,登录微信商户平台(pay.w...

    一、前言

       这次的项目主要是关于微信公众号的一个开发,本人这次分配的模块是后台微信公众号的支付和退款,第一次接触微信公众的项目刚开始一脸懵逼,开发过程中遇到各种坑,所以想自己写一篇详细的关于微信公众号的开发,希望能对小伙伴们有所帮助!

    二、微信申请退款接口

    微信退款接口文档:微信公众号退款申请接口开发文档

    退款申请流程:前端调用微信退款申请接口,退款申请需要双向的证书验证,登录微信商户平台(pay.weixin.qq.com)-->账户中心-->账户设置-->API安全-->证书下载。具体安装请参考微信证书安装文档:商户证书安装指导。在微信退款申请接口调用时需要读取服务器安装的证书,然后才能想微信发送请求,否则请求回发送失败或者返回数据为空。如果退款接口中设置了退款结果通知的URL,那么在退款申请成功后会给设置的通知接口返回数据,当放回的结果为SUCCESS时,会携带一部分加密的数据,数据解密方式:

    解密步骤如下: 

    (1)对加密串A做base64解码,得到加密串B

    (2)对商户key做md5,得到32位小写key* ( key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置 )

    (3)用key*对加密串B做AES-256-ECB解密(PKCS7Padding)

    解密成功后可进行相应的业务处理,代码如下:

    申请退款接口:

    /**
    	 * 
    	* @Title: refund  
    	* @Description: 微信退款 
    	* @param @param request
    	* @param @param response
    	* @param @return
    	* @param @throws Exception     
    	* @return Map<String,String>    
    	* @throws
    	 */
    	@ResponseBody
    	@RequestMapping("/refund")
    	public JsPayResult refund(HttpServletRequest request, HttpServletResponse response) throws Exception {
    		
    		// 公众账号ID
    		String appid = Constants.APPID;
    		// 商户号
    		String mch_id = Constants.MCHID;
    		// 随机字符串
    		String nonce_str = CommonUtil.getRandomStr();
    		// 商户订单号
    		String out_trade_no = request.getParameter("out_trade_no");
    		// 商户退款单号,订单号是唯一的,加上订单号防止在高并发下退款单号不唯一
    		String out_refund_no = CommonUtil.getOrderIdByTime()+out_trade_no;
    		// 订单金额
    		String total_fee1 = CommonUtil.getMoney(request.getParameter("total_fee"));
    		String total_fee = CommonUtil.getMoney("0.01");
    		// 退款金额
    		String refund_fee1 = request.getParameter("refund_fee");
    		String refund_fee = CommonUtil.getMoney("0.01");
    		//退款结果通知url
    		String notify_url = Constants.REFOUND_NOTIFY_URL;
    		// 将请求参数封装至Map集合中
    		SortedMap<String, String> paramMap = new TreeMap<String, String>();
    		paramMap.put("appid", appid);
    		paramMap.put("mch_id", mch_id);
    		paramMap.put("nonce_str", nonce_str);
    		paramMap.put("out_trade_no", out_trade_no);
    		paramMap.put("out_refund_no", out_refund_no);
    		paramMap.put("total_fee", total_fee);
    		paramMap.put("refund_fee", refund_fee);
    		paramMap.put("notify_url", notify_url);
    		logger.info(paramMap);
    		// 签名
    		String sign = SignUtil.createSign(paramMap, Constants.PARTNER_KEY);
    		paramMap.put("sign", sign);
    		// 请求的xml数据
    		String requestXml = XMLUtil.map2Xml(paramMap, "xml");
    		
    	//1.指定读取证书格式为PKCS12
            KeyStore keyStore  = KeyStore.getInstance("PKCS12");
            //2.读取本机存放的PKCS12证书文件
            FileInputStream instream = new FileInputStream(new File(Constants.CERTIFICATE_PATH));
            try {
            	 //指定PKCS12的密码(商户ID)
                keyStore.load(instream, Constants.MCHID.toCharArray());
            } finally {
                instream.close();
            }
            //3.ssl双向验证发送http请求报文
            SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, Constants.MCHID.toCharArray()).build();
            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext,new String[] { "TLSv1" },null,
                    SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
            CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
            //4.发送数据到微信的退款接口
            HttpPost httpost= HttpClientConnectionManager.getPostMethod(Constants.REFUND_URL);
            httpost.setEntity(new StringEntity(requestXml, "UTF-8"));
            HttpResponse weixinResponse = httpClient.execute(httpost);
            String resposeXmL = EntityUtils.toString(weixinResponse.getEntity(), "UTF-8");
            
    		//5.将返回的xml转换为map
    		Map<String, String> responseMap = XMLUtil.xml2Map(resposeXmL);
    		JsPayResult result = new JsPayResult();
    		if (Constants.RETURN_CODE.equals(responseMap.get("return_code"))) {
    			
    			result.setAppId(responseMap.get("appid"));
    			result.setMchId(responseMap.get("mch_id"));
    			result.setNonceStr(responseMap.get("nonce_str"));
    			// 微信订单号
    			result.setTransactionId(responseMap.get("transaction_id"));
    			// 商户订单号
    			result.setOutRradeNo(responseMap.get("out_trade_no"));
    			// 商户退款单号
    			result.setOutRefundNo(responseMap.get("out_refund_no"));
    			// 微信退款单号
    			result.setRefundId(responseMap.get("refund_id"));
    			// 退款金额
    			result.setSettlementRefundRee(responseMap.get("settlement_refund_fee"));
    			// 订单金额
    			result.setTotalFee(responseMap.get("total_fee"));
    			//申请退款金额
    			result.setRefundFee(responseMap.get("refund_fee"));
    			// 现金支付金额
    			result.setCashFee(responseMap.get("cash_fee"));
    			//退款状态
    			result.setRefundStatus(responseMap.get("refund_status"));
    			//退款成功时间
    			result.setSuccessTime(responseMap.get("success_time"));
    			//退款入账账户
    			result.setRefundRecvAccout(responseMap.get("refund_recv_accout"));
    			//退款资金来源
    			result.setRefundAccount(responseMap.get("refund_account"));
                //退款发起来源
    			result.setRefundRequestSource(responseMap.get("refund_request_source"));
    			result.setResultCode(Constants.RESULT_CODE_SUCCESS);
    			result.setMessage("退款成功!");
    			logger.info("*******退款申请**********"+"退款成功!");
    		}
    		else {
    			result.setResultCode(Constants.RESULT_CODE_FAIL);
    		    result.setMessage("退款失败!");
    		    logger.info("*******退款申请**********"+"退款失败!");
    			
    		}
    		return result;
    	}

    退款接口中涉及到的双向证书验证:

    //1.指定读取证书格式为PKCS12
            KeyStore keyStore  = KeyStore.getInstance("PKCS12");
            //2.读取本机存放的PKCS12证书文件
            FileInputStream instream = new FileInputStream(new File(Constants.CERTIFICATE_PATH));//读取证书的安装路径
            try {
            	 //指定PKCS12的密码(商户ID)
                keyStore.load(instream, Constants.MCHID.toCharArray());
            } finally {
                instream.close();
            }
            //3.ssl双向验证发送http请求报文
            SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, Constants.MCHID.toCharArray()).build();
            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext,new String[] { "TLSv1" },null,
                    SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
            CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
            //4.发送数据到微信的退款接口
            HttpPost httpost= HttpClientConnectionManager.getPostMethod(Constants.REFUND_URL);
            httpost.setEntity(new StringEntity(requestXml, "UTF-8"));
            HttpResponse weixinResponse = httpClient.execute(httpost);
            String resposeXmL = EntityUtils.toString(weixinResponse.getEntity(), "UTF-8");

    三、退款结果通知接口

    /**
    	 * 
    	* @Title: refundNotify  
    	* @Description: 退款结果通知  
    	* @param @param request
    	* @param @param response
    	* @param @return
    	* @param @throws Exception     
    	* @return Map<String,String>    
    	* @throws
    	 */
    	@ResponseBody
    	@RequestMapping("/refundNotify")
    	public JsPayResult refundNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
    		// 将request请求中的数据转换为字符串
    		String reqpXml = CommonUtil.readRequestStr(request);
    		// 将返回串转换成 Map
    		Map<String, String> xmlToMap = XMLUtil.xml2Map(reqpXml);
    		// 返回给微信的结果
    		String respXml = "";
    		JsPayResult result = new JsPayResult();
    		// 在return_code为SUCCESS的时候有返回 req_info
    		if (Constants.RETURN_CODE.equals(xmlToMap.get("return_code"))) {
    			// 退款返回加密信息
    			String reqInfo = xmlToMap.get("req_info");
    			// 解密后的信息
    			String decodeReqInfo = AESUtil.decryptData(reqInfo);
    			// 将解密后的信息换成 Map
    			Map<String, String> reqInfoMap = XMLUtil.xml2Map(decodeReqInfo);
    
    			ResponseResult responseResult = refundRegistration(Constants.BRANCHCODE, reqInfoMap.get("out_trade_no"),
    					reqInfoMap.get("transaction_id"), "3300", DateUtil.getTradeTime(reqInfoMap.get("success_time")),
    					Constants.YYSOURCE);
    			if (Constants.RESULTCODE.equals(responseResult.getResultCode())) {
    				respXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
    						+ "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
    				result.setMessage("退款成功!");
    			} else {
    				respXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
    						+ "<return_msg><![CDATA[ERROR]]></return_msg>" + "</xml> ";
    				result.setMessage("退款失败!");
    			}
    			
    			logger.info("*******退款通知**********" + "退款成功!");
    		} else {
    			respXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA["
    					+ xmlToMap.get("return_code") + "]]></return_msg>" + "</xml> ";
    			result.setMessage("退款失败!");
    			logger.info("*******退款通知**********" + "退款失败!");
    		}
    
    		result.setResultCode(xmlToMap.get("return_code"));
    		BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
    		out.write(respXml.getBytes());
    		out.flush();
    		out.close();
    		return result;
    	}

    解密方式:

    public class AESUtil {
    
    	/**
    	 * 密钥算法
    	 */
    	private static final String ALGORITHM = "AES";
    	/**
    	 * 加解密算法/工作模式/填充方式
    	 */
    	private static final String ALGORITHM_MODE_PADDING = "AES/ECB/PKCS7Padding";
    	/**
    	 * 生成key(商户密钥)
    	 */
    	private static final String key = "qp2tMA7jIDLyRRhz83Ut2eVQh8qaI5PD";
    
    	/**
    	 * 对商户key做md5
    	 */
    	private static SecretKeySpec secretKey = new SecretKeySpec(MD5Util.MD5Encode(key, "UTF-8").toLowerCase().getBytes(),
    			ALGORITHM);
    
    	/**
    	 * AES加密
    	 * 
    	 * @param data
    	 * @return
    	 * @throws Exception
    	 */
    	public static String encryptData(String data) throws Exception {
    		Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
    		// 创建密码器
    		Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING);
    		// 初始化
    		cipher.init(Cipher.ENCRYPT_MODE, secretKey);
    		return Base64Util.encode(cipher.doFinal(data.getBytes()));
    	}
    
    	/**
    	 * AES解密
    	 * 
    	 * @param base64Data
    	 * @return
    	 * @throws Exception
    	 */
    	public static String decryptData(String base64Data) throws Exception {
    		Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
    		// 创建密码器
    		Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING);
    		// 初始化
    		cipher.init(Cipher.DECRYPT_MODE, secretKey);
    		return new String(cipher.doFinal(Base64Util.decode(base64Data)));
    	}
    
    	public static void main(String[] args) throws Exception {
    		// String A 为测试字符串,是微信返回过来的退款通知字符串
    		String A = "N0lJ0LWjHQlVOTEYdlRoVDbqWq+tRo9ZzvcwvIGHjFN6pYmHEX44W9l/jlyw8cwHueWk3m4hpldha73MgmCZJUu5LBZv27y/Vx2RkvHKCkiI5mV9pqQxiJmZTB1PN6s2wT3EVN1BFciGNzhozqQNIOyn/B9VOVKXkTeh1to8nI/UFVDexDE4ZyMBoB9oCQVcnkAuPaWqibHMU7i0iapB1UEMYJCgRKza9OGtvs0WbqIRgVVhtFxpxMhHmIaxzvH0JrdD/iOAYEV/NxUkye2HNJahatcYFBFQlbrTTBJ67MXZ6NzwFaOqqQYxZAKKEDrU++zu7hhX0lC5rZ5Uoyavn/sYTK3ZoCgAg/6O+S/f9sg+FoD8BrZmxC6tZwTfkDGsEO09m/JSTDxgU9ToCypyQt+bCxIFhLkGt8wKAtIEo0VOrfT9yMvHyBNLHtvNXj9gTQP+bMtpWAr0iMNgLwyXC2KY2FVxLmBEAnIIcXw7W15QItPcNVpQZceZooZwXn3QT6D4QDoHyg7ymHiAbtax0xHeYVGuGDB95E22q5C1Hh1a+7nyqkkJm1tzgJwyU+hhCw3Kw0Sj4JJVoLn6hFIBmVrDHf9x7j6VBULZ+39zk2upEudu/2TU1QVx96RCMW2O8EKXthPjzoOzZh1KeBsdkodrSn6gpBRNhIdbeimyAANVTyN+eeHThdx4tgEhodr9nVawFCSnD7jajowwaABFv/5AeWXSohfbbxAVrghNCjsfR4Grybr9fb6wB7hJ2yPZIKgdf8nGa9B7joKfZl2N7xIRawhGAVR3RRC2ajBEiabqaNhBCvhHPzR75oXViUL5OzVSyYznvrE1JEIgtGSN0rI/hUBIxhnTEv/X9C3NWiYRWoLMt29vJbQlk9hgQHVnTpH00khjXe8tdtMIkY7FUJmIsZH8D0jDMAvDj00Zl6r5z7FsyzRR+0xNsiyj8BPAxmSLqyrvXtgYx91N8I16TsgEBPJACL7tHkUr+kjQNXNzRp32mJFkB7/ZNQaXH8cm5aUAFk9eCuQAD4GqQxoYHOs/L7q2WMdajxPPxQSO6JU=";
    		String B = AESUtil.decryptData(A);
    		System.out.println(B);
    	}
    
    }

    有兴趣的朋友可以关注下本人的微信公众号:“JAVA菜鸟程序猿”

     

     

     

    展开全文
  • Java实现微信申请退款功能

    千次阅读 2019-06-14 16:10:31
    今天要将微信的最后一个功能给做完了,这个功能就是申请退款。刚开始我先开了一下文档,发现它跟那个企业付款到零钱特别相似,然后就自己模仿企业付款到零钱把它给实现了。 二,解决方法 申请退款官方文档:...
  • 前两天刚刚完成公司申请退款接口,来这里总结下自己遇到的那些坑,希望能帮助让大家少走些弯路,一路绿灯 微信官方API文档 业务很简单,就是把微信那边需要的参数封装到map里,一起传过去就好 需要准备的东西 还有...
  • 微信公众号开发申请退款

    千次阅读 2018-05-10 15:07:01
    准备工作:获取商户证书(java开发使用的证书文件apiclient_cert.p12)微信支付接口中,涉及资金回滚的接口会使用到商户证书,包括退款、撤销接口。商家在申请微信支付成功后,收到的相应邮件后,可以按照指引下载...
  • Java微信APP支付-申请退款

    千次阅读 2018-09-04 16:02:16
    前面已经讲过微信APP支付的统一下单、支付结果通知的接口开发,现在我们讲述一下申请退款的流程开发。 官方的API地址:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_4&amp;index=6 1、应用...
  • 微信支付之退款

    万次阅读 2016-06-16 11:13:45
    下面介绍下微信支付退款功能的开发步骤: 一、下载证书并导入到系统 微信退款是需要证书的,这个证书不是官方demo中的证书,而是需要自己在微信商户平台中的api安全栏下载的证书,在官方的证书使用实例的一个...
  • 微信-原路退款流程

    2019-08-03 16:51:50
    部分内容参考:... 这里主要描述 退款回调信息内容: 微信申请退款接口文档: https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=9_4&index=4 微信退款通知接口文档: https://pay.weixin.qq....
  • 微信JSAPI支付,微信APP支付,微信退款,支付宝手机网站支付,支付宝APP支付,支付宝退款,我都放到个人公众号:JAVA大贼船。觉得个人以后开发会用到的可以关注一下哦!少走点弯路… 官方文档 APP申请退款和JSAPI申请...
  • 最近,经历多个微信小程序支付以及小程序退款实战项目,今天编者经过整理,把小程序申请退款的实战项目案例分享给大家,希望能让大家借鉴,在项目开发中少走弯路。   小程序处理退款前提需安装商户安全证书: ...
  • 微信支付中,有生成预订单接口、查询订单状态接口、关闭订单接口、申请退款接口和退款查询接口。 之前我已经写过一片文章介绍如何使用微信支付拉起收银台支付,完整的介绍了从调用微信接口,到将微信接口返回的数据...
  • 开发之前翻阅了很多帖子,结合自己的实际开发情况,将微信支付/退款 流程以及code贴出,希望通过这一篇帖子就能解决你的问题,有不清楚的直接留言,我会及时回复(ง •̀_•́)ง   一些说明:xxxUtils为工具类...
  • Java微信退款开发

    千次阅读 2019-04-04 15:20:25
    微信支付接口中,涉及资金回滚的接口会使用到商户证书,包括退款、撤销接口。商家在申请微信支付成功后,可以按照以下路径下载:微信商户平台(pay.weixin.qq.com)–>账户设置–>API安全–>证书下载。 ...
  • springboot之微信支付与退款

    千次阅读 2020-02-12 09:12:12
    基于springboot实现小程序微信支付与退款 最近需要再写小程序商城,无可避免的需要用到微信支付与商品售后退款等功能。于是研究了一些大佬的代码之后整合出了这个比较简单的微信支付与退款。 相关内容引用了以下两位...
  • 之前在做公众号开发过程中有集成过微信的支付功能,但是一些帐号相关的申请与设置都由其他同事弄好后提供过来的,最近APP项目中集成微信和支付宝的功能,从相关接口权限和开发都自己亲自走了一遍,不得不说走了不少...
  • 微信支付之原路退款

    万次阅读 2020-08-21 11:32:37
    1.场景还原 最近项目要求上线微信支付原路退款功能,今天笔者就微信支付原路退款的流程梳理下,方便有需要的伙伴借阅2.准备工作①获取微信支付的相关配置WECHATPAY_PARTNER = "150xxxxxxx"; //商户号 ...
  • 微信退款开发 --Java

    千次阅读 2018-06-25 17:50:43
    一、下载证书并导入到系统 微信支付接口中,涉及资金回滚的接口会使用到商户证书,包括退款、撤销接口。商家在申请微信支付成功后,可以按照以下路径下载:微信商户平台(pay.weixin.qq.com)--&gt;账户设置--&...
  • PHP实现微信申请退款

    2020-03-12 10:05:00
    由于业务需求,还需要有微信退款,经过研究和摸索,也终于搞定了。 前期准备:当然是搞定了微信支付,不然怎么退款,这次还是使用官方的demo。当然网上可能也有很多大神自己重写和封装了demo,或许更加好用简洁,...
1 2 3 4 5 ... 20
收藏数 1,142
精华内容 456
关键字:

微信开发生成退款申请