前言

此处整理为简便,将所有应用到的方法都整理到了一起,实际开发中尽量将controller,service,mapper,工具类分开。此文章着重注意退款回调,其中应用了数据解密(作者一开始困扰在此处);

微信退款请求

退款请求数据均来自客户支付的订单信息,以订单为依据进行退款;其中的逻辑以自己的业务需求来制定,此处仅此校验订单是否存在与订单状态是否符合退款需求;
此处请求退款需要小程序绑定商户平台的“退款证书”,在商户平台申请下载;

/**
 * @program: youpin
 * @description: 微信相关
 * @author: Mr.Jkx
 * @create: 2020-03-23 15:13
 */
@RestController
@RequestMapping(value = "/wxpay", produces = "application/json;charset=UTF-8")
public class WxPayController {
    private static final Logger LOGGER = (Logger) LoggerFactory.getLogger(WxPayController.class);

    @Resource
    private TOrderMapper tOrderMapper;

    /**
     * @Description: 微信退款
     * @Author: Mr.Jkx
     * @date: 2020/3/23 15:08
     */
    @RequestMapping(value = "/refundOrderByWx")
    public void refundOrderByWx(TOrder tOrder) throws Exception {
        // 退款证书所在服务器路径(商户平台申请下载)
        String certificatePath = "";
        // 查询退款订单数据
        TOrder tOrderData = tOrderMapper.selectOrderData(tOrder.getoId());
        if (null != tOrderData && !StringUtils.equals(OrderStatus.ORDER_STATUS1.getCode(), tOrder.getoStatus())) {
            // 退款总金额(商品总价钱+运费),此处订单退款金额需要和订单支付金额相符,否则调取不成功
            int refundTotalFee = new Double(tOrderData.getoTotalPrice() * 100 + tOrderData.getoWaybillPrice() * 100).intValue();
            String out_refund_no = UUIDUtil.getUUID();
            tOrderData.setoOutRefundNo(out_refund_no);
            PayUtil payUtil = new PayUtil();
            payUtil.setAppid("appid");
            payUtil.setMch_id("mch_id"); // 商户号
            payUtil.setPayKey("payKey"); // 秘钥
            payUtil.setOut_trade_no(tOrderData.getoCode());
            payUtil.setNonce_str(tOrderData.getoId());
            payUtil.setTotal_fee(refundTotalFee);
            payUtil.setRefund_fee(refundTotalFee);
            payUtil.setOut_refund_no(out_refund_no);
            payUtil.setTransaction_id(tOrderData.getoTransactionId());
            payUtil.setNotify_url("https://XXX/wxpay/refundCardOrder");
            Map<String, Object> map = wxRefund(payUtil, certificatePath);
            if (map.get("return_code").equals("SUCCESS")) {
                // 退款请求成功进行退款业务逻辑处理 TODO
            } else {
                System.out.println("微信退款请求失败");
            }
        } else {
            System.out.println("订单不存在或订单状态不争取不能退款");
        }
    }

