微信小程序基于java实现v2支付,提现,退款

v2支付

v2微信官方文档

封装支付请求实体

import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;

import java.math.BigDecimal;

@Getter
@Setter
public class WeixinPayForm{

	@ApiModelProperty("支付信息")
	private String body;

	@ApiModelProperty("请求ip")
	private String ip;

	@ApiModelProperty("交易订单号")
	private String outTradeNo;

	@ApiModelProperty("总金额")
	private BigDecimal totalAmount;

	@ApiModelProperty("小程序openid")
	private String openId;

	@ApiModelProperty("支付回调地址")
	private String notifyUrl;
	
	//app,xcx,gzh
	@ApiModelProperty("xcx")
	private String wxType;

	@ApiModelProperty("支付信息")
	private String authCode;
	
	//门店编号
	@ApiModelProperty("设备信息")
	private String deviceInfo;

	@ApiModelProperty("商户id")
	private String subMchId;

}

controller接口暴露层

	@ApiOperation(value = "支付下单")
	@ResponseBody
	@RequestMapping(value={"/buy"}, method=RequestMethod.POST)
        
		String message = payService.payFoodOrder( orderNo, new PayBaseForm(payMethod, this.getApplicationType(), this.getRequestIp()));
		return CommonResult.success(message);
    }

payFoodOrder 支付接口实现类

 public String payFoodOrder(UmsMember member, String orderNo, PayBaseForm baseForm)  {
        //查询订单 可以根据实际业务来 目前写死是为了方便理解 实际业务可以换成对应实体参数
        WeixinPayForm wxform = new WeixinPayForm();
        wxform.setBody("订单支付");
        wxform.setIp(getRequestIp());
     	//商户订单号
        wxform.setOutTradeNo("0010001");
     	//总金额
        wxform.setTotalAmount(new BigDecimal(100));
        wxform.setWxType("xcx");
        wxform.setDeviceInfo("cccc");
        wxform.setOpenId("xccxxx*********");
        //支付回调地址
        wxform.setNotifyUrl(WechatUtil.getPayNotifyUrl() + WXPAY_NOTIFY_URL_FOOD_ORDER);
        wxform.setSubMchId("商户号id");
        return new WeixinPay().pay(wxform);
    }

获取请求ip

	
	public String getRequestIp(){
		HttpServletRequest request = this.getRequest();
		String ip = request.getHeader("x-forwarded-for");
		if(ip == null) {
			ip = request.getRemoteAddr();
		} else {
			String[] ips = ip.split(",");
			ip = ips[0];
		}
		
		return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
	}

wxform.setNotifyUrl(WechatUtil.getPayNotifyUrl() + WXPAY_NOTIFY_URL_FOOD_ORDER);

这个回调地址是你自己代码里面定义的回调接口,例如你定义的controller回调接口url是 feedback/wx/notifurl ,
即是 wxform.setNotifyUrl(“feedback/wx/notifurl”),微信终端会去调用这个回调方法的,可以通过日志确认。

WeixinPay().pay(wxform)方法

public String pay(WeixinPayForm form) throws Exception{
    	//微信服务商APPID,一般情况为认证的服务号appid
		String appId = "wxd******";
        //子商户号
		String mchId = "160*****";
    	//小程序appid
		String subAppid = "111111******";
		tradeType = "JSAPI";
		Map<String,String> paramMap = new LinkedHashMap<>();
		paramMap.put("appid", appId);
		paramMap.put("body", form.getBody());
		paramMap.put("device_info", StringUtils.isNotBlank(form.getDeviceInfo()) ? form.getDeviceInfo() : "WEB");
		paramMap.put("fee_type", "CNY");
		paramMap.put("limit_pay", "no_credit");
		paramMap.put("mch_id", mchId);
		paramMap.put("nonce_str", StringUtils.genenrateUniqueInd());
		paramMap.put("notify_url", form.getNotifyUrl());
//		if(!isApp){
//			paramMap.put("openid", form.getOpenId());
//		}
		paramMap.put("out_trade_no", form.getOutTradeNo());
		paramMap.put("sign_type", "MD5");
		paramMap.put("spbill_create_ip", form.getIp());
		paramMap.put("spbill_create_ip", form.getIp());
		paramMap.put("sub_appid", subAppid);
		paramMap.put("sub_mch_id", form.getSubMchId());
		if(!isApp){
			paramMap.put("sub_openid", form.getOpenId());
		}
		paramMap.put("total_fee", String.valueOf(form.getTotalAmount().multiply(new BigDecimal(100)).intValue()));
		paramMap.put("trade_type", tradeType);
		System.out.println("=======签名参数开始==========");
		System.out.println(paramMap.toString());
		System.out.println("=======签名参数结束==========");
		paramMap.put("sign", WechatUtil.MD5(paramMap, IspConstant.WX_ISP_MCH_KEY));
		//签名
		String xmlParams = getPayXmlString(paramMap, isApp);
		System.out.println("=======签名XML开始==========");
		System.out.println(xmlParams);
		System.out.println("=======签名XML结束==========");
		String message = HttpUtil.doPostByXml(WechatUtil.GEN_ORDER_URL, xmlParams);
	    WeixinResponse response = WechatUtil.xmlToBean(message, WeixinResponse.class); 
	    System.out.println("=======返回信息开始==========");
		System.out.println(message);
		 System.out.println("=======返回结束开始==========");
		
		if("SUCCESS".equals(response.getReturn_code())){
			if(StringUtils.isNotBlank(response.getResult_code()) && "SUCCESS".equals(response.getResult_code())){
				//返回公众号,小程序前端所需参数
				Map<String,String> result = new LinkedHashMap<>();
                result.put("appId", subAppid);
                result.put("nonceStr", StringUtils.genenrateUniqueInd());
                result.put("package", "prepay_id="+response.getPrepay_id());
                result.put("signType", "MD5");
                result.put("timeStamp", String.valueOf(System.currentTimeMillis()));
                result.put("paySign", WechatUtil.MD5(result, IspConstant.WX_ISP_MCH_KEY));
				return JsonUtils.object2JsonString(result);
			}else{
				throw new ServiceException("支付失败,失败原因:"+response.getErr_code_des());
			}
		}else{
			throw new ServiceException("支付失败,失败原因:"+response.getReturn_msg());
		}
	}

StringUtils.genenrateUniqueInd() 生成订单号

