前言:
此处整理为简便,将所有应用到的方法都整理到了一起,实际开发中尽量将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;
}
}