    /**
     * @Description: 微信退款
     * @Author: Mr.Jkx
     * @date: 2020/3/23 15:59
     */
    public static Map<String, Object> wxRefund(PayUtil payUtil, String certificatepath) throws Exception {
        String mch_id = payUtil.getMch_id();
        String stringA = "appid=" + payUtil.getAppid()
                + "&mch_id=" + mch_id
                + "&nonce_str=" + payUtil.getNonce_str()
                + "¬ify_url=" + payUtil.getNotify_url()
                + "&out_refund_no=" + payUtil.getOut_refund_no()
                + "&out_trade_no=" + payUtil.getOut_trade_no()
                + "&refund_fee=" + payUtil.getRefund_fee()
                + "&total_fee=" + payUtil.getTotal_fee()
                + "&key=" + payUtil.getPayKey();

        String sign = Md5Util.md5(stringA).toUpperCase();

        String xml = "<xml>" +
                "   <appid>" + payUtil.getAppid() + "</appid>" +
                "   <mch_id>" + mch_id + "</mch_id>" +
                "   <nonce_str>" + payUtil.getNonce_str() + "</nonce_str>" +
                "   <out_refund_no>" + payUtil.getOut_refund_no() + "</out_refund_no>" +
                "   <out_trade_no>" + payUtil.getOut_trade_no() + "</out_trade_no>" +
                "   <refund_fee>" + payUtil.getRefund_fee() + "</refund_fee>" +
                "   <total_fee>" + payUtil.getTotal_fee() + "</total_fee>" +
                "   <notify_url>" + payUtil.getNotify_url() + "</notify_url>" +
                "   <sign>" + sign + "</sign>" +
                "</xml> ";

        LOGGER.info("调试模式_退款接口 请求XML数据:{}", xml);

        //调用统一下单接口,并接受返回的结果
        String result = payOfCertificate(Constants.WX_REFUND_URL, xml, certificatepath, mch_id);
        LOGGER.info("------退款回执信息{}-------" + result);
        // 将解析结果存储在HashMap中
        Map map = doXMLParse(result);
        LOGGER.info("----订单退款-----回执数据:{}", map);
        return map;
    }

    /**
     * @Description: 加载证书 发送请求
     * @Author: Mr.Jkx
     * @date: 2020/3/23 16:06
     * url:退款请求地址
     * data:请求数据
     * certificatepath:证书路径
     * mch_id:商品平台mch_id
     */
    public static String payOfCertificate(String url, String data, String certificatepath, String mch_id) throws Exception {
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        FileInputStream is = new FileInputStream(new File(certificatepath));
        try {
            keyStore.load(is, mch_id.toCharArray());
        } finally {
            is.close();
        }
        SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, mch_id.toCharArray()).build();
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
                sslcontext,
                new String[]{"TLSv1"},
                null,
                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();
        }
    }

    /**
     * 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。
     *
     * @param strxml
     * @return
     */
    public static Map doXMLParse(String strxml) throws Exception {
        if (null == strxml || "".equals(strxml)) {
            return null;
        }
        Map m = new HashMap();
        InputStream in = String2Inputstream(strxml);
        SAXBuilder builder = new SAXBuilder();
        Document doc = builder.build(in);
        Element root = doc.getRootElement();
        List list = root.getChildren();
        Iterator it = list.iterator();
        while (it.hasNext()) {
            Element e = (Element) it.next();
            String k = e.getName();
            String v = "";
            List children = e.getChildren();
            if (children.isEmpty()) {
                v = e.getTextNormalize();
            } else {
                v = getChildrenText(children);
            }
            m.put(k, v);
        }
        //关闭流
        in.close();
        return m;
    }

    /**
     * 获取子结点的xml
     *
     * @param children
     * @return String
     */
    public static String getChildrenText(List children) {
        StringBuffer sb = new StringBuffer();
        if (!children.isEmpty()) {
            Iterator it = children.iterator();
            while (it.hasNext()) {
                Element e = (Element) it.next();
                String name = e.getName();
                String value = e.getTextNormalize();
                List list = e.getChildren();
                sb.append("<" + name + ">");
                if (!list.isEmpty()) {
                    sb.append(getChildrenText(list));
                }
                sb.append(value);
                sb.append("</" + name + ">");
            }
        }

        return sb.toString();
    }

    public static InputStream String2Inputstream(String str) {
        return new ByteArrayInputStream(str.getBytes());
    }
}

微信退款回调

微信退款分为两个步骤,首先请求微信退款,然后再异步给予退款回执请求,通知退款是否成功;
退款地址在请求时已经作为参数请求到微信,以此为依据请求回调地址;
退款回执接口,接收参数,参数解密,根据回执参数判断是否退款成功,并进行相应业务逻辑;

@RestController
@RequestMapping(value = "/wxpay", produces = "application/json;charset=UTF-8")
public class WxPayController {
    private static final Logger LOGGER = (Logger) LoggerFactory.getLogger(WxPayController.class);