import java.io.UnsupportedEncodingException;
import java.util.Random;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.math.RandomUtils;

public class StringUtils extends org.apache.commons.lang.StringUtils {
	private static final Pattern URL = Pattern.compile(
		"^((https|http|ftp|rtsp|mms)?://)" 
	     + "+(([0-9a-z_!~*'().&=+$%-]+: )?[0-9a-z_!~*'().&=+$%-]+@)?" 
	     + "(([0-9]{1,3}\\.){3}[0-9]{1,3}" 
	     + "|" 
	     + "([0-9a-z_!~*'()-]+\\.)*" 
	     + "([0-9a-z][0-9a-z-]{0,61})?[0-9a-z]\\." 
	     + "[a-z]{2,6})" 
	     + "(:[0-9]{1,4})?" 
	     + "((/?)|" 
	     + "(/[0-9a-z_!~*'().;?:@&=+$,%#-]+)+/?)$", Pattern.CASE_INSENSITIVE
	);
	
	private static final Pattern PHONE = Pattern.compile("^1[3|4|5|6|7|8|9]([0-9])\\d{8}$");
	
	private static final Pattern MONEY = Pattern.compile("^[0-9]+$|^[0-9]+\\.[0-9]{1,6}$");
	
	private static final Pattern TELPHONE = Pattern.compile("^(0[0-9]{2,4}-?[0-9]{7,8})|(1[3|4|5|7|8][0-9]{9})$");
	
	private static final Pattern IP = Pattern.compile("^((\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5]|[*])\\.){3}(\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5]|[*])$");
	
	private static final Pattern EMAIL = Pattern.compile("^([a-zA-Z0-9]*[-_]?[a-zA-Z0-9]+)*@([a-zA-Z0-9]*[-_]?[a-zA-Z0-9]+)+[\\.][A-Za-z]{2,3}([\\.][A-Za-z]{2})?$");

	private static final Pattern SUZI = Pattern.compile("^[0-9]*$");
	
	 public static boolean isContainChinese(String str) {
	        Pattern p = Pattern.compile("[\u4e00-\u9fa5]");
	        Matcher m = p.matcher(str);
	        if (m.find()) {
	            return true;
	        }
	        return false;
	    }
	
	public static boolean isEqual(Object obj1, Object obj2) {
		if(obj1 == null && obj2 == null) return true;
		if(obj1 == null || obj2 == null) return false;
		
		return obj1.equals(obj2);
	}
	
	public static boolean isNotEqual(Object obj1, Object obj2) {
		return !isEqual(obj1, obj2);
	}
	
	/**
	 * 六位数字验证码
	 * @return
	 */
	public static String getSixCode() {
		String result = "";
		for (int i = 0; i < 6; i++) {
			result += new Random().nextInt(10);
		}
		return result;
	}
	
	public static String getFourCode() {
		String result = "";
		for (int i = 0; i < 4; i++) {
			result += new Random().nextInt(10);
		}
		return result;
	}
	
	/**
	 * 数字字母验证码
	 */
    public static String getStringRandom(int length) {  
        String val = "";  
        Random random = new Random();  
          
        //参数length,表示生成几位随机数  
        for(int i = 0; i < length; i++) {  
              
            String charOrNum = random.nextInt(2) % 2 == 0 ? "char" : "num";  
            //输出字母还是数字  
            if( "char".equalsIgnoreCase(charOrNum) ) {  
                //输出是大写字母还是小写字母  
                int temp = random.nextInt(2) % 2 == 0 ? 65 : 97;  
                val += (char)(random.nextInt(26) + temp);  
            } else if( "num".equalsIgnoreCase(charOrNum) ) {  
                val += String.valueOf(random.nextInt(10));  
            }  
        }  
        return val;  
    }
    
  
	
	/**
	 * 生成20位订单号
	 */
	public static String genenrateInd(){
		Random random = new Random();
		Integer x = random.nextInt(899999);
		Integer y = x + 100000;
		return DateUtil.getShortCurrentTimeStr() + y.toString();
	}
	
	public static String[] chars = new String[] { "a", "b", "c", "d", "e", "f",  
            "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s",  
            "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5",  
            "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I",  
            "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",  
            "W", "X", "Y", "Z" };  
  
  
	public static String generateShortUuid(int length) {  
	    StringBuffer shortBuffer = new StringBuffer();  
	    String uuid = UUID.randomUUID().toString().replace("-", "");  
	    for (int i = 0; i < length; i++) {  
	        String str = uuid.substring(i * 4, i * 4 + 4);  
	        int x = Integer.parseInt(str, 16);  
	        shortBuffer.append(chars[x % 0x3E]);  
	    }  
	    return shortBuffer.toString();  
	}
	
	/**
	 * 生成32位的唯一序列号
	 * @return
	 */
	public static String genenrateUniqueInd() {
		return MD5Util.MD5Encode(UUID.randomUUID().toString(), "utf-8");
	}
	
	public static String genOrderNo(Integer memberId) {
		return (System.currentTimeMillis() + (memberId==null?0L:memberId)) + StringUtils.leftPad("" + RandomUtils.nextInt(1000), 3, "0");
	}
	
	public static void main(String[] args) {
		System.out.println(genenrateInd());
	}
}

生成xml字符串 getPayXmlString();

