【微信退款】Java实现微信APP退款和JSAPI退款流程

前言

微信登录网页授权与APP授权
微信JSAPI支付
微信APP支付
微信APP和JSAPI退款
支付宝手机网站支付
支付宝APP支付
支付宝退款
以上我都放到个人公众号,搜一搜:JAVA大贼船,文末有公众号二维码!觉得个人以后开发会用到的可以关注一下哦!少走点弯路…

官方文档

APP申请退款和JSAPI申请退款可以共用,各自的文档基本一样

微信APP申请退款文档

https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_4&index=6

微信JSAPI申请退款文档

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

代码实现
引入依赖
		<!-- 微信支付 -->
			<dependency>
				<groupId>com.github.wxpay</groupId>
				<artifactId>wxpay-sdk</artifactId>
				<version>0.0.3</version>
			</dependency>
配置参数

application.yml

# 微信相关配置
wx:
  #商户 ID(微信支付平台-账户中心-个人信息)
  MCH_ID: 
  # APP_ID(微信开放平台或公众号查找)
  A_APP_ID: 
  # 秘钥(微信开放平台或公众号查找)
  A_APP_SECRET: 
  # APP_ID(微信开放平台或公众号查找)
  H_APP_ID: 
  # 秘钥(微信开放平台或公众号查找)
  H_APP_SECRET: 
  # 消息加解密所用到的解密串(微信公众号-基本配置查找)
  H_ENCODINGAESKEY:
  # token(微信公众号-基本配置查找)
  H_TOKEN: 
  # 支付秘钥KEY(微信支付平台-账户中心-api安全-api秘钥)
  KEY: 
  # 支付商户证书所载目录(微信支付平台-账户中心-api安全-API证书)
  CERT_PATH: 
  #支付成功回调地址
  WX_REFUND_CALLBACK_URL: 

YmlParament

@Component
@Data
public class YmlParament {
	/*微信相关字段*/
	@Value("${wx.A_APP_ID}")
	private String a_app_id;
    
	@Value("${wx.A_APP_SECRET}")
	private String a_app_secret;
    
	@Value("${wx.MCH_ID}")
	private String mch_id;
    
	@Value("${wx.KEY}")
	private String key;
    
	@Value("${wx.CERT_PATH}")
	private String cert_path;
    
    @Value("${wx.H_APP_ID}")
	private String h_app_id;
    
	@Value("${wx.H_APP_SECRET}")
	private String h_app_secret;
    
	@Value("${wx.H_ENCODINGAESKEY}")
	private String h_encodingaeskey;
    
	@Value("${wx.H_TOKEN}")
	private String h_token;
    
    @Value("${wx.WX_CALLBACK_URL}")
	private String wx_refund_callback_url;

微信申请退款
  • 初始化微信配置
@Component
public class WxConfig {
   @Autowired
   private YmlParament ymlParament;
   
     /**
    * 初始化微信配置
    * @throws Exception 
    */
   @Bean(autowire = Autowire.BY_NAME,value = WxParament.H5_WX_PAY)
   public WXPay setH5WXPay() throws Exception {
      return new WXPay(new WxPayConfig(
            ymlParament.getCert_path(),
            ymlParament.getH_app_id(),
            ymlParament.getMch_id(),
            ymlParament.getkey()));
   }
 }

	@Bean(autowire = Autowire.BY_NAME,value = WxParament.APP_WX_PAY)
	public WXPay setAppWXPay() throws Exception {
		return new WXPay(new WxPayConfig(
				ymlParament.getCert_path(),
				ymlParament.getA_app_id(),
				ymlParament.getMch_id(),
				ymlParament.getkey()));
	}

WxPayConfig

public class WxPayConfig implements WXPayConfig {
	private byte[] certData;
	private String appID;
	private String mchID;
	private String key;

	public WxPayConfig(String certPath, String appID,String mchID,String key) throws Exception {
		File file = new File(certPath);
		InputStream certStream = new FileInputStream(file);
		this.certData = new byte[(int) file.length()];
		certStream.read(this.certData);
		certStream.close();
		this.appID = appID;
		this.mchID = mchID;
		this.key = key;
	}
}
  • 微信申请退款接口,关键代码(服务层,业务逻辑略)
  	@Resource(name = WxParament.H5_WX_PAY)
    private WXPay wxH5Pay;
    @Resource(name = WxParament.APP_WX_PAY)
    private WXPay wxAppPay;