    @Resource
    private TOrderMapper tOrderMapper;

    /**
     * @Description: 微信退款回调
     * @Author: Mr.Jkx
     * @date: 2020/3/23 16:14
     */
    @RequestMapping(value = "/refundCardOrder")
    public void refundCardOrder(HttpServletRequest request, HttpServletResponse response) throws Exception {
        String xmlStr = NotifyServlet.getWxXml(request);
        LOGGER.info("---- refundCard order request data(xml) ----:{}", xmlStr);
        Map<String, String> map2 = refundCardMessageDecrypt(xmlStr);
        //	退款状态SUCCESS	SUCCESS-退款成功、CHANGE-退款异常、REFUNDCLOSE—退款关闭
        String refund_status = map2.get("refund_status");
        String transactionId = map2.get("transaction_id");
        LOGGER.info("---- refundCard order request data >> transactionId:{}", transactionId);
        // 根据支付微信回执订单号查询订单数据
        TOrder tOrder = tOrderMapper.selOrderByTransactionId(transactionId);
        LOGGER.info("---- refundCard sel order data >> oStatus:{}", tOrder.getoStatus());
        String refundId = map2.get("refund_id");
        tOrder.setoOutRefundNo(refundId);
        if (null != tOrder && StringUtils.equals(OrderStatus.ORDER_STATUS5.getCode(), tOrder.getoStatus())) {
            if (WXPayConstants.SUCCESS.equals(refund_status)) {
                // 退款成功支付回调,退款成功,退款逻辑处理 TODO
            }
        }
        // 微信支付操作成功之后,回执操作
        wxPayReceipt(response);
    }

    /**
     * @Description: 微信退款回调数据解密
     * @Author: Mr.Jkx
     * @date: 2020/6/8 19:04
     */
    public Map<String, String> refundCardMessageDecrypt(String xmlStr) throws Exception {
        LOGGER.info("-----微信退款回调请求参数解密 START ;xmlStr:{}", xmlStr);
        Map<String, String> aesMap = new HashMap<>();
        // xml转换为map
        Map<String, String> map = xmlToMap(xmlStr);
        if (WXPayConstants.SUCCESS.equalsIgnoreCase(map.get("return_code"))) {
            /** 以下字段在return_code为SUCCESS的时候有返回: **/
            // 加密信息:加密信息请用商户秘钥进行解密,详见解密方式
            String req_info = map.get("req_info");
            // 信息解密
            String resultStr = AESUtil.decryptData(req_info);
            aesMap = xmlToMap(resultStr);
            LOGGER.info("-----微信退款回调请求参数解密 END ;strData:{}, mapData:{}", xmlStr, aesMap);
        } else {
            LOGGER.info("微信退款-----数据返回失败!!!");
        }
        return aesMap;
    }

    /**
     * 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;
        }
    }

    /**
     * @Description: 微信回执信息
     * @Author: Mr.Jkx
     * @date: 2020/5/11 11:19
     */
    private void wxPayReceipt(HttpServletResponse response) throws IOException {
        String resXml = "";
        // 微信支付成功之后,回执操作
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/xml; charset=utf-8");
        PrintWriter out = response.getWriter();
        resXml = "<xml>"
                + "<return_code><![CDATA[SUCCESS]]></return_code>"
                + "<return_msg><![CDATA[OK]]></return_msg>"
                + "</xml> ";
        out.print(resXml);
        out.close();
    }
}

微信回调数据解密工具类

package com.pinto.youpin.util;

import com.pinto.youpin.util.wxpay.WXPayUtil;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.Security;
import java.util.Base64;

/**
 * 微信支付AES加解密工具类
 *
 * @author yclimb
 * @date 2018/6/21
 */
public class AESUtil {

    /**
     * 密钥算法
     */
    private static final String ALGORITHM = "AES";

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

