微信小程序支付->退款

   微信小程序退款的时候如果是线上,就会涉及到Linux读取打包后项目存放文件路径失败问题,获取不到其中的微信退款证书,在这里就需要使用流的方式进行读取路径,经大佬指点才最终上线可以退款成功读取证书。本地测试如何读取微信支付退款证书以及安装就不解释了,大家去百度就可以看到一大堆,这里只讲解如何在项目上线后使用流读取退款证书。

1.PayService

/*** 微信支付退款* @param response* @param wechatReturnRequestParam* @return*/Map<String, Object> refund(HttpServletResponse response, WechatReturnRequestParam wechatReturnRequestParam);

2.WechatReturnRequestParam实体类 ->自行需求更换

public class WechatReturnRequestParam {private String appId;//公众账号IDprivate String mchId;//商户号private String nonceStr;//随机字符串private String sign;//签名private String signType = "MD5";//签名类型private String transactionId;//微信支付返回的订单号private String outTradeNo;//商户订单号private String outRefundNo;//退款单号private String totalFee;//订单金额private String refundFee;//退款金额private String refundFeeType = "CNY";//货币类型private String refundDesc;//退款原因private String notifyUrl;//通知地址private String mchSecret;
}

3.PayServiceImpl

@Overridepublic synchronized Map<String, Object> refund(HttpServletResponse response, WechatReturnRequestParam we) {//返回参数Map<String, Object> resMap = new HashMap<>();String resXml = "";try {//拼接统一下单地址参数Map<String, String> paraMap = new HashMap<>();//商户订单号   outTradeNo         String outTradeNo = we.getOutTradeNo();//商户退款单号   transaction_idString outRefundNo = we.getOutRefundNo();//同上,也是填写商户退款单号   transaction_idString transactionId = we.getTransactionId();//订单金额  这两笔金额一定要一致,都是支付的金额String totalFee = we.getTotalFee().toString();//退款金额   CommUtils此工具类中方法可进行元->分的转换String refundFee = we.getRefundFee().toString();System.out.println("订单号="+outTradeNo);paraMap.put("appid",WechatConfig.Appid);paraMap.put("mch_id",WechatConfig.mch_id);//生成的随机字符串paraMap.put("nonce_str",WXPayUtil.generateNonceStr());paraMap.put("out_trade_no",outTradeNo);//商户订单号idparaMap.put("out_refund_no",outRefundNo);//商户退款单号paraMap.put("total_fee",totalFee);paraMap.put("refund_fee",refundFee);String sign = WXPayUtil.generateSignature(paraMap, WechatConfig.key);//生成签名paraMap.put("sign",sign);//将所有参数map转化成xmlString xml = WXPayUtil.mapToXml(paraMap);System.out.println("xml"+xml);//退款路径《https://api.mch.weixin.qq.com/secapi/pay/refund》String refund_url = WechatConfig.refund_url;System.out.println("refund_url" + refund_url);System.out.println("------------------------------分------------------------------割------------------------------线------------------------------");//发送post请求申请退款String xmlStr = HttpClientUtil.doRefund(refund_url, xml);System.out.println("退款xmlStr=========="+xmlStr);System.out.println("------------------------------分------------------------------割------------------------------线------------------------------");/* 退款成功回调修改订单状态 */if (xmlStr.indexOf("SUCCESS") != -1){Map<String, String> map = WXPayUtil.xmlToMap(xmlStr);if (map.get("return_code").equals("SUCCESS")){resMap.put("success",true);//此步说明退款成功resMap.put("data","退款成功");System.out.println("退款成功");System.out.println("------------------------------分------------------------------割------------------------------线------------------------------");try {//在这里执行自己呃业务逻辑,在退款之后要对自己订单进行一个状态的修改(已退款)//业务逻辑没有什么//// //通知微信服务器不要再进行回调actionresXml = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());out.write(resXml.getBytes());out.flush();out.close();System.out.println("返回给微信的值:"+resXml.getBytes());} catch (IOException e) {resMap.put("fail","订单状态修改失败");}}}else {resMap.put("success","fail");//此步说明退款成功}} catch (Exception e) {e.printStackTrace();}return resMap;}

4.CommUtils

在进行退款时候穿的金额参数需要进行一次以分为单位的转换

/*** 元转换成分* @param amount* @return*/public static String getMoney(String amount) {if(amount==null){return "";}// 金额转化为分为单位// 处理包含, ¥ 或者$的金额String currency =  amount.replaceAll("\\$|\\¥|\\,", "");int index = currency.indexOf(".");int length = currency.length();Long amLong = 0l;if(index == -1){amLong = Long.valueOf(currency+"00");}else if(length - index >= 3){amLong = Long.valueOf((currency.substring(0, index+3)).replace(".", ""));}else if(length - index == 2){amLong = Long.valueOf((currency.substring(0, index+2)).replace(".", "")+0);}else{amLong = Long.valueOf((currency.substring(0, index+1)).replace(".", "")+"00");}return amLong.toString();}
  1. WXPayUtil (这里补全一下代码,之前的工具类中方法可能有所缺失)

生成的随机字符串用于拼接统一下单地址并生成签名返回给微信通知,而后将所有参数map转化成xml


public class WXPayUtil {private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";private static final Random RANDOM = new SecureRandom();/*** 获取随机字符串 (采用截取8位当前日期数  + 4位随机整数)* @return*/public static String getNonceStr() {//获得当前日期Date now = new Date();SimpleDateFormat outFormat = new SimpleDateFormat("yyyyMMddHHmmss");String currTime = outFormat.format(now);//截取8位String strTime = currTime.substring(8, currTime.length());//得到4位随机整数int num = 1;double random = Math.random();if (random < 0.1) {random = random + 0.1;}for (int i = 0; i < 4; i++) {num = num * 10;}num = (int)random * num;return strTime + num;}/*** 解析xml得到 prepay_id 预支付id* @param result* @return* @throws DocumentException*/public static String getPayNo(String result) throws DocumentException {Map<String, String> map = new HashMap<String, String>();InputStream in = new ByteArrayInputStream(result.getBytes());SAXReader read = new SAXReader();Document doc = read.read(in);//得到xml根元素Element root = doc.getRootElement();//遍历  得到根元素的所有子节点@SuppressWarnings("unchecked")List<Element> list =root.elements();for(Element element:list){//装进mapmap.put(element.getName(), element.getText());}//返回码String return_code = map.get("return_code");//返回信息String result_code = map.get("result_code");//预支付idString prepay_id = "";//return_code 和result_code 都为SUCCESS 的时候返回 预支付idif(return_code.equals("SUCCESS")&&result_code.equals("SUCCESS")){prepay_id = map.get("prepay_id");}return prepay_id;}/***//     * @param requestUrl请求地址//     * @param requestMethod请求方法//     * @param outputStr参数*/public static String httpRequest(String requestUrl,String requestMethod,String outputStr){// 创建SSLContextStringBuffer buffer = null;try{URL url = new URL(requestUrl);HttpURLConnection conn = (HttpURLConnection) url.openConnection();conn.setRequestMethod(requestMethod);conn.setDoOutput(true);conn.setDoInput(true);conn.connect();//往服务器端写内容if(null !=outputStr){OutputStream os=conn.getOutputStream();os.write(outputStr.getBytes("utf-8"));os.close();}// 读取服务器端返回的内容InputStream is = conn.getInputStream();InputStreamReader isr = new InputStreamReader(is, "utf-8");BufferedReader br = new BufferedReader(isr);buffer = new StringBuffer();String line = null;while ((line = br.readLine()) != null) {buffer.append(line);}br.close();}catch(Exception e){e.printStackTrace();}return buffer.toString();}public static String urlEncodeUTF8(String source){String result=source;try {result=java.net.URLEncoder.encode(source, "UTF-8");} catch (UnsupportedEncodingException e) {// TODO Auto-generated catch blocke.printStackTrace();}return result;}/*** 签名字符串* @param key 密钥* @return 签名结果*/public static String sign(String text, String key, String input_charset) {text = text + "&key=" + key;return DigestUtils.md5Hex(getContentBytes(text, input_charset));}/*** 除去数组中的空值和签名参数* @param sArray 签名参数组* @return 去掉空值与签名参数后的新签名参数组*/public static Map<String, String> paraFilter(Map<String, String> sArray) {Map<String, String> result = new HashMap<String, String>();if (sArray == null || sArray.size() <= 0) {return result;}for (String key : sArray.keySet()) {String value = sArray.get(key);if (value == null || value.equals("") || key.equalsIgnoreCase("sign")|| key.equalsIgnoreCase("sign_type")) {continue;}result.put(key, value);}return result;}/*** 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串* @param params 需要排序并参与字符拼接的参数组* @return 拼接后字符串*/public static String createLinkString(Map<String, String> params) {List<String> keys = new ArrayList<String>(params.keySet());Collections.sort(keys);String prestr = "";for (int i = 0; i < keys.size(); i++) {String key = keys.get(i);String value = params.get(key);if (i == keys.size() - 1) {// 拼接时,不包括最后一个&字符prestr = prestr + key + "=" + value;} else {prestr = prestr + key + "=" + value + "&";}}return prestr;}/*** 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) {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 {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");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;}/*** 生成签名** @param data 待签名数据* @param key API密钥* @return 签名*/public static String generateSignature(final Map<String, String> data, String key) throws Exception {return generateSignature(data, key, SignType.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(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 (SignType.MD5.equals(signType)) {return MD5(sb.toString()).toUpperCase();}else if (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 Logger getLogger() {Logger logger = LoggerFactory.getLogger("wxpay java sdk");return logger;}}

6.退款路径

此退款路径在前两篇支付与回调中都提及过此配置类,关于微信配置都在WechatConfig中,如有需要可看前两篇文章。

/*退款路径*/public static final String refund_url = "https://api.mch.weixin.qq.com/secapi/pay/refund";

7.此时发送post请求申请退款就需要读取《微信商户证书->退款证书》

在这里我只写对于上线Linux的打包项目读取当中static文件证书方式

(1)需要去pay.weixin.com 下载证书

(2)解压 apiclient_cert.p12

将这两个文件放到项目中resources中,static名字随意取什么,要的是代码中一定要路径一致。准备工作做好之后就开始读取文件的方式。

注意:想要读取此路径,需要使用流的写法。或许有其他方法,但是我能力有限,确实不太清楚。

/*** https双向签名认证,用于支付申请退款*///证书的密码  -> 自己的商户号public static String SSLCERT_PASSWORD = "***商户号";@SuppressWarnings("deprecation")public static String doRefund(String url, String data) throws Exception {byte[] certData = null;try {InputStream certStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("static/apiclient_cert.p12");System.out.println(certStream + "有没有");certData = IOUtils.toByteArray(certStream);certStream.close();} catch (Exception e) {e.printStackTrace();}ByteArrayInputStream certBis = new ByteArrayInputStream(certData);System.out.println(certBis + "有没有");//注意PKCS12证书 是从微信商户平台-》账户设置-》 API安全 中下载的KeyStore keyStore = KeyStore.getInstance("PKCS12");keyStore.load(certBis, SSLCERT_PASSWORD.toCharArray());//下载证书时的密码、默认密码是你的MCHID mch_idSSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, SSLCERT_PASSWORD.toCharArray())//这里也是写密码的.build();SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext,SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);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();}}

到此微信支付的退款就全部写完,大家共同进步,关注一下吧。

微信小程序退款功能(详解完整)相关推荐

  1. 微信小程序退款流程详解

    原创 Dr Hydra 码农参上 2020-11-29 11:00 收录于合集#微信开发技术3个 在上一篇中我们介绍了微信小程序的支付流程,这一篇接着讲一下小程序的退款流程,首先看一下官方给出的介绍: ...

  2. php小程序地图处理,微信小程序 地图map详解及简单实例

    微信小程序 地图map 微信小程序map 地图属性名类型默认值说明longitudeNumber中心经度 latitudeNumber中心纬度 scaleNumber1缩放级别 markersArra ...

  3. 小程序向Java传值,微信小程序 页面传值详解

    微信小程序 页面传值详解 一. 跨页面传值. 1 . 用 navigator标签传值或 wx.navigator, 比如 这里将good_id=16 参数传入detail页面, 然后detail页面的 ...

  4. 微信小程序详解 php,微信小程序canvas基础详解

    canvas 元素用于在网页上绘制图形.HTML5 的 canvas 元素使用 JavaScript 在网页上绘制2D图像.本文主要和大家分享微信小程序canvas基础详解,希望能帮助到大家. 一.了 ...

  5. 微信小程序底部菜单详解

    微信小程序底部菜单详解 只需要在app.json里面修改配置,即可 {"pages":["pages/index/index","pages/logs ...

  6. 微信小程序详解 php,微信小程序列表开发详解

    本文主要和大家分享微信小程序列表开发详解,主要以代码的形式和大家分享,希望能帮助到大家. 一.知识点 (一).列表渲染 wx:for tip:wx:for="array"可以等于参 ...

  7. php 微信小程序 循环 多选,微信小程序 for 循环详解

    1,wx:for 在组件上使用wx:for控制属性绑定一个数组,即可使用数组中各项的数据重复渲染该组件.默认数组的当前项的下标变量名默认为index,数组当前项的变量名默认为item 事例如下: wx ...

  8. 【迷宫】地下迷宫游戏-微信小程序开发流程详解

    可曾记得,小时候上学路边买的透明铅笔盒,里面内嵌了一个小球,它用重力可从起点滚动到终点,对小朋友来说是感觉有趣的,在这个游戏的基础上,弄一款微信小程序的迷宫探索游戏试试,在不同关卡的迷宫中解开机关与谜 ...

  9. 微信小程序后台开发详解

    微信小程序后台开发 前言 开发环境 开发流程 项目整体结构 接口开发 项目部署 ip映射 Nginx反向代理 gunicorn+super多进程开启服务+进程监控 ssl证书 小程序常用功能 微信支付 ...

最新文章

  1. 学计算机专业英语报告范文,计算机学习报告
  2. html 导航右侧弹出层,CSS导航栏及弹窗示例代码
  3. 【Android 安装包优化】资源混淆 ( resources.arsc 资源映射表混淆 | resources.arsc 资源映射表二进制格式分析 | 混淆全局字符串池和资源名称字符串池 )
  4. django模板中使用JQ代码实现瀑布流显示效果
  5. MySQL从入门到精通50讲(十)-MySQL中null值如何处理
  6. Ice笔记---异步程序设计demo
  7. WCF 第二章 契约 单向操作
  8. iOS 各种编译错误汇总
  9. DataGrid单击行时改变颜色
  10. 互联网汽车迎新成员 Alibaba YunOS Auto冠名2016世俱杯
  11. fork linux 低权限,linux/Unix下python的fork详解及应用举例
  12. 分成互质组 (信息学奥赛一本通-T1221)
  13. Normalization的总结框架
  14. android消息机制—Looper
  15. linux救援模式使用yum,linux学习笔记-第二课-yum,救援模式,单用户模式,运行级别...
  16. c语言文本编辑器源代码_程序员专属的10个免费编程文本编辑器,哪个是你的最爱?...
  17. 剑指offer——python【第16题】合并两个有序链表
  18. tcp 测试工具 android,安卓版手机tcp调试助手
  19. 新浪微博API使用入门:申请应用、授权、使用官方java版本SDK
  20. Linux系统开发|QT制作聊天软件实验报告

热门文章

  1. edger多组差异性分析_用R实现批量差异分析(t检验和方差分析),自己算P值
  2. Vue设置浏览器title-icon
  3. 浅谈极值点偏移(化为单变量以及ALG不等式)
  4. 机器学习-泰坦尼克号幸存者预测
  5. JavaScript——模拟自动饮料机
  6. ORACLE分配DBA权限
  7. 【关于油猴的安装和使用的教程】
  8. 一片外文的计算机网络方面的文献,计算机网络专科外文文献 计算机网络专科核心期刊参考文献有哪些...
  9. 海思芯片中VPSS的group和channel的概念.
  10. 论文笔记:FILLING THE G AP S: MULTIVARIATE TIME SERIES IMPUTATION BY GRAPH NEURAL NETWORKS