	@Transactional
    @Override
    public void wxRefund(String refundNo, String sendOrderNo,String orderAmount,String refundAmount) throws Exception {
        checkWxRefund();
        //微信方式退款
        Map<String, String> data = new HashMap<String, String>();
        data.put("nonce_str", WXPayUtil.generateNonceStr());
        data.put("sign_type", "MD5");
        //商户订单号
        data.put("out_trade_no", sendOrderNo);
        //商户退款单号(多次退款需要提交原支付订单的商户订单号和设置不同的退款单号。)
        data.put("out_refund_no", refundNo);
        //订单金额
        data.put("total_fee", orderAmount);
        //退款金额
        data.put("refund_fee", refundAmount);
        data.put("notify_url", ymlParament.getWx_refund_return_url());
        data.put("refund_desc","我要退款");
        //这里的payInfo是保存着支付记录,从而找到这笔订单的支付方式
        Map<String, String> payRefundMap = WxPay.payRefund(payInfo.getPayWayInfo()== PayInfoEums.PAY_WAY_INFO_1.getValue()?wxH5Pay:wxAppPay, data);
        if ("FAIL".equals(payRefundMap.get("result_code")) ||
                "FAIL".equals(payRefundMap.get("return_code")) ) {
            //微信申请退款失败
        }
    }


private void checkWxRefund(){
        //1、交易时间超过一年的订单无法提交退款
        // 2、微信支付退款支持单笔交易分多次退款,多次退款需要提交原支付订单的商户订单号和设置不同的退款单号。
        // 申请退款总金额不能超过订单金额。
        // * 一笔退款失败后重新提交,请不要更换退款单号,请使用原商户退款单号
       
        //3、请求频率限制:150qps,即每秒钟正常的申请退款请求次数不超过150次
        // *     错误或无效请求频率限制:6qps,即每秒钟异常或错误的退款申请请求不超过6次

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

WxPay

/**
	 * 申请退款 参考链接:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4
	 * 注意退款结果通知,此接口为异步,只能判断是否调用成功,不能判断是否退款成功,退款成功需要参考回调:
	 * https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_16&index=10
	 */
	public static Map<String, String> payRefund(WXPay wp, Map<String, String> parament) throws Exception {
		return wp.refund(parament);
	}
退款结果通知
*当商户申请的退款有结果后,微信会把相关结果发送给商户,商户需要接收处理,并返回应答。
* 对后台通知交互时,如果微信收到商户的应答不是成功或超时,微信认为通知失败,
* 微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,
* 但微信不保证通知最终能成功(通知频率为15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 总计 24h4m)。
* 注意:同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。
* 推荐的做法是,当收到通知进行处理时,首先检查对应业务数据的状态,
* 判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。
* 在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
* 特别说明:退款结果对重要的数据进行了加密,商户需要用商户秘钥进行解密后才能获得结果通知的内容
	@Autowired
    private YmlParament ymlParament;

@ApiOperation("微信退款通知")
@PostMapping("callBackWxRefund")
public String callBackWxRefund(HttpServletRequest request) throws Exception {
    // 1、获取参数
    Map<String, String> params = WxUtils.getMapByRequest(request);
    log.info("微信退款回调回来啦!!!!参数为:" + params);
        //2、解密方式
        //解密步骤如下:
        //(1)对加密串A做base64解码,得到加密串B
        //(2)对商户key做md5,得到32位小写key* ( key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置 )
        //(3)用key*对加密串B做AES-256-ECB解密(PKCS7Padding)
        try {
            Map<String, String> reqInfo = WxUtils.decryptData(params.get("req_info"), ymlParament.getKey());
            // 4、把订单的相关详情查询出来
            //5、判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。
            checkCallbackWxRefund(params,reqInfo);
            //退款成功
  			//业务逻辑
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    return wxPaySuccess();
}

 private void checkCallbackWxRefund(Map<String,String> params,Map<String, String> reqInfo) throws Exception {
        if (!"SUCCESS".equals(params.get("refund_status"))){
            throw new Exception("微信退款失败");
        }
        if (StringUtils.isEmpty(reqInfo)){
            throw new Exception("微信退款参数解密失败");
        }
      	//校验业务逻辑

    }

  private static String wxPaySuccess() throws Exception {
        Map<String, String> succResult = new HashMap();
        succResult.put("return_code", "SUCCESS");
        succResult.put("return_msg", "OK");
        return WXPayUtil.mapToXml(succResult);
    }
工具类

WxUtils

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


/**
	 * 获取微信过来的请求参数
	 * @param request
	 * @return
	 * @throws Exception
	 */
	public static Map<String, String> getMapByRequest(HttpServletRequest request) throws Exception{
		InputStream inStream = request.getInputStream();
		ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
		byte[] buffer = new byte[1024];
		int len = 0;
		while ((len = inStream.read(buffer)) != -1) {
			outSteam.write(buffer, 0, len);
		}
		
		Map<String, String> ret= WXPayUtil.xmlToMap(new String(outSteam.toByteArray(), "utf-8"));
		outSteam.close();
		inStream.close();
		return ret;
	}

/**
	    * @param base64Data  需要进行base64解码的字符串
		* @param password  商户key
	    * @return java.util.Map<java.lang.String,java.lang.String>
	    * @author aotezi
	    * @date 2020/4/7 15:52
	*/
	public static Map<String, String> decryptData(String base64Data,String password) throws Exception {
		Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING);
		SecretKeySpec key = new SecretKeySpec(MD5(password).toLowerCase().getBytes(), ALGORITHM);
		cipher.init(Cipher.DECRYPT_MODE, key);
		byte[] decode = decode(base64Data);
		byte[] doFinal = cipher.doFinal(decode);
		Map<String, String> xmlToMap = WXPayUtil.xmlToMap(new String(doFinal, "utf-8"));
		return xmlToMap;
	}

/**
     * 生成 MD5
     *
     * @param data 待处理数据
     * @return MD5结果
     */
    private String MD5(String data) {
    	try {
    		 byte[] array = MessageDigest.getInstance("MD5").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();
		} catch (Exception e) {
			e.printStackTrace();
		}
    	return null;
    }


 /**
     * 解码
     * @param encodedText
     * @return
     */
    private static byte[] decode(String encodedText){
        final Base64.Decoder decoder = Base64.getDecoder();
        return decoder.decode(encodedText);
    }

在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值