接口链接:https://api.mch.weixin.qq.com/secapi/pay/refund

当交易发生之后一段时间内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付款退还给买家,微信支付将在收到退款请求并且验证成功之后,按照退款规则将支付款按原路退到买家帐号上。需要下载数

字证书,Java只需要商户证书文件apiclient_cert.p12。

注意:

1、交易时间超过一年的订单无法提交退款

2、微信支付退款支持单笔交易分多次退款,多次退款需要提交原支付订单的商户订单号和设置不同的退款单号。申请退款总金额不能超过订单金额。 一笔退款失败后重新提交,请不要更换退款单号,请使用原商户退款单号

3、请求频率限制:150qps,即每秒钟正常的申请退款请求次数不超过150次

错误或无效请求频率限制:6qps,即每秒钟异常或错误的退款申请请求不超过6次

4、每个支付订单的部分退款次数不能超过50次

1、将微信退款所需参数封装成RefundInfo实体

public class RefundInfo implementsSerializable{/****/

private static final long serialVersionUID = 1L;/*** 公众账号ID*/

privateString appid;/*** 商户号*/

privateString mch_id;/*** 随机字符串*/

privateString nonce_str;/*** 签名*/

privateString sign;/*** 微信订单号*/

privateString transaction_id;/*** 商户退款单号,同一退款单号多次请求 只退款一次*/

privateString out_refund_no;/*** 订单金额*/

private inttotal_fee;/*** 退款金额*/

private intrefund_fee;/*** 退款结果通知url*/

privateString notify_url;/*** 退款原因:可不填*/

privateString refund_desc;//省略setter、getter方法

}

创建RefundInfo

/*** 微信退款的xml的java对象

*@paramparams UniformOrderParams

*@return

*/

public staticRefundInfo createRefundInfo(RefundParams refundParams) {

WeixinConfig wxConfig=WeixinConfig.getInstance();

String nonce_str= new StringWidthWeightRandom().getNextString(32);

RefundInfo refundInfo= newRefundInfo();

refundInfo.setAppid(wxConfig.getAppid());

refundInfo.setMch_id(wxConfig.getMch_id());

refundInfo.setNonce_str(nonce_str);

refundInfo.setNotify_url(wxConfig.getWx_refund_notify_url());

refundInfo.setRefund_desc(refundParams.getRefund_desc());

refundInfo.setRefund_fee(refundParams.getRefund_fee());

refundInfo.setTotal_fee(refundParams.getTotal_fee());

refundInfo.setTransaction_id(refundParams.getTransaction_id());

refundInfo.setOut_refund_no(refundParams.getOut_refund_no());returnrefundInfo;

}

2、调前面写的统一的微信调用接口申请退款,将微信的返回结果转换成map

@Overridepublic Maprefund(RefundParams refundParams) {

RefundInfo refundInfo=CommonUtil.createRefundInfo(refundParams);//将bean转换为map

SortedMap paras =CommonUtil.convertBean(refundInfo);

String sgin=SginUtil.createSgin(paras);

refundInfo.setSign(sgin);

String xml= CommonUtil.beanToXML(refundInfo).replace("__", "_").

replace("", "");

WeixinConfig wxConfig=WeixinConfig.getInstance();

Map map =CommonUtil.httpsRequestToXML(

wxConfig.getWx_refund_url(),"POST",xml,true);returnmap;

}

3、微信将退款结果通过notify_url通知商户处理退款结果

在微信返回的退款结果中有一个加密字段:req_info,这个加密字段需要进行三步解密才能获得完整的退款结果。官方给出的解密步骤如下:

3.1 通过微信退款通知获取到req_info

//从request中获取通知信息,并转化成map

Map map =CommonUtil.parseXml(request);//微信退款信息

String return_code = (String) map.get("return_code");

String return_msg= (String) map.get("return_msg");

String req_info= (String) map.get("req_info");//返回的加密信息,需要解密

3.2 对加密串req_info做base64解码,得到加密串B

byte[] B = Base64.decodeBase64(base64Data)

3.3对商户key做md5,得到32位小写key*

MD5Util.MD5Encode(password, "UTF-8").toLowerCase().getBytes()

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

/*** AES解密

*

*@parambase64Data 解密内容

*@parampassword 解密密码

*@return*@throwsException*/