    /**
     * 生成key
     */
    private static SecretKeySpec KEY;

    static {
        try {
            // Constants.API_KEY(微信支付秘钥)
            KEY = new SecretKeySpec(WXPayUtil.MD5(Constants.API_KEY).toLowerCase().getBytes(), ALGORITHM);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * AES加密
     *
     * @param data d
     * @return str
     * @throws Exception e
     */
    public static String encryptData(String data) throws Exception {
        // 创建密码器
        Security.addProvider(new BouncyCastleProvider());
        Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING, "BC");
        // 初始化
        cipher.init(Cipher.ENCRYPT_MODE, KEY);
        return base64Encode8859(new String(cipher.doFinal(data.getBytes()), "ISO-8859-1"));

    }

    /**
     * 解密方式
     * 解密步骤如下:
     * (1)对加密串A做base64解码,得到加密串B
     * (2)对商户key做md5,得到32位小写key* ( key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置 )
     * (3)用key*对加密串B做AES-256-ECB解密(PKCS7Padding)
     */
    public static String decryptData(String base64Data) throws Exception {
        Security.addProvider(new BouncyCastleProvider());
        Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING, "BC");
        cipher.init(Cipher.DECRYPT_MODE, KEY);
        return new String(cipher.doFinal(base64Decode8859(base64Data).getBytes("ISO-8859-1")), "utf-8");
    }