private String getPayXmlString(Map<String,String> paramMap, boolean isApp){
		StringBuilder sb = new StringBuilder(); 
		sb.append("<xml>"); 
		sb.append("<appid><![CDATA["+MapUtil.getStr(paramMap, "appid")+"]]></appid>"); 
		sb.append("<body><![CDATA["+MapUtil.getStr(paramMap, "body")+"]]></body>"); 
		sb.append("<device_info><![CDATA["+MapUtil.getStr(paramMap, "device_info")+"]]></device_info>"); 
		sb.append("<fee_type><![CDATA["+MapUtil.getStr(paramMap, "fee_type")+"]]></fee_type>"); 
		sb.append("<limit_pay><![CDATA["+MapUtil.getStr(paramMap, "limit_pay")+"]]></limit_pay>");
		sb.append("<mch_id><![CDATA["+MapUtil.getStr(paramMap, "mch_id")+"]]></mch_id>"); 
		sb.append("<nonce_str><![CDATA["+MapUtil.getStr(paramMap, "nonce_str")+"]]></nonce_str>"); 
		sb.append("<notify_url><![CDATA["+MapUtil.getStr(paramMap, "notify_url")+"]]></notify_url>"); 
		/*if(!isApp){
			sb.append("<openid><![CDATA["+MapUtil.getStr(paramMap, "openid")+"]]></openid>");
		}*/
		sb.append("<out_trade_no><![CDATA["+MapUtil.getStr(paramMap, "out_trade_no")+"]]></out_trade_no>"); 
		sb.append("<sign_type><![CDATA["+MapUtil.getStr(paramMap, "sign_type")+"]]></sign_type>"); 
		sb.append("<spbill_create_ip><![CDATA["+MapUtil.getStr(paramMap, "spbill_create_ip")+"]]></spbill_create_ip>"); 
		sb.append("<sub_appid><![CDATA["+MapUtil.getStr(paramMap, "sub_appid")+"]]></sub_appid>"); 
		sb.append("<sub_mch_id><![CDATA["+MapUtil.getStr(paramMap, "sub_mch_id")+"]]></sub_mch_id>"); 
		if(!isApp){
			sb.append("<sub_openid><![CDATA["+MapUtil.getStr(paramMap, "sub_openid")+"]]></sub_openid>");
		}
		sb.append("<total_fee><![CDATA["+MapUtil.getStr(paramMap, "total_fee")+"]]></total_fee>"); 
		sb.append("<trade_type><![CDATA["+MapUtil.getStr(paramMap, "trade_type")+"]]></trade_type>"); 
		sb.append("<sign><![CDATA["+MapUtil.getStr(paramMap, "sign")+"]]></sign>"); 
		sb.append("</xml>"); 
		return sb.toString();
	}

生成秘钥签名

WechatUtil.MD5(paramMap, IspConstant.WX_ISP_MCH_KEY)

import java.io.Writer;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSONObject;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.core.util.QuickWriter;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.xml.DomDriver;
import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
import com.thoughtworks.xstream.io.xml.XppDriver;

import cn.hutool.http.HttpUtil;

@Component
public class WechatUtil {
	
	private static final String key1 = "key1";
	private static final String key2 = "key2";
	public static final String JS_CODE_URL="https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code";
	public static final String AUTH2_URL="https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
	public static final String USERINFO_URL="https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID";
	public static final String GEN_ORDER_URL="https://api.mch.weixin.qq.com/pay/unifiedorder";
	public static final String WECHAT_TRANSFERS_URL="https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers";
	public static final String REFUND_ORDER_URL="https://api.mch.weixin.qq.com/secapi/pay/refund";
	public static final String ACCESS_TOKEN_URL="https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=SECRET";
	public static final String WXACODE="https://api.weixin.qq.com/wxa/getwxacode?access_token=ACCESS_TOKEN";
	public static final String TEMPLATE_URL="https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=ACCESS_TOKEN";
	public static final String DOWNLOADBILL_URL="https://api.mch.weixin.qq.com/pay/downloadbill";

	@Autowired 
	private WeixinConfigService weixinService;
	@Autowired
	private RedisUtil redisUtil;
	
	private static SysWeixinConfig config;
	
	@PostConstruct
    public void init() throws ServiceException {
		config = weixinService.getAllWeixinConfigs().get(0);
	}
	
	public static String getPayNotifyUrl() {
		return config.getPayNotifyUrl();
	}
	
	public static String getKfAppId() {
		return config.getKfAppId();
	}
	
	public static String getKfSecret() {
		return config.getKfSecret();
	}
	
	public static String getMchId() {
		return config.getMchId();
	}
	
	public static String getMchKey() {
		return config.getMchKey();
	}
	
	public static String getGzhAppId(){
		return config.getGzhAppId();
	}
	
	public static String getGzhSecret(){
		return config.getGzhSecret();
	}
	
	public static String getXcxAppId(){
		return config.getXcxAppId();
	}
	
	public static String getXcxSecret(){
		return config.getXcxSecret();
	}
	
	public static String getFiveXcxAppId(){
		return config.getFiveXcxAppId();
	}
	
	public static String getFiveXcxSecret(){
		return config.getFiveXcxSecret();
	}
	
	public static String getCertPath(){
		return config.getCertPath();
	}
	
	public static String MD5(Map<String,String> paramMap){
		return MD5(paramMap, WechatUtil.getMchKey());
	}

	/**
	 * 返回加密签名
	 * @param paramMap xml字符串
	 * @param mchKey  微信服务商商户号
	 * @return
	 */
	public static String MD5(Map<String,String> paramMap, String mchKey){
		StringBuffer signA = new StringBuffer();
		for(String key : paramMap.keySet()){
			signA.append(key+"="+paramMap.get(key)+"&");
		}
		String signTemp = signA.toString()+"key="+mchKey;
		return MD5Util.MD5Encode(signTemp,"utf-8").toUpperCase();
	}
	
	/**
     * bean转成微信的xml消息格式
     * @param object
     * @return
     */
    public static String beanToXml( Object object) {
        XStream xStream = getMyXStream();
        xStream.alias("xml", object.getClass());
        xStream.processAnnotations(object.getClass());
        String xml = xStream.toXML(object);
        if (!StringUtils.isEmpty(xml)){
            return xml;
        }else{
            return null;
        }
    }

