原文链接:https://www.cnblogs.com/hongzm/p/7366510.html

微信公众号退款开发

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

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,拒绝退款:4Integer sale_type = "";// 根据订单pk获取订单VOEasyhouseSalelogVO 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();}}}

java微信退款接口相关推荐

  1. java 微信退款接口_java版微信和支付宝退款接口

    本文实例为大家分享了java微信退款接口和支付宝退款接口的具体代码,供大家参考,具体内容如下 1.微信退款接口 相对来说我感觉微信的退款接口还是比较好调用的,直接发送httppost请求即可: /** ...

  2. java微信退款接口demo_微信公众平台开发(6) 微信退款接口

    接口链接:https://api.mch.weixin.qq.com/secapi/pay/refund 当交易发生之后一段时间内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付款退还给 ...

  3. Java 微信支付接口(统一下单,异步回调,订单退款,取消订单)

    一.准备工作 APP绑定微信商户平台获取商户id(mchID).证书(商户后台下载).支付签名密钥(商户后台设置api密钥).退款签名密钥(商户后台设置api密钥ipv3)等 1.导入微信支付SDK ...

  4. JAVA 支付宝退款接口

    2019独角兽企业重金招聘Python工程师标准>>> **构建表单参数 ** <input type="hidden" name="WIDbat ...

  5. Java微信分享接口开发

    Java微信分享接口开发 http://blog.csdn.net/juewang_love/article/details/76076417

  6. 微信退款接口(你们遇到的坑)

    为了自我学习和交流PHP(jQuery,Linux,lamp,shell,JavaScript,服务器)等一系列的知识,希望光临本博客的人可以进来交流.寻求共同发展.搭建平台.本人博客也有许多的技术文 ...

  7. java微信退款解密,微信退款-异步通知 报文解密

    拿到报文 对req_info 用商户秘钥进行解密 package com.ly.upg.message.util; import com.alibaba.fastjson.JSONObject; im ...

  8. java 微信退款配置_微信支付退款配置

    微信支付退款配置 1.微信支付配置 第一步,登录商城后台,设置->交易设置->支付配置 ,选择微信支付,点击配置进入到微信支付参数配置界面. 从应用ID和应用密钥下面的提示可以看出,微信支 ...

  9. php中paypal/支付宝/微信退款接口封装

    class OrderController extends AdminbaseController{//提交退款信息到支付公司服务器public function refund_post(){$id ...

最新文章

  1. 一个简单的动态内表alv案例
  2. 哪吒之魔童降世视听语言影评_豆瓣评分8.7,这个“新哪吒”不一般|《哪吒之魔童降世》影评...
  3. c语言CString转数字函数,CString与16进制的CByteArray之间相互转化
  4. 【英语学习工具】学习英语硬背硬记太难了, 在这里解说 LeHoCat 提供免费的 视频集 工具的使用方法, 看视频学英语的工具, 制作英语教学课件的工具, 帮助自学英语(详细图文)第2版
  5. 在Fragment中使用ListView+ViewPage
  6. windows 10和windows server 2016系统AD的administrator密码修改
  7. 可以叫板Google的一个搜索引擎——DuckDuckGo
  8. 完全卸载SQL server 2005的方法
  9. fabric 中 peer 和 couch 容器中网络和数据存放目录地址
  10. 上网行为安全之终端识别和管理技术
  11. xy坐标正负方向_xy坐标分别代表什么
  12. 实时高速实现改进型中值滤波算法_爱学术_免费下载
  13. Python实例:七段数码管
  14. ubuntu壁纸自动切换
  15. SSLOJ 1323.交流
  16. RS485为什么需要隔离?什么情况下可以不用隔离?
  17. C++ accumulate()函数
  18. JAVA面试基础知识整理
  19. C/C++,python,java,C#月经贴问题
  20. 三重积分的C语言验算

热门文章

  1. 制作可以在微信和钉钉都能识别的二维码
  2. 解决使用360卫士清理后出来的系统异常故障
  3. 为什么巨头不约而同选择VR一体机?这篇测评或许可以告诉你
  4. 【概率论】蒙提霍尔问题
  5. 论文:Exploring Phrase Grounding without Training: Contextualisation and Extension to Text-Based Image
  6. java计算机毕业设计基于安卓Android/微信小程序的宿舍管理服务平台APP
  7. Flash WebGame 开发经验心得和PureMVC框架细说
  8. IT圈有哪些被经常读错
  9. 2022年学习和实习总结——收获颇多
  10. python利用百度云接口实现文字OCR功能