    /**
     * Base64解码
     *
     * @param source base64 str
     * @return str
     */
    public static String base64Decode8859(final String source) {
        String result = "";
        final Base64.Decoder decoder = Base64.getDecoder();
        try {
            // 此处的字符集是ISO-8859-1
            result = new String(decoder.decode(source), "ISO-8859-1");
        } catch (final UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * Base64加密
     *
     * @param source str
     * @return base64 str
     */
    public static String base64Encode8859(final String source) {
        String result = "";
        final Base64.Encoder encoder = Base64.getEncoder();
        byte[] textByte = null;
        try {
            //注意此处的编码是ISO-8859-1
            textByte = source.getBytes("ISO-8859-1");
            result = encoder.encodeToString(textByte);
        } catch (final UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * @Description: 测试
     * @Author: Mr.Jkx
     * @date: 2020/6/8 18:53
     */
    public static void main(String[] args) throws Exception {
        String A = "qS/pmvAXYetUObwHm9bAod9G3SVBKQK5CiIgETwHJT4ExUpJnIg87m37KlokIsBZCnQBIO2Ear7Q/IazZ6jDNsnmsITqYt1hPYloGjdjRGlqdSSBVRjk9NIkRRQIlb+5AOHJttfVKMsbMK8FzoysE+rL8yKaOzXvsNCA2g60z3bEw3x891ZwPPiUSkVJGeIHpafWdR94Y/j3hfsrEw5KOTGiPneH5d9zhC73MW/kDWu9+wDkJCtCf5fNc9GIC5x2zKNZozpQ9wT/WLyjSz/En166xbgUt9tApaaQSayFQ0eSokMjYYLKO5KJQ355QtkvZlW96rX9IO6hVHXDgPD7kJOTh/L99ZQtG5umLBfOd9i3xVH4qH+gvi/i0gEpvQOhTvxcrZeKs8Rsliua46u/aBdUy6GlICRxQPmvKBfL9cE2L5MZGqHkCMTmSr1i4L8Ubxoi3Yv6TCTTOo4MVc64igb9HttMVfOiLFrZKAyH64Y5C6+GATUMSzWhXDn089QyrZk+W6GFkQlA6dBlO7v0aucF8t3L6SFtnxm6XkH6eD4/FFxKz+wsqKDX1s+GnPGQdwjxsS3RLGjJuNoSB7N+v4AUbMgLT2sBzew89ow7/vEUMjJMQt3eISwprOaDZqZQBgdLVUwDyWnrWi50Rr2wEuJXv/m6x8f40wN93L8GvGbMsWGXlp9V9W3LR2LZD9CnrWAlhoYoDGMAwCKuPh+dfjXmVGttGxegM+PlUR8nq6Qr1zwHz4dV3PgzWlf3n5qR72tAJ/Y0045n3dT7Iw4UNzBHC6XkUIA884paHbZ3D0V95+WrdyVQ4icsgZIneaAMZfslVsnigUjnXl3m/qZGlW5A6d93VXNe8bQgA6s6lJeEsaZc3sLVPi5Tlr2nfbgdhB4XqYkR4DebEbUzalSOqM+OOeCsYj920+FboIxvShy2ECk6bjebMM3kYw0s1NUWXynKFTvbgZ35H9TNKaeom1qYVmbb/581N8+sO3yDDFzZaaqLqOtaUtgIe2SOS2A6GRnKSanqbsJVU4j2amWEpicl3WchYV9KPeuoqodu+4UCsaY2juUIcbbof/ygkG5NkDz27RA4fBxxAlqvtzEftw==";
        Security.addProvider(new BouncyCastleProvider());
        System.out.println(AESUtil.decryptData(A));
    }
}

微信请求数据组装对象

package com.pinto.youpin.entity.tool;

public class PayUtil {

    private String appid;//微信分配的小程序ID  必填
    private String mch_id;//微信支付分配的商户号  必填
    private String device_info;//自定义参数,可以为终端设备号(门店号或收银设备ID),PC网页或公众号内支付可以传"WEB" 设备号

    /**
     * 微信支付API接口协议中包含字段nonce_str,主要保证签名不可预测。我们推荐生成随机数算法如下:调用随机数函数生成,将得到的值转换为字符串。
     */
    private String nonce_str;//随机字符串,长度要求在32位以内。 必填

    /**
     * stringA="appid=wxd930ea5d5a258f4f&body=test&device_info=1000&mch_id=10000100&nonce_str=ibuaiVcKdpRxkhJA";
     * <p>
     * stringSignTemp=stringA+"&key=192006250b4c09247ec02edce69f6a2d" //注:key为商户平台设置的密钥key
     * <p>
     * sign=MD5(stringSignTemp).toUpperCase()="9A0A8659F005D6984697E2CA0A9CF3B7" //注:MD5签名方式
     * <p>
     * sign=hash_hmac("sha256",stringSignTemp,key).toUpperCase()="6A9AE1657590FD6257D693A078E1C3E4BB6BA4DC30B23E0EE2496E54170DACD6" //注:HMAC-SHA256签名方式
     */
    private String sign;//通过签名算法计算得出的签名值  必填

    private String sign_type;//签名类型,默认为MD5,支持HMAC-SHA256和MD5。

    private String body;//商品简单描述,该字段请按照规范传递 必填

    private String detail;//商品详细描述,对于使用单品优惠的商户,改字段必须按照规范上传

    private String attach;//附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。

    private String out_trade_no;//商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*且在同一个商户号下唯一 必填

    private String fee_type;//符合ISO 4217标准的三位字母代码,默认人民币:CNY,详细列表请参见

    private int total_fee;//订单总金额,单位为分  必填

    private String spbill_create_ip;//APP和H5支付提交用户端ip,Native支付填调用微信支付API的机器IP。123.12.12.123  必填

    private String time_start;//订单生成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010

    private String time_expire;//订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。订单失效时间是针对订单号而言的,由于在请求支付的时候有一个必传参数prepay_id只有两小时的有效期,所以在重入时间超过2小时的时候需要重新请求下单接口获取新的prepay_id

    private String goods_tag;//订单优惠标记,使用代金券或立减优惠功能时需要的参数

    private String notify_url;//异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。 必填

    private String trade_type;//小程序取值如下:JSAPI 必填

    private String product_id;//trade_type=NATIVE时,此参数必传。此参数为二维码中包含的商品ID,商户自行定义。

    private String limit_pay;//上传此参数no_credit--可限制用户不能使用信用卡支付

    private String openid;//trade_type=JSAPI,此参数必传,用户在商户appid下的唯一标识。openid如何获取 必填

    private String Out_refund_no;//商户退款单号

    private int refund_fee;//退款金额

    private String transaction_id;

    private String payKey; // 支付秘钥
    public String getAppid() {
        return appid;
    }

    public void setAppid(String appid) {
        this.appid = appid;
    }

    public String getMch_id() {
        return mch_id;
    }

    public void setMch_id(String mch_id) {
        this.mch_id = mch_id;
    }

    public String getDevice_info() {
        return device_info;
    }

    public void setDevice_info(String device_info) {
        this.device_info = device_info;
    }

    public String getNonce_str() {
        return nonce_str;
    }

    public void setNonce_str(String nonce_str) {
        this.nonce_str = nonce_str;
    }

    public String getSign() {
        return sign;
    }

    public void setSign(String sign) {
        this.sign = sign;
    }

    public String getSign_type() {
        return sign_type;
    }

    public void setSign_type(String sign_type) {
        this.sign_type = sign_type;
    }

    public String getBody() {
        return body;
    }

    public void setBody(String body) {
        this.body = body;
    }

    public String getDetail() {
        return detail;
    }

    public void setDetail(String detail) {
        this.detail = detail;
    }

    public String getAttach() {
        return attach;
    }

    public void setAttach(String attach) {
        this.attach = attach;
    }

    public String getOut_trade_no() {
        return out_trade_no;
    }

    public void setOut_trade_no(String out_trade_no) {
        this.out_trade_no = out_trade_no;
    }

    public String getFee_type() {
        return fee_type;
    }

    public void setFee_type(String fee_type) {
        this.fee_type = fee_type;
    }

    public int getTotal_fee() {
        return total_fee;
    }

    public void setTotal_fee(int total_fee) {
        this.total_fee = total_fee;
    }

    public String getSpbill_create_ip() {
        return spbill_create_ip;
    }

    public void setSpbill_create_ip(String spbill_create_ip) {
        this.spbill_create_ip = spbill_create_ip;
    }

    public String getTime_start() {
        return time_start;
    }

    public void setTime_start(String time_start) {
        this.time_start = time_start;
    }

    public String getTime_expire() {
        return time_expire;
    }

    public void setTime_expire(String time_expire) {
        this.time_expire = time_expire;
    }

    public String getGoods_tag() {
        return goods_tag;
    }

    public void setGoods_tag(String goods_tag) {
        this.goods_tag = goods_tag;
    }

    public String getNotify_url() {
        return notify_url;
    }

    public void setNotify_url(String notify_url) {
        this.notify_url = notify_url;
    }

    public String getTrade_type() {
        return trade_type;
    }

    public void setTrade_type(String trade_type) {
        this.trade_type = trade_type;
    }

    public String getProduct_id() {
        return product_id;
    }

    public void setProduct_id(String product_id) {
        this.product_id = product_id;
    }

    public String getLimit_pay() {
        return limit_pay;
    }

    public void setLimit_pay(String limit_pay) {
        this.limit_pay = limit_pay;
    }

    public String getOpenid() {
        return openid;
    }

    public void setOpenid(String openid) {
        this.openid = openid;
    }

    public String getOut_refund_no() {
        return Out_refund_no;
    }

    public void setOut_refund_no(String out_refund_no) {
        Out_refund_no = out_refund_no;
    }

    public int getRefund_fee() {
        return refund_fee;
    }

    public void setRefund_fee(int refund_fee) {
        this.refund_fee = refund_fee;
    }

    public String getTransaction_id() {
        return transaction_id;
    }

    public void setTransaction_id(String transaction_id) {
        this.transaction_id = transaction_id;
    }

    public String getPayKey() {
        return payKey;
    }

    public void setPayKey(String payKey) {
        this.payKey = payKey;
    }
}

作者能力有限,如有问题欢迎批评指正!!!