	/**
     * xml转成bean泛型方法
     * @param resultXml
     * @param clazz
     * @param <T>
     * @return
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
	public static <T> T xmlToBean(String resultXml, Class clazz) {
        // XStream对象设置默认安全防护,同时设置允许的类
        XStream stream = new XStream(new DomDriver());
//        XStream.setupDefaultSecurity(stream);
//        stream.allowTypes(new Class[]{clazz});
        stream.processAnnotations(new Class[]{clazz});
//        stream.setMode(XStream.NO_REFERENCES);
        stream.alias("xml", clazz);
        return (T) stream.fromXML(resultXml);
    }

	public static String getIp2(HttpServletRequest request) {
		String ip = request.getHeader("X-Forwarded-For");
		if (!StringUtils.isEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
			// 多次反向代理后会有多个ip值,第一个ip才是真实ip
			int index = ip.indexOf(",");
			if (index != -1) {
				return ip.substring(0, index);
			} else {
				return ip;
			}
		}
		ip = request.getHeader("X-Real-IP");
		if (!StringUtils.isEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
			return ip;
		}
		return request.getRemoteAddr();
	}

  //xstream扩展,bean转xml自动加上![CDATA[]]
    public static XStream getMyXStream() {
        return new XStream(new XppDriver() {
            @Override
            public HierarchicalStreamWriter createWriter(Writer out) {
                return new PrettyPrintWriter(out) {
                    // 对所有xml节点都增加CDATA标记
                    boolean cdata = true;
 
                    @SuppressWarnings("rawtypes")
					@Override
                    public void startNode(String name, Class clazz) {
                        super.startNode(name, clazz);
                    }
 
                    @Override
                    protected void writeText(QuickWriter writer, String text) {
                        if (cdata) {
                            writer.write("<![CDATA[");
                            writer.write(text);
                            writer.write("]]>");
                        } else {
                            writer.write(text);
                        }
                    }
                };
            }
        });
    }
    
    
    public void sendWxMessage(JSONObject paramsMap, Integer type) throws ServiceException {
		String acctenToken = getAccessToken(type);
		String message;
		try {
			message = HttpUtil.post(TEMPLATE_URL.replace("ACCESS_TOKEN", acctenToken), paramsMap.toString());
			System.out.println(message);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
    
    public String getAccessToken(Integer type) throws ServiceException {
    	String key = ACCESS_TOKEN_KEY_7;
    	if(type == 5) key = ACCESS_TOKEN_KEY_5;
		String access_token = (String) redisUtil.getRedisValue(key);
		if(StringUtils.isEmpty(access_token)){
			JSONObject jsonObject = HttpUtil.doGet(
					ACCESS_TOKEN_URL
					.replace("APPID", type == 5 ? getFiveXcxAppId() : getXcxAppId())
					.replace("SECRET", type == 5 ? getFiveXcxSecret() : getXcxSecret()));
			access_token = jsonObject.getString("access_token");
			if(!StringUtils.isEmpty(access_token)){
				redisUtil.delete(key);
				redisUtil.setRedisValue(key, access_token, jsonObject.getLong("expires_in"), TimeUnit.SECONDS);
			}
		}
		return access_token;
	}
    
}

WeixinResponse转换实体


import com.thoughtworks.xstream.annotations.XStreamAlias;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class WeixinResponse {
	
	@XStreamAlias("appid")
	private String appid;
	
	@XStreamAlias("sub_appid")
	private String sub_appid;
	
	@XStreamAlias("sub_mch_id")
	private String sub_mch_id;
	
	@XStreamAlias("sub_is_subscribe")
	private String sub_is_subscribe;
	
	@XStreamAlias("device_info")
	private String device_info;
	
	@XStreamAlias("nonce_str")
	private String nonce_str;
	
	@XStreamAlias("sign")
	private String sign;
	
	@XStreamAlias("sign_type")
	private String sign_type;
	
	@XStreamAlias("settlement_total_fee")
	private String settlement_total_fee;
	
	@XStreamAlias("coupon_fee")
	private String coupon_fee;

	@XStreamAlias("coupon_fee_1")
	private String coupon_fee_1;

	@XStreamAlias("coupon_count")
	private String coupon_count;
	
	@XStreamAlias("coupon_type_0")
	private String coupon_type_0;
	
	@XStreamAlias("coupon_id_0")
	private String coupon_id_0;
	
	@XStreamAlias("coupon_fee_0")
	private String coupon_fee_0;
	
	@XStreamAlias("attach")
	private String attach;
	
	@XStreamAlias("code_url")
	private String code_url;
	
	@XStreamAlias("return_code")
	private String return_code;
	
	@XStreamAlias("return_msg")
	private String return_msg;
	
	@XStreamAlias("result_code")
	private String result_code;
	
	@XStreamAlias("err_code")
	private String err_code;
	
	@XStreamAlias("err_code_des")
	private String err_code_des;
	
	@XStreamAlias("mch_billno")
	private String mch_billno;
	
	@XStreamAlias("mch_id")
	private String mch_id;
	
	@XStreamAlias("wxappid")
	private String wxappid;
	
	@XStreamAlias("re_openid")
	private String re_openid;
	
	@XStreamAlias("total_amount")
	private String total_amount;
	
	@XStreamAlias("send_listid")
	private String send_listid;
	
	@XStreamAlias("prepay_id")
	private String prepay_id;
	
	@XStreamAlias("trade_type")
	private String trade_type;
	
	@XStreamAlias("openid")
	private String openid;
	
	@XStreamAlias("sub_openid")
	private String sub_openid;
	
	@XStreamAlias("is_subscribe")
	private String is_subscribe;
	
	@XStreamAlias("bank_type")
	private String bank_type;
	
	@XStreamAlias("total_fee")
	private String total_fee;
	
	@XStreamAlias("cash_fee")
	private String cash_fee;
	
	@XStreamAlias("transaction_id")
	private String transaction_id;
	
	@XStreamAlias("out_trade_no")
	private String out_trade_no;
	
	@XStreamAlias("time_end")
	private String time_end;
	
	@XStreamAlias("fee_type")
	private String fee_type;
	
	@XStreamAlias("cash_fee_type")
	private String cash_fee_type;
}

JsonUtils.object2JsonString()

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;


public class JsonUtils {
	public static final Gson gson = new Gson();
	
	public static final GsonBuilder gsonBuilder = new GsonBuilder();

	public static <T> T json2Object(Object data, Class<T> clazz) {
		return gson.fromJson(gson.toJson(data), clazz);
	}

	public static String object2JsonString(Object obj) {
		if(obj == null) {
			return null;
		}
		return gson.toJson(obj).toString();
	}

	public static <T> T fromJson(String data, Class<T> clazz) {
		return gson.fromJson(data, clazz);
	}
	
	/**
	 * 转化成Json,不转化hmtl字符及不忽略空值
	 * @param obj
	 * @return
	 */
	public static String object2JsonNoEscaping(Object obj){
		gsonBuilder.disableHtmlEscaping();
		gsonBuilder.serializeNulls();
		return gsonBuilder.create().toJson(obj);
	}
 }

v2提现

controller接口暴露层

    @ApiOperation(value = "提现")
    @OperationLog
    @ApiImplicitParams({
            @ApiImplicitParam(paramType="query",name = "id", value = "提现订单id",dataType="Long",required=true)
    })
    @ResponseBody
    @RequestMapping(value="/member/ith_draw", method=RequestMethod.GET)
    public CommonResult<Boolean> findWithdrawLog(HttpServletRequest request, @RequestParam("id") Long id) throws ServiceException {
        return CommonResult.success(this.iUmsMemberRechargeConfigService.doExtract(request,id));
    }