public static String decryptData(String base64Data,String password) throwsException {//创建密码器

Cipher cipher =Cipher.getInstance(ALGORITHM_MODE_PADDING);//使用密钥初始化,设置为解密模式

cipher.init(Cipher.DECRYPT_MODE, getSecretKey(password));//执行操作

byte[] result =cipher.doFinal(Base64.decodeBase64(base64Data));return new String(result, "utf-8");

}

3.5 将解密后的字符串转化为map,解析出退款结果

String decryptResult =AESUtil.decryptData(req_info,WeixinConfig.getInstance().getWxKey());

Map reqMap =CommonUtil.parseXml(decryptResult);

log.info(reqMap);//微信退款单号

String refundId = reqMap.get("refund_id");//微信付款订单号

String outTradeNo = reqMap.get("out_trade_no");

...

4、解密过程可能出现的问题

4.1微信官网指定解密的填充方式为:PKCS7Padding,解密时出现bug:

在java中用aes256进行加密,但是发现java里面不能使用PKCS7Padding,而java中自带的是PKCS5Padding填充,那解决办法是,通过BouncyCastle组件来让java里面支持PKCS7Padding填充。

Security.addProvider(new BouncyCastleProvider());

4.2 因为美国的出口限制,Sun通过权限文件(local_policy.jar、US_export_policy.jar)做了相应限制。可能出现bug:

Oracle在其官方网站上提供了无政策限制权限文件(Unlimited Strength Jurisdiction Policy Files),我们只需要将其部署在JRE环境中,就可以解决限制问题。把无政策限制权限文件的local_policy.jar文件和US_export_policy.jar替换掉原来jdk安装目录的安全目录下,如:%jre%/lib/security。

JDK8 jar包下载地址:

JDK7 jar包下载地址:

DK6 jar包下载地址:

附完整AES加解密代码:

AESUtil:

packagecom.sanwn.framework.core.util;importjava.security.Security;importjavax.crypto.Cipher;importjavax.crypto.spec.SecretKeySpec;importorg.apache.commons.codec.binary.Base64;importorg.bouncycastle.jce.provider.BouncyCastleProvider;public classAESUtil {/*** 密钥算法*/

private static final String ALGORITHM = "AES";/*** 加解密算法/工作模式/填充方式*/

private static final String ALGORITHM_MODE_PADDING = "AES/ECB/PKCS7Padding";/*** AES加密

*

*@paramdata 加密内容

*@parampassword 加密密码

*@return*@throwsException*/

public static String encryptData(String data,String password) throwsException {//Security.addProvider(new BouncyCastleProvider());//创建密码器

Cipher cipher =Cipher.getInstance(ALGORITHM_MODE_PADDING);//初始化为加密模式的密码

cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(password));//加密

byte[] result =cipher.doFinal(data.getBytes());returnBase64.encodeBase64String(result);

}/*** AES解密

*

*@parambase64Data 解密内容

*@parampassword 解密密码

*@return*@throwsException*/

public static String decryptData(String base64Data,String password) throwsException {

Security.addProvider(newBouncyCastleProvider());//创建密码器

Cipher cipher =Cipher.getInstance(ALGORITHM_MODE_PADDING);//使用密钥初始化,设置为解密模式

cipher.init(Cipher.DECRYPT_MODE, getSecretKey(password));//执行操作

byte[] result =cipher.doFinal(Base64.decodeBase64(base64Data));return new String(result, "utf-8");

}/*** 生成加密秘钥

*

*@return

*/