接口实现逻辑

import lombok.Getter;
import lombok.Setter;

import java.math.BigDecimal;

@Getter
@Setter
public class WeixinEntPayForm {

	/**
	 * 提现单号
	 */
	private String partnerTradeNo;
	/**
	 * 用户openId
	 */
	private String openId;

	/**
	 * 退款金额
	 */
	private BigDecimal totalAmount;

	/**
	 * 用户名
	 */
	private String userName;

	//app,xcx,gzh
	private String wxType;

}

提现

  /**
     * 提现
     *
     * @param request
     * @param id      操作id
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean doExtract(HttpServletRequest request, Long id) throws ServiceException {
        WeixinEntPayForm form = new WeixinEntPayForm();
        form.setOpenId("scx");
        form.setTotalAmount(new BigDecimal(111));
        form.setUserName("ssss");
        form.setPartnerTradeNo("11281932218");
        Map<String, String> map = new WeixinPay().entPay(request, form);
        if (ObjectUtils.isEmpty(map)) {
            return true;
        }
        Date now = new Date();
        if ("200".equals(map.get("code"))) {
            //修改提现订单
            update();
            //生成提现记录  看自己的个人实际业务
            insert();
            //修改用户余额 积分  冻结金额
            update();
        } else {
            throw new BusinessException(map.get("message"));
        }
        return false;
    }

WeixinPay.entPay()

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileWriter;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.NumberUtil;
import com.github.wxpay.sdk.WXPay;
import com.github.wxpay.sdk.WXPayUtil;
import lombok.extern.log4j.Log4j2;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.util.*;

@Log4j2
public class WeixinPay {
    public Map<String, String> entPay(HttpServletRequest request, WeixinEntPayForm form) {
		String appId = "appid";
		String mchId = "商户号";
		boolean isApp = false;
		Map<String, String> paramMap = new LinkedHashMap<>();
		// 商户账号appid
		paramMap.put("mch_appid", appId);
		// 商户
		paramMap.put("mchid", mchId);
		// 随机字符串
		paramMap.put("nonce_str", WXPayUtil.generateNonceStr());
		// 交易订单号
		paramMap.put("partner_trade_no", form.getPartnerTradeNo());
		// 用户openid
		paramMap.put("openid", form.getOpenId());
		// 校验用户姓名选项
		paramMap.put("check_name", "NO_CHECK");
		// 金额 分
		paramMap.put("amount", String.valueOf(form.getTotalAmount().multiply(new BigDecimal(100)).intValue()));
		//备注
		paramMap.put("desc", "提现");
		paramMap.put("spbill_create_ip", WechatUtil.getIp2(request));
		paramMap.put("sign", WechatUtil.MD5(paramMap, IspConstant.WX_ISP_MCH_KEY));
		log.info("=======签名参数开始weixin-entpay==========");
		log.info(paramMap.toString());
		String requestXml = getRequestXml(paramMap);
		log.info("=======签名参数结束weixin-entpay==========");
		String xmlResult = CertHttpUtil.postData(WechatUtil.WECHAT_TRANSFERS_URL, requestXml, IspConstant.WX_ISP_MCH_ID, IspConstant.WX_CERT_PATH);
		log.info("=======返回信息开始weixin-entpay==========");
		log.info(xmlResult);
		log.info("=======返回信息结束weixin-entpay==========");
		Map<String, String> mapResult = null;
		try {
			mapResult = WXPayUtil.xmlToMap(xmlResult);
		} catch (Exception e) {
			log.info("xml转换错误======================" + e.getMessage());
		}
		Map<String, String> result = new HashMap<>();
		// 转账成功
		if ("SUCCESS".equalsIgnoreCase(mapResult.get("result_code"))) {
			//todo 记录提现日志 生成消费单
			result.put("code", "200");
			result.put("message", "支付成功");
		}
		// 转帐失败
		else if ("FAIL".equalsIgnoreCase(mapResult.get("result_code"))) {
			result.put("code", "500");
			// 系统错误需要重试[请先调用查询接口,查看此次付款结果,如结果为不明确状态(如订单号不存在),请务必使用原商户订单号进行重试。
			if ("SYSTEMERROR".equalsIgnoreCase(mapResult.get("err_code"))) {
//                result.put("message", "支付成功");]
			}
			// 金额超限
			else if ("AMOUNT_LIMIT".equalsIgnoreCase(mapResult.get("err_code"))) {

				result.put("message", "金额超限");
			}
			// 余额不足
			else if ("NOTENOUGH".equalsIgnoreCase(mapResult.get("err_code"))) {
//                result.put("message", "支付成功");
			}
			// 超过频率限制,请稍后再试。
			else if ("FREQ_LIMIT".equalsIgnoreCase(mapResult.get("err_code"))) {
				result.put("message", "超过频率限制,请稍后再试。");
			}
			// 已经达到今日付款总额上限/已达到付款给此用户额度上限
			else if ("MONEY_LIMIT".equalsIgnoreCase(mapResult.get("err_code"))) {
				result.put("message", "已经达到今日付款总额上限");
			}
			// 无法给非实名用户付款
			else if ("V2_ACCOUNT_SIMPLE_BAN".equalsIgnoreCase(mapResult.get("err_code"))) {
				result.put("message", "无法给非实名用户付款");
			}
			// 该用户今日付款次数超过限制,如有需要请登录微信支付商户平台更改API安全配置
			else if ("SENDNUM_LIMIT".equalsIgnoreCase(mapResult.get("err_code"))) {
				result.put("message", "今日付款次数超过限制");
			}
		} else {
			// 系统错误
			log.info("------------------具体报错信息------------------" + mapResult.get("err_code_des"));
			result.put("code", "500");
			result.put("message", "系统错误");
		}
		return result;
	}
}

WechatUtil.getIp2(request)

	public static String getIp2(HttpServletRequest request) {
		String ip = request.getHeader("X-Forwarded-For");
		if (!StringUtils.isEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
			// 多次反向代理后会有多个ip值,第一个ip才是真实ip
			int index = ip.indexOf(",");
			if (index != -1) {
				return ip.substring(0, index);
			} else {
				return ip;
			}
		}
		ip = request.getHeader("X-Real-IP");
		if (!StringUtils.isEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
			return ip;
		}
		return request.getRemoteAddr();
	}

WechatUtil.generateNonceStr()

   public static String generateNonceStr() {
        return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
    }

CertHttpUtil.postData

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyStore;

import javax.net.ssl.SSLContext;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;

public class CertHttpUtil {
	 
    private static int socketTimeout = 10000;// 连接超时时间,默认10秒
    private static int connectTimeout = 30000;// 传输超时时间,默认30秒
    private static RequestConfig requestConfig;// 请求器的配置
    private static CloseableHttpClient httpClient;// HTTP请求器
 
    /**
     * 通过Https往API post xml数据
     *
     * @param url API地址
     * @param xmlObj 要提交的XML数据对象
    * @param mchId 商户ID
    * @param certPath 证书位置
     * @return
     */
    public static String postData(String url, String xmlObj, String mchId, String certPath) {
        // 加载证书
        try {
            initCert(mchId, certPath);
        } catch (Exception e) {
            e.printStackTrace();
        }
        String result = null;
        HttpPost httpPost = new HttpPost(url);
        // 得指明使用UTF-8编码,否则到API服务器XML的中文不能被成功识别
        StringEntity postEntity = new StringEntity(xmlObj, "utf-8");
        httpPost.addHeader("Content-Type", "text/xml");
        httpPost.setEntity(postEntity);
        // 根据默认超时限制初始化requestConfig
        requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout).build();
        // 设置请求器的配置
        httpPost.setConfig(requestConfig);
        try {
            HttpResponse response = null;
            try {
                response = httpClient.execute(httpPost);
            } catch (IOException e) {
                e.printStackTrace();
            }
            HttpEntity entity = response.getEntity();
            try {
                result = EntityUtils.toString(entity, "UTF-8");
            } catch (IOException e) {
                e.printStackTrace();
            }
        } finally {
            httpPost.abort();
        }
        return result;
    }
 
    /**
     * 加载证书
     *
     * @param mchId 商户ID
     * @param certPath 证书位置
     * @throws Exception
     */
    @SuppressWarnings("deprecation")
	private static void initCert(String mchId, String certPath) throws Exception {
        // 证书密码,默认为商户ID
        String key = mchId;
        // 证书的路径
        String path = certPath;
        // 指定读取证书格式为PKCS12
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        // 读取本机存放的PKCS12证书文件
        FileInputStream instream = new FileInputStream(new File(path));
        try {
            // 指定PKCS12的密码(商户ID)
            keyStore.load(instream, key.toCharArray());
        } finally {
            instream.close();
        }
        SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, key.toCharArray()).build();
        SSLConnectionSocketFactory sslsf =
                new SSLConnectionSocketFactory(sslcontext, new String[] {"TLSv1"}, null,
                        SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
        httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
    }
}