private staticSecretKeySpec getSecretKey(String password) {

SecretKeySpec key= new SecretKeySpec(MD5Util.MD5Encode(password, "UTF-8").toLowerCase().getBytes(), ALGORITHM);returnkey;

}public static voidmain(String[] args){

String A= "QMp6bLccUtxAhoK6KxevK0yA0hMESKUbnz1paA2dU4nIw5tPbUjr3UiRdGzNxfRve91MZgHuUSMcOqfvQcRWoxrEoWGLEeqabGsPgZe538vbAaLVGBhV49BEFP8MfGu3ux/q/+Clz5tmtgG7JdZzEsV3S9z1ki2JlG0usNmsWbSS8VIhKBRbAsCejzGs7YLD4FNA89YZ0fEpAMLhAhmRJmw5ymjPTSUHZ4RkPWDqOrN58AkDuKkM3eL/JzFK6coimp9YJhkeY8rCEmKcLgDM3G6tfPBQ3z2hS3yyhJWLoYkpuRk7qcWMyuls0t8ix/2vuWmilQCyraC6uSLdfK4d7wr6H+t7cTELoNOyrKSIIrTvy5IGqGQuS4+fUjrC5G3jVDa9Ol7SHDJlYzWTvtN3/WS+MjMPsjyrkEudjZNen6kMuiQcTNyCtynAshSpmLQa9CQx4u1pqkthtisRKvMjizefZEPSjW0bezM1aZOkkw4syDy/4PB18QnMjRbJJqZ0S5EfRJ9gN3fgxb6+GXQy3M9BIP/Pvx7FRMorKapq/6ACJJHesG2Rq4sWdAMBoYiFz5OKpIlLAHhz3KpFXbulYimm3zSJChpxXiymOqEt4ozrStiK6jet5Df/jGkjXJAiUG1xEkmDXoG9+WbHr054V4qr2NFCotjOoNCxN1XM18OtFbU4ZBEX9sCtsx5AmEAbyexu8M7/7NpBtXZhSB8VwcoYGhg7VgiEMpAqZaG/94RkjLGjQ9vRn3yQCwaUyAkkgvlqOqV5KcoQvq0UKN/6adNGuoEfiF5daPh2y3JfYTiY2fTMTS9iLfWK0vgZ9doLys8UJvEwwxl5ohnLXYTi7I6tA4dNkRihFMnuNqJblg1VtX4fTfYfQTMyYj2SbiP8MuNLjJxE60gDjC2fZnv6evbqp7ARSsSH/O0EGcYcgfLCTrfODWkJVkUZrxTMl4muuekafqA15wmGMpl4BwjC3rTepdd2YpY8Psilst8q7kbmZCtQ4ezykoFuanzvVmz+T0Ku72hmXd7VCRaU+Q3ORA==";try{

String B= decryptData(A,"your password");

System.out.println(B);

}catch(Exception e) {//TODO Auto-generated catch block

e.printStackTrace();

}

}

}

MD5Util

packagecom.sanwn.framework.core.util;importjava.security.MessageDigest;public classMD5Util {public final staticString MD5(String s) {char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};try{byte[] btInput =s.getBytes();//获得MD5摘要算法的 MessageDigest 对象

MessageDigest mdInst = MessageDigest.getInstance("MD5");//使用指定的字节更新摘要

mdInst.update(btInput);//获得密文

byte[] md =mdInst.digest();//把密文转换成十六进制的字符串形式

int j =md.length;char str[] = new char[j * 2];int k = 0;for (int i = 0; i < j; i++) {byte byte0 =md[i];

str[k++] = hexDigits[byte0 >>> 4 & 0xf];

str[k++] = hexDigits[byte0 & 0xf];

}return newString(str);

}catch(Exception e) {

e.printStackTrace();return null;

}

}private static String byteArrayToHexString(byteb[]) {

StringBuffer resultSb= newStringBuffer();for (int i = 0; i < b.length; i++)

resultSb.append(byteToHexString(b[i]));returnresultSb.toString();

}private static String byteToHexString(byteb) {int n =b;if (n < 0)

n+= 256;int d1 = n / 16;int d2 = n % 16;return hexDigits[d1] +hexDigits[d2];

}public staticString MD5Encode(String origin, String charsetname) {

String resultString= null;try{

resultString= newString(origin);

MessageDigest md= MessageDigest.getInstance("MD5");if (charsetname == null || "".equals(charsetname))

resultString=byteArrayToHexString(md.digest(resultString.getBytes()));elseresultString=byteArrayToHexString(md.digest(resultString.getBytes(charsetname)));

}catch(Exception exception) {

}returnresultString;

}private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};public static voidmain(String[] asd) {

String con= "your password";

String str= MD5Encode(con, "UTF-8");

System.out.println(str.toUpperCase());

}

}

测试结果:

java微信退款接口demo_微信公众平台开发(6) 微信退款接口相关推荐

  1. 微信公众平台开发(73) 客服接口发送客服消息

    微信公众平台开发(73) 客服接口发送客服消息 原文:微信公众平台开发(73) 客服接口发送客服消息 微信公众平台开发 客服接口 发送客服消息 作者:方倍工作室  地址:http://www.cnbl ...

  2. 视频教程-微信公众平台开发入门-微信开发

    微信公众平台开发入门 订阅后,请点击此处观看大牛全套视频课程:课程播放页链接 价格可能因活动略有浮动 刘运强 ¥39.00 立即订阅视频课 订阅后:请点击此处观看视频课程 视频教程-微信公众平台开发入 ...

  3. 微信公众平台开发[2] —— 微信端分享功能

    背景 初次尝试微信公众号的开发,对于学习方法的探索都是来源于网上的博客.问答,对于参差不齐的信息,自己也是有苦说不出,抽出一点时间写点文章,既是对自己的学习总结,也希望给予同是菜鸟的小白一点帮助. 今 ...

  4. php 微信公众平台开发之微信群发信息

    这篇文章主要为大家详细介绍了php微信公众平台开发之微信群发信息,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 1.目的 完成在微信公众号中群发消息.这里只是完成简单的文字发送.也可以发送语音图片 ...

  5. java获取微信用户源码_Java微信公众平台开发(十)--微信用户信息的获取

    前面的文章有讲到微信的一系列开发文章,包括token获取.菜单创建等,在这一篇将讲述在微信公众平台开发中如何获取微信用户的信息,在上一篇我们有说道微信用户和微信公众账号之间的联系可以通过Openid关 ...

  6. 微信公众平台开发(121) 微信二维码海报

    关键字:微信公众平台 二维码 海报 作者:方倍工作室 原文: http://www.cnblogs.com/txw1958/p/weixin-poster.html 本文介绍微信公众平台下二维码海报的 ...

  7. 微信公众平台开发(110) 微信连Wi-Fi

    微信连Wi-Fi是为商家的线下场所提供一套完整和便捷的微信连Wi-Fi的方案.商家接入微信连Wi-Fi后,顾客无需输入繁琐的Wi-Fi密码,通过微信扫二维码等方式即可快速上网.微信连Wi-Fi还帮助商 ...

  8. 微信公众平台开发 mysql_微信公众平台开发数据库操作

    一.简介 前面讲解的功能开发都是简单的调用API 完成的,没有对数据库进行操作.在接下来的高级功能开发中,需要使用到数据库,所以在这一篇中,将对MySQL 数据库的操作做一下简单的介绍,以供读者参考. ...

  9. Java微信公众平台开发(十)--微信自定义菜单的创建实现

    转自:http://www.cuiyongzhi.com/post/48.html 自定义菜单这个功能在我们普通的编辑模式下是可以直接在后台编辑的,但是一旦我们进入开发模式之后我们的自定义菜单就需要自 ...

最新文章

  1. as3绕过策略文件给视频截图
  2. CCAI 2020 | 史元春:走出AI伦理困境「演讲回顾」
  3. linux git hudson,如何使用SSH密钥配置Hudson和git插件
  4. JavaScript学习笔记(十)构造函数封装、对象检测、对象继承
  5. uint32是什么数据类型_C++编程基础: 4. 数据类型
  6. dubbox 的各种管理和监管
  7. Collectors.counting()
  8. ITIL学习笔记——ITIL入门小知识
  9. 移动创业者,这么多免费资源,你不要?
  10. 前端分页功能(通用)
  11. invoke-obfuscation使用遇到的问题及Encoding免杀
  12. ch341a i2c 安卓_CH341A转I2C的Labview应用说明
  13. php存在文件夹_PHP创建文件夹 PHP判断文件夹文件是否存在
  14. linux 串口 lsr 0xc9,linux 串口驱动(三)
  15. GraphicsMagick创建空白图片
  16. Adnroid 自定义流式布局
  17. effective morden c++4
  18. 中国定向公开赛海南昌江站开赛 600余名选手穿越雨林
  19. 两步快速获取小程序源码
  20. python decimal_连载|想用Python做自动化测试?了解数值计算和随机数生成神器

热门文章

  1. 服务器桌面显示计算机网络连接,阴阳师桌面版连接不上服务器怎么办 连接不上服务器解决办法...
  2. python来玩猜字游戏吧
  3. net、FTP、telnet命令和远程登录重启服务器
  4. 3D OpenGL ES
  5. Intergraph CADWorx (Plant, PID, Equipment, IP, SpecEditor) 2014 build 23.07.2013
  6. Golang中文乱码配置解决流程
  7. 如何制作一个塔防游戏 Cocos2d-x 2.0.4
  8. git 的合并原理(递归三路合并算法)
  9. R730服务器环境搭建(centos7、lanproxy、docker、k8s)
  10. 基于golang编写的区块链公链demo