生成xml字符串

	@SuppressWarnings("rawtypes")
	public static String getRequestXml(Map<String, String> parameters) {
		StringBuffer sb = new StringBuffer();
		sb.append("<xml>");
		Set es = parameters.entrySet();
		Iterator it = es.iterator();
		while (it.hasNext()) {
			Map.Entry entry = (Map.Entry) it.next();
			String k = (String) entry.getKey();
			String v = (String) entry.getValue();
			if ("attach".equalsIgnoreCase(k) || "body".equalsIgnoreCase(k) || "sign".equalsIgnoreCase(k)) {
				sb.append("<" + k + ">" + "<![CDATA[" + v + "]]></" + k + ">");
			} else {
				sb.append("<" + k + ">" + v + "</" + k + ">");
			}
		}
		sb.append("</xml>");
		return sb.toString();
	}

v2退款

![](https://img-blog.csdnimg.cn/img_convert/f8c5c0e718095cfa50079c781e3b9101.png#clientId=u89e85d45-da94-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u363733d3&margin=[object Object]&originHeight=486&originWidth=437&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=u56a22731-966e-4215-bec6-b7e14ecd38c&title=)
cancelAndRefundOrder 退款接口

    @Override
	public void cancelAndRefundOrder() {
        BigDecimal refundAmount = new BigDecimal(111);
        refundAmount = refundAmount == null ? BigDecimal.ZERO : refundAmount;
      
        //获取支付订单
        WeixinRefundForm wxform = new WeixinRefundForm();
        wxform.setWxType("xcx");
        wxform.setOutRefundNo("110001111");
        wxform.setOutTradeNo("110001111");
        wxform.setRefundFee("退款金额");
        wxform.setTotalAmount("订单金额");
        wxform.setNotifyUrl("退款回调地址");
        String subMchId = "商户号id";
        wxform.setSubMchId(subMchId);
        new WeixinPay().refund(wxform);
      
    }

refund 退款封装

public void refund(WeixinRefundForm form) throws ServiceException{
		String appId = "微信服务商APPID";
		String mchId = "微信服务商商户号";
		String subAppid = "小程序appid";
		boolean isApp = false;
		Map<String,String> paramMap = new LinkedHashMap<>();
		paramMap.put("appid", appId);
		paramMap.put("mch_id", mchId);
		paramMap.put("nonce_str", StringUtils.genenrateUniqueInd());
		paramMap.put("notify_url", form.getNotifyUrl());
		paramMap.put("out_refund_no", form.getOutRefundNo());
		paramMap.put("out_trade_no", form.getOutTradeNo());
		paramMap.put("refund_fee", String.valueOf(form.getRefundFee().multiply(new BigDecimal(100)).intValue()));
		paramMap.put("sign_type", "MD5");
		paramMap.put("sub_appid", subAppid);
		paramMap.put("sub_mch_id", form.getSubMchId());
		paramMap.put("total_fee", String.valueOf(form.getTotalAmount().multiply(new BigDecimal(100)).intValue()));
		System.out.println("=======签名参数开始==========");
		System.out.println(paramMap.toString());
		System.out.println("=======签名参数结束==========");
		paramMap.put("sign", WechatUtil.MD5(paramMap, IspConstant.WX_ISP_MCH_KEY));
		//签名
		String xmlParams = getRefundXmlString(paramMap, isApp);
		System.out.println("=======签名XML开始==========");
		System.out.println(xmlParams);
		System.out.println("=======签名XML结束==========");
		String message = CertHttpUtil.postData(WechatUtil.REFUND_ORDER_URL, xmlParams, IspConstant.WX_ISP_MCH_ID, IspConstant.WX_CERT_PATH);
	    WeixinRefundResponse response = WechatUtil.xmlToBean(message, WeixinRefundResponse.class); 
	    System.out.println("=======返回信息开始==========");
		System.out.println(message);
		 System.out.println("=======返回结束开始==========");
		
		if("SUCCESS".equals(response.getReturn_code())){
			if(StringUtils.isNotBlank(response.getResult_code()) && "SUCCESS".equals(response.getResult_code())){
				//退款申请成功,结果通过退款查询接口查询 
			}else{
				throw new ServiceException("退款申请失败,失败原因:"+response.getErr_code_des());
			}
		}else{
			throw new ServiceException("退款申请失败,失败原因:"+response.getReturn_msg());
		}
	}

生成退款xml字符串

private String getRefundXmlString(Map<String,String> paramMap, boolean isApp){
		StringBuilder sb = new StringBuilder(); 
		sb.append("<xml>"); 
		sb.append("<appid><![CDATA["+MapUtil.getStr(paramMap, "appid")+"]]></appid>"); 
		sb.append("<mch_id><![CDATA["+MapUtil.getStr(paramMap, "mch_id")+"]]></mch_id>"); 
		sb.append("<nonce_str><![CDATA["+MapUtil.getStr(paramMap, "nonce_str")+"]]></nonce_str>"); 
		sb.append("<notify_url><![CDATA["+MapUtil.getStr(paramMap, "notify_url")+"]]></notify_url>"); 
		sb.append("<out_refund_no><![CDATA["+MapUtil.getStr(paramMap, "out_refund_no")+"]]></out_refund_no>"); 
		sb.append("<out_trade_no><![CDATA["+MapUtil.getStr(paramMap, "out_trade_no")+"]]></out_trade_no>"); 
		sb.append("<refund_fee><![CDATA["+MapUtil.getStr(paramMap, "refund_fee")+"]]></refund_fee>"); 
		sb.append("<sign_type><![CDATA["+MapUtil.getStr(paramMap, "sign_type")+"]]></sign_type>"); 
		sb.append("<sub_appid><![CDATA["+MapUtil.getStr(paramMap, "sub_appid")+"]]></sub_appid>"); 
		sb.append("<sub_mch_id><![CDATA["+MapUtil.getStr(paramMap, "sub_mch_id")+"]]></sub_mch_id>"); 
		sb.append("<total_fee><![CDATA["+MapUtil.getStr(paramMap, "total_fee")+"]]></total_fee>"); 
		sb.append("<sign><![CDATA["+MapUtil.getStr(paramMap, "sign")+"]]></sign>"); 
		sb.append("</xml>"); 
		return sb.toString();
	}

支付回调

微信支付成功回调----> 这边用到了设计模式,工厂模式

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;


import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;

@Api(tags = "订单支付回调", description = "FoodOrderPayCallbackController")
@Controller
public class FoodOrderPayCallbackController extends PayCallbackController {
	

	@ApiOperation(value = "微信支付回调")
    @ResponseBody
    @RequestMapping(value="/pay/callbak", method=RequestMethod.POST)
    public String paySusForWx(HttpServletRequest request) throws Exception{
		return this.paySusForWx(request, CallbackManager.CALLBACK_FOOD_ORDER);
    }
	
}

PayCallbackController 支付回调controller

import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;



@Log4j2
@Controller
public class PayCallbackController {
	
	@Autowired
	private CallbackManager paySusManager;
	
    public String paySusForWx(HttpServletRequest request, String type) throws Exception{
		InputStream inStream = request.getInputStream(); 
		int _buffer_size = 1024; 
		if (inStream != null) { 
			ByteArrayOutputStream outStream = new ByteArrayOutputStream(); 
			byte[] tempBytes = new byte[_buffer_size]; 
			int count = -1; 
			while ((count = inStream.read(tempBytes, 0, _buffer_size)) != -1) {
				outStream.write(tempBytes, 0, count); 
			} 
			tempBytes = null; outStream.flush(); 
			//将流转换成字符串 
			String result = new String(outStream.toByteArray(), "UTF-8"); 
			log.info("微信支付回调:"+result);
			WeixinResponse response = WechatUtil.xmlToBean(result, WeixinResponse.class);
			
			//通知成功
			if(StringUtils.isNotBlank(response.getReturn_code()) && "SUCCESS".equals(response.getReturn_code())){
				if(StringUtils.isNotBlank(response.getResult_code()) && "SUCCESS".equals(response.getResult_code())){
					//支付成功
					paySusManager.getPaySusFactory(type).paySus(response.getOut_trade_no());
				}
			}
			
		}
		
		return "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
    }
	
}

CallbackManager 定义回调工厂管理类

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class CallbackManager {


    public static final String CALLBACK_RECHARGE = "recharge";
    public static final String CALLBACK_FOOD_ORDER = "foodOrder";
    /**
     * 退款
     */
    public static final String CALLBACK_REFUND_HANDLE = "refundHandle";
    /**
     * 申请退款
     */
    public static final String CALLBACK_REFUND_APPLY = "refundApply";

    @Autowired
    private FoodOrderLogic foodOrderLogic;
    @Autowired
    private RefundLogic refundLogic;
    @Autowired
    private RechargeLogic rechargeLogic;

    public CallbackFactory getPaySusFactory(String mode) {
        switch (mode) {
            case CALLBACK_REFUND_APPLY:
                return refundLogic;
            case CALLBACK_FOOD_ORDER:
                return foodOrderLogic;
            case CALLBACK_RECHARGE:
                return rechargeLogic;
            default:
                return null;
        }
    }

}

CallbackFactory回调工厂

import org.springframework.stereotype.Component;


import java.util.concurrent.ExecutionException;

@Component
public abstract class CallbackFactory{
	
	public abstract void paySus(String orderNo) ;
}

FoodOrderLogic 回调处理逻辑方法

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import com.dtp.core.DtpExecutor;
import com.dtp.core.DtpRegistry;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;

@Slf4j
@Service("paySusFoodOrder")
public class FoodOrderLogic extends CallbackFactory {

  

    @Override
    public void paySus(String orderNo) throws ServiceException, ExecutionException, InterruptedException {

        //处理回调逻辑       
    }


    public void refundSus(String refundNo, String type) throws ServiceException {
        switch (type) {
            case CallbackManager.CALLBACK_REFUND_APPLY:
                refundService.refundSusCallback(refundNo, null);
                break;

            case CallbackManager.CALLBACK_REFUND_HANDLE:
                OmsFoodOrder order = foodOrderService.getOrderByOrderNo(refundNo);
                foodOrderService.cancelAndRefundOrderSus(order);
                break;
            default:
                break;
        }
    }
}

微信退款回调

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;


import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;

@Api(tags = "申请售后退款回调", description = "RefundApplyCallbackController")
@Controller
public class RefundApplyCallbackController extends RefundCallbackController {

	
	@ApiOperation(value = "微信退款回调")
	@RequestMapping(value="/weixin/pay/refund/callback", method=RequestMethod.POST)
    @ResponseBody
    public String paySusJoinPinkaForWx(HttpServletRequest request) throws Exception{
		return this.refundSusForWx(request, CallbackManager.CALLBACK_REFUND_APPLY);
    }
	
}

WeixinRefundResponse 微信退款实体

import com.thoughtworks.xstream.annotations.XStreamAlias;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class WeixinRefundResponse {
	
	@XStreamAlias("appid")
	private String appid;
	
	@XStreamAlias("sub_appid")
	private String sub_appid;
	
	@XStreamAlias("nonce_str")
	private String nonce_str;
	
	@XStreamAlias("sign")
	private String sign;
	
	@XStreamAlias("out_refund_no")
	private String out_refund_no;
	
	@XStreamAlias("refund_id")
	private String refund_id;
	
	@XStreamAlias("refund_fee")
	private String refund_fee;
	
	@XStreamAlias("settlement_refund_fee")
	private String settlement_refund_fee;
	
	@XStreamAlias("settlement_total_fee")
	private String settlement_total_fee;
	
	@XStreamAlias("return_code")
	private String return_code;
	
	@XStreamAlias("return_msg")
	private String return_msg;
	
	@XStreamAlias("result_code")
	private String result_code;
	
	@XStreamAlias("refund_account")
	private String refund_account;
	
	@XStreamAlias("err_code")
	private String err_code;
	
	@XStreamAlias("err_code_des")
	private String err_code_des;
	
	@XStreamAlias("cash_refund_fee")
	private String cash_refund_fee;
	
	@XStreamAlias("mch_id")
	private String mch_id;
	
	@XStreamAlias("sub_mch_id")
	private String sub_mch_id;
	
	@XStreamAlias("coupon_type_$n")
	private String coupon_type_$n;
	
	@XStreamAlias("coupon_refund_fee")
	private String coupon_refund_fee;
	
	@XStreamAlias("coupon_refund_fee_$n")
	private String coupon_refund_fee_$n;
	
	@XStreamAlias("coupon_refund_count")
	private String coupon_refund_count;
	
	@XStreamAlias("coupon_refund_id_$n")
	private String coupon_refund_id_$n;
	
	@XStreamAlias("total_fee")
	private String total_fee;
	
	@XStreamAlias("cash_fee")
	private String cash_fee;
	
	@XStreamAlias("transaction_id")
	private String transaction_id;
	
	@XStreamAlias("out_trade_no")
	private String out_trade_no;
	
	@XStreamAlias("fee_type")
	private String fee_type;
	
	@XStreamAlias("cash_fee_type")
	private String cash_fee_type;
	
	@XStreamAlias("req_info")
	private String req_info;
	
	@XStreamAlias("refund_channel")
	private String refund_channel;
	
	@XStreamAlias("refund_recv_accout")
	private String refund_recv_accout;
	
	@XStreamAlias("refund_request_source")
	private String refund_request_source;
	
	@XStreamAlias("refund_status")
	private String refund_status;
	
	@XStreamAlias("success_time")
	private String success_time;
	
}

RefundCallbackController 微信退款回调实现逻辑

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.util.Base64;

import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import lombok.extern.log4j.Log4j2;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;


@Log4j2
@Controller
public class RefundCallbackController  {
	
    private static Cipher cipher = null;  //解码器

	
    public String refundSusForWx(HttpServletRequest request, String type) throws Exception{
		InputStream inStream = request.getInputStream();
		int _buffer_size = 1024;
		if (inStream != null) {
			ByteArrayOutputStream outStream = new ByteArrayOutputStream();
			byte[] tempBytes = new byte[_buffer_size];
			int count = -1;
			while ((count = inStream.read(tempBytes, 0, _buffer_size)) != -1) {
				outStream.write(tempBytes, 0, count);
			}
			tempBytes = null; outStream.flush();
			//将流转换成字符串
			String result = new String(outStream.toByteArray(), "UTF-8");
			log.info("微信退款回调:{}", result);
			WeixinRefundResponse response = WechatUtil.xmlToBean(result, WeixinRefundResponse.class);

			String reqInfo = response.getReq_info();

			init(IspConstant.WX_ISP_MCH_KEY);
			Base64.Decoder decoder = Base64.getDecoder();
	        byte[] base64ByteArr = decoder.decode(reqInfo);

	        String jmResult = new String(cipher.doFinal(base64ByteArr));
	        log.info("解密结果:{}", jmResult);
	        String jmr = jmResult.replaceAll("<root>", "<xml>").replaceAll("</root>", "</xml>");
	        if(jmr.contains("]></refund_recv_accout>") && !jmr.contains("]]></refund_recv_accout>")){
	    		jmr = jmr.replaceAll("]></refund_recv_accout>", "]]></refund_recv_accout>");
	    	}
	        WeixinRefundResponse notifyResponse = WechatUtil.xmlToBean(jmr, WeixinRefundResponse.class);

			//通知成功
			if(StringUtils.isNotBlank(notifyResponse.getRefund_status()) && "SUCCESS".equals(notifyResponse.getRefund_status())){
					log.info("退款单号:{}, type:{}", notifyResponse.getOut_refund_no(), type);
                	//具体个人实现逻辑 可以去调用不同自己的实现逻辑代码
					refundSus(notifyResponse.getOut_refund_no(), type);
				}
		}

		return "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
    }
	
    public static void init(String mchKey) {
//        String key = MD5Util.MD5Encode(WechatUtil.getMchKey(), "");
    	String key = MD5Util.MD5Encode(mchKey, "");
        SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), "AES");
        Security.addProvider(new BouncyCastleProvider());
        try {
            cipher = Cipher.getInstance("AES/ECB/PKCS7Padding");
            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        }
    }
    
    
    
}
  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值