Commit b399846c authored by hezhen's avatar hezhen

添加退款功能

parent e2bc25cd
......@@ -35,9 +35,7 @@ public class SystemConfig {
public static final String WINXIN_PARTNER_KEY = SystemProperty.getConfig("WINXIN_PARTNER_KEY");
public static final String WINXIN_PARTNER = SystemProperty.getConfig("WINXIN_PARTNER");
// 移动端app微信支付配置参数
/**
* appStore配置
*/
// 微信开发平台应用id(OK)
public static String APP_ID = SystemProperty.getConfig("APP_ID");
// 财付通商户号(OK)
......@@ -58,5 +56,7 @@ public class SystemConfig {
// 平台所有微信的回调地址的域名
public static final String weixinHost = SystemProperty.getConfig("weixinHost");
// 存放退款证书目录
public static String APICLIENT_CERT = SystemProperty.getConfig("APICLIENT_CERT");
}
......@@ -34,3 +34,5 @@ APP_PARTNER_KEY=cnitr89201000QZNWcnitr89201000QZ
APP_TRADE_TYPE=APP
#支付回调
weixinHost=xxfcmgmt.upyuns.com
#证书存放目录
APICLIENT_CERT=D:\\server\\cert\\apiclient_cert.p12
......@@ -69,6 +69,11 @@ public class OrderRefund implements Serializable {
@Column(name = "serial_number")
@ApiModelProperty(value = "支付接口返回的流水号")
private String serialNumber;
//退款描述
@Column(name = "refund_desc")
@ApiModelProperty(value = "退款描述")
private String refundDesc;
//创建时间
@Column(name = "crt_time")
......
package com.xxfc.platform.universal.feign;
import com.alibaba.fastjson.JSONObject;
import com.xxfc.platform.universal.entity.OrderRefund;
import com.xxfc.platform.universal.vo.OrderPayVo;
import com.xxfc.platform.universal.vo.OrderRefundVo;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
......@@ -24,5 +26,7 @@ public interface ThirdFeign {
public JSONObject uploadFiles(@RequestParam("files") MultipartFile[] files);
@RequestMapping(value = "/pay/app/wx", method = RequestMethod.POST)
public JSONObject wx(@RequestBody OrderPayVo orderPayVo);
@RequestMapping(value = "/refund/app/wx", method = RequestMethod.POST)
public JSONObject refund(@RequestBody OrderRefundVo orderRefundVo);
}
......@@ -21,35 +21,47 @@ import java.io.Serializable;
public class OrderPayVo{
//订单号
@ApiModelProperty(value = "订单号")
private String orderNo;
//用户id
@ApiModelProperty(value = "用户id")
private Integer userId;
//1:微信公众号支付 2.支付宝即时到账,3,银联
@ApiModelProperty(value = "1:微信公众号支付 2.支付宝即时到账,3,银联")
private Integer payWay;
//渠道:1-租车;2-旅游
@ApiModelProperty(value = "渠道:1-租车;2-旅游")
private Integer channel;
//来源:1-app;2-小程序
@ApiModelProperty(value = "1-app;2-小程序")
private Integer type;
//买家IP地址
@ApiModelProperty(value = "买家IP地址")
private String buyerIp;
//商品标题
@ApiModelProperty(value = "商品标题")
private String subject;
//商品描述信息
@ApiModelProperty(value = "商品描述信息")
private String body;
//回调地址
@ApiModelProperty(value = "回调地址")
private String notifyUrl;
//支付金额
@ApiModelProperty(value = "支付金额")
private Integer amount;
//买家第三方付款账号
@ApiModelProperty(value = "买家第三方付款账号")
private String buyerAccount;
......
package com.xxfc.platform.universal.vo;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
/**
* 订单支付退款
*
* @author zjw
* @email nishijjo@qq.com
* @date 2019-05-28 16:17:42
*/
@Data
public class OrderRefundVo{
//订单号
@ApiModelProperty(value = "订单号")
private String orderNo;
//用户id
@ApiModelProperty(value = "用户id")
private Integer userId;
//支付金额
@ApiModelProperty(value = "支付金额")
private Integer amount;
//退款金额
@ApiModelProperty(value = "退款金额")
private Integer refundAmount;
//支付接口返回的流水号
@ApiModelProperty(value = "支付接口返回的流水号")
private String serialNumber;
//退款描述
@ApiModelProperty(value = "退款描述")
private String refundDesc;
}
package com.xxfc.platform.universal.weixin.api;
import java.io.File;
import java.io.FileInputStream;
import java.security.KeyStore;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import javax.net.ssl.SSLContext;
import com.github.wxiaoqi.security.common.util.process.SystemConfig;
import com.xxfc.platform.universal.weixin.util.Snowflake;
import com.xxfc.platform.universal.weixin.util.WeChatSignUtil;
import com.xxfc.platform.universal.weixin.util.XmlUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
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.protocol.HttpContext;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
/**
* 微信支付退款工具类(先测试双向证书退款功能是否能正常,如果能正常再接入正常业务)
*
* @author hezhen
*
*/
@Slf4j
public class WxPayRefundUtils {
private static final String URL = "https://api.mch.weixin.qq.com/secapi/pay/refund";
// private static final String pay_appId = "wx81470220f10b266e";
// private static final String pay_mchId = "1423414902";
// private static final String subMchId = "1511773781";
private static final String APICLIENT_CERT = SystemConfig.APICLIENT_CERT;
// private static final String pay_partnerKey =
// "CNITR89201000CNITR89201000FUWUSH";
public static boolean refund(String pay_appId, String pay_mchId,String pay_partnerKey,
String out_trade_no,String out_refund_no, String total_fee, String refund_fee, String refund_desc) {
String result = postXML(URL, genXML(pay_appId, pay_mchId,pay_partnerKey, out_trade_no,out_refund_no, total_fee,
refund_fee, refund_desc), pay_mchId);
System.out.println(result);
// 判定退款操作结果是否正确
String return_msg = "<return_msg><![CDATA[OK]]></return_msg>";
String result_code = "<result_code><![CDATA[SUCCESS]]></result_code>";
String return_code = "<return_code><![CDATA[SUCCESS]]></return_code>";
return result.contains(result_code) && result.contains(return_msg) && result.contains(return_code);
}
private static String postXML(String url, String xml, String pay_mchId) {
try (FileInputStream ips = new FileInputStream(new File(APICLIENT_CERT));) {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(ips, pay_mchId.toCharArray());
SSLContext sslContext = SSLContexts.custom().loadKeyMaterial(keyStore, pay_mchId.toCharArray()).build();
@SuppressWarnings("deprecation")
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, new String[] { "TLSv1" },
null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
return post(httpClient, url, xml, "text/xml; charset=UTF-8", "UTF-8", null, null);
} catch (Exception e1) {
e1.printStackTrace();
}
return null;
}
private static String post(CloseableHttpClient httpclient, String uri, String data, String miniType, String charset,
String cookie, String refer) {
HttpPost httpPost = new HttpPost(uri);
if (miniType != null) {
httpPost.setHeader("Content-Type", miniType);
}
if (data != null) {
StringEntity stringEntity = null;
try {
if (charset != null) {
stringEntity = new StringEntity(data, charset);
} else {
stringEntity = new StringEntity(data);
}
} catch (Exception e) {
e.printStackTrace();
return "failure";
}
httpPost.setEntity(stringEntity);
}
if (cookie != null) {
httpPost.setHeader("Cookie", cookie);
}
if (refer != null) {
httpPost.setHeader("Refer", refer);
}
CloseableHttpResponse httpResponse = null;
HttpContext httContext = HttpClientContext.create();
try {
httpResponse = httpclient.execute(httpPost, httContext);
} catch (Exception e) {
e.printStackTrace();
return "failure";
}
HttpEntity httpEntity = null;
try {
httpEntity = httpResponse.getEntity();
} catch (Exception e) {
e.printStackTrace();
return "failure";
}
String response_body = null;
try {
response_body = EntityUtils.toString(httpEntity, "utf-8");
} catch (Exception e) {
e.printStackTrace();
return "failure";
}
try {
httpResponse.close();
} catch (Exception e) {
e.printStackTrace();
}
try {
httpclient.close();
} catch (Exception e) {
e.printStackTrace();
}
return response_body;
}
private static String genXML(String pay_appId, String pay_mchId, String pay_partnerKey,
String out_trade_no,String out_refund_no, String total_fee, String refund_fee, String refund_desc) {
Random random = new Random();
long nextLong = random.nextLong();
String nonce_str = nextLong + "";
String sign = genSign(pay_appId, pay_mchId, pay_partnerKey, out_trade_no, total_fee, refund_fee,
out_refund_no, refund_desc, nonce_str);
XMLTempBean xmlTempBean = new XMLTempBean(pay_appId, pay_mchId, nonce_str, out_trade_no, total_fee,
refund_fee, out_refund_no, refund_desc, sign);
String xml = XmlUtils.toXml("xml", XMLTempBean.class, xmlTempBean);
log.info("genXML:"+xml);
return xml;
}
private static String genSign(String pay_appId, String pay_mchId, String pay_partnerKey,
String out_trade_no, String total_fee, String refund_fee, String out_refund_no, String refund_desc,
String nonce_str) {
Map<String, String> map = new HashMap<String, String>();
map.put("appid", pay_appId);
map.put("mch_id", pay_mchId);
map.put("nonce_str", nonce_str);
map.put("out_trade_no", out_trade_no);
map.put("total_fee", total_fee);
map.put("refund_fee", refund_fee);
map.put("out_refund_no", out_refund_no);
map.put("refund_desc", refund_desc);
String sign = WeChatSignUtil.getMD5Sign(map, pay_partnerKey);
return sign;
}
}
class XMLTempBean {
private String appid;
private String mch_id;
private String nonce_str;
private String out_trade_no;
private String total_fee;
private String refund_fee;
private String refund_desc;
private String out_refund_no;
private String sign;
public XMLTempBean(String appid, String mch_id, String nonce_str, String out_trade_no,
String total_fee, String refund_fee, String out_refund_no, String refund_desc, String sign) {
super();
this.appid = appid;
this.mch_id = mch_id;
this.nonce_str = nonce_str;
this.out_trade_no = out_trade_no;
this.total_fee = total_fee;
this.refund_fee = refund_fee;
this.refund_desc = refund_desc;
this.out_refund_no = out_refund_no;
this.sign = sign;
}
public String getAppid() {
return appid;
}
public String getMch_id() {
return mch_id;
}
public String getNonce_str() {
return nonce_str;
}
public String getOut_trade_no() {
return out_trade_no;
}
public String getTotal_fee() {
return total_fee;
}
public String getRefund_desc() {
return refund_desc;
}
public String getSign() {
return sign;
}
public String getOut_refund_no() {
return out_refund_no;
}
public String getRefund_fee() {
return refund_fee;
}
}
package com.xxfc.platform.universal.weixin.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 主键id生成器
*
* @author linlihua
*
*/
public class Snowflake {
private static final Logger logger = LoggerFactory.getLogger(Snowflake.class);
private long workerId;// 工作id 5位 0-31
private long datacenterId;// 数据中心id 5位 0-31
private long sequence;// 2的12次方减1
/**
* 生成系统时间戳毫秒
*
* @return
*/
private long genTime() {
return System.currentTimeMillis();
}
// 再定义工作id位数
private long workerIdBits = 5L;
private long datacenterIdBits = 5L;
private long sequenceBits = 12L;
// 位运算求得工作id范围值
private long maxWorkerId = -1 ^ (-1 << workerIdBits);
private long maxDatacenterId = -1 ^ (-1 << datacenterIdBits);
private long sequenceMask = -1 ^ (-1 << sequenceBits);
private long twepoch = 1522145570000L;
private long workerIdShift = sequenceBits;
private long datacenterIdShift = sequenceBits + workerIdBits;
private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
private Snowflake(long workerId, long datacenterId, long sequance) {
if (workerId < 0 || workerId > maxWorkerId) {
logger.error("创建Snowflake实例失败workerId过大");
return;
}
if (datacenterId < 0 || datacenterId > maxDatacenterId) {
logger.error("创建Snowflake实例失败datacenterId过大");
return;
}
this.workerId = workerId;
this.datacenterId = datacenterId;
this.sequence = sequance;
}
private long lastTimestamp = -1L;
// 因为此类是单例的,所以这个方法就不需要synchronized
private synchronized long nextId() {
long timestamp = genTime();
if (lastTimestamp == timestamp) {// 在同一毫秒内,执行了多次
// 如果sequence超过了4095,那么和4095进行与运算则会变为0,当同一毫秒内,生成了4095个序列,那么必须在下一毫秒内,并且序列再次从0开始,防止溢出4095
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
timestamp = tilTimestamp(lastTimestamp);
}
} else {
sequence = 0;
}
lastTimestamp = timestamp;
return ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift)
| (workerId << workerIdShift) | sequence;
}
private long tilTimestamp(long lastTimestamp) {
long timestamp = genTime();
while (lastTimestamp >= timestamp)
timestamp = genTime();
return timestamp;
}
public static long build() {
return Holder.getSingleton().nextId();
}
// 静态内部类加载实现单例
private static class Holder {
private static Snowflake snowflake = new Snowflake(1, 1, 1);
private static Snowflake getSingleton() {
return Holder.snowflake;
}
}
}
package com.xxfc.platform.universal.weixin.util;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;
/**
*
*/
public class WeChatSignUtil {
/**
*
* @param signParamMap
* @param keyValue
* @return
*/
public static String getMD5Sign(Map<String, String> signParamMap, String keyValue) {
StringBuilder sb = new StringBuilder();
ArrayList<String> keyList = new ArrayList<String>(signParamMap.keySet());
Collections.sort(keyList);
for (String key : keyList) {
String value = signParamMap.get(key);
if (!"".equals(value) && !"key".equals(key) && !"sign".equals(key)) {
sb.append(key).append("=").append(value).append("&");
}
}
sb.append("key=").append(keyValue);
return getMD5(sb.toString()).toUpperCase();
}
/**
* MD5
*/
private static final char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
'F' };
public static String getMD5(String inStr) {
byte[] inStrBytes = null;
try {
inStrBytes = inStr.getBytes("utf-8");
} catch (UnsupportedEncodingException e1) {
e1.printStackTrace();
}
try {
MessageDigest MD = MessageDigest.getInstance("MD5");
MD.update(inStrBytes);
byte[] mdByte = MD.digest();
char[] str = new char[mdByte.length * 2];
int k = 0;
for (int i = 0; i < mdByte.length; i++) {
byte temp = mdByte[i];
str[k++] = hexDigits[temp >>> 4 & 0xf];
str[k++] = hexDigits[temp & 0xf];
}
return new String(str);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
}
package com.xxfc.platform.universal.weixin.util;
import java.io.Writer;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.core.util.QuickWriter;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
import com.thoughtworks.xstream.io.xml.XmlFriendlyReplacer;
import com.thoughtworks.xstream.io.xml.XppDriver;
/**
* Xml工具类
*
* @author Deacon
*
*/
public class XmlUtils {
private static String PREFIX_CDATA = "<![CDATA[";
private static String SUFFIX_CDATA = "]]>";
public static XStream getXStream()
{
return new XStream(new XppDriver(new XmlFriendlyReplacer("_-", "_") {
public HierarchicalStreamWriter createWriter(Writer out) {
return new PrettyPrintWriter(out) {
// 对所有xml节点的转换都增加CDATA标记
boolean cdata = true;
@SuppressWarnings({ "rawtypes" })
public void startNode(String name, Class clazz) {
super.startNode(name, clazz);
}
protected void writeText(QuickWriter writer, String text) {
if (cdata) {
writer.write("<![CDATA[");
writer.write(text);
writer.write("]]>");
} else {
writer.write(text);
}
}
};
}
}));
}
public static String toXml(String alias, Class cls, Object obj) {
XStream xstream = getXStream();
xstream.alias(alias, cls);
return xstream.toXML(obj);
}
public static <T> T fromXml(String alias, Class cls, String xml) {
XStream xstream = getXStream();
xstream.alias(alias, cls);
return (T) xstream.fromXML(xml);
}
}
package com.xxfc.platform.universal.biz;
import com.alibaba.fastjson.JSONObject;
import com.github.wxiaoqi.security.common.util.process.ResultCode;
import com.github.wxiaoqi.security.common.util.process.SystemConfig;
import com.github.wxiaoqi.security.common.util.result.JsonResultUtil;
import com.xxfc.platform.universal.controller.OrderRefundController;
import com.xxfc.platform.universal.vo.OrderRefundVo;
import com.xxfc.platform.universal.weixin.api.WxPayRefundUtils;
import com.xxfc.platform.universal.weixin.util.OrderUtil;
import com.xxfc.platform.universal.weixin.util.Snowflake;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import com.xxfc.platform.universal.entity.OrderRefund;
......@@ -14,5 +26,45 @@ import com.github.wxiaoqi.security.common.biz.BaseBiz;
* @date 2019-05-28 16:17:42
*/
@Service
@Slf4j
public class OrderRefundBiz extends BaseBiz<OrderRefundMapper,OrderRefund> {
//申请退款
public JSONObject refund(OrderRefundVo orderRefundVo)throws Exception{
if(orderRefundVo==null){
log.error("-----参数为空-----------");
return JsonResultUtil.createFailedResult(ResultCode.NULL_CODE, "参数为空");
}
String out_trade_no=orderRefundVo.getOrderNo();
String appid= SystemConfig.APP_ID;
String mchId=SystemConfig.APP_PARTNER;
String partnerKey=SystemConfig.APP_PARTNER_KEY;
Integer payAmount=orderRefundVo.getAmount();
Integer refundAmount=orderRefundVo.getRefundAmount();
String refundDesc =StringUtils.isNotBlank(orderRefundVo.getRefundDesc())?orderRefundVo.getRefundDesc(): "审核通过,退款";
String out_refund_no = Snowflake.build() + "";
if(StringUtils.isBlank(out_trade_no)||StringUtils.isBlank(appid)||StringUtils.isBlank(mchId)||StringUtils.isBlank(partnerKey)
||payAmount==null||payAmount==0||refundAmount==null||refundAmount==0){
log.error("-----参数为空-----------");
return JsonResultUtil.createFailedResult(ResultCode.NULL_CODE, "参数为空");
}
boolean flag=WxPayRefundUtils.refund(appid,mchId,partnerKey,out_trade_no,out_refund_no,payAmount+"",
refundAmount+"",refundDesc);
if(flag){
OrderRefund orderRefund= new OrderRefund();
BeanUtils.copyProperties(orderRefund,orderRefundVo);
if(StringUtils.isNotBlank(orderRefund.getRefundDesc())){
orderRefund.setRefundDesc(refundDesc);
}
orderRefund.setStatus(2);
orderRefund.setFinishTime(System.currentTimeMillis());
String trade_no = OrderUtil.GetOrderNumber("");
orderRefund.setRefundTradeNo(trade_no);
orderRefund.setOutRefundNo(out_refund_no);
insertSelective(orderRefund);
return JsonResultUtil.createSuccessResultWithObj(trade_no);
}
return JsonResultUtil.createDefaultFail();
}
}
\ No newline at end of file
package com.xxfc.platform.universal.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Parameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.ArrayList;
import java.util.List;
/**
* @Description : swagger配置配置
* @Author : Mars
* @Date : 2017年9月6日
*/
@Configuration
@EnableSwagger2
public class SwaggerConfig {
/**
* Every Docket bean is picked up by the swagger-mvc framework - allowing for multiple
* swagger groups i.e. same code base multiple swagger resource listings.
*/
@Bean
public Docket customDocket(){
ParameterBuilder ticketPar = new ParameterBuilder();
List<Parameter> pars = new ArrayList<Parameter>();
ticketPar.name("Authorization").description("user Authorization")
.modelRef(new ModelRef("string")).parameterType("header")
.required(false).build(); //header中的ticket参数非必填,传空也可以
pars.add(ticketPar.build()); //根据每个方法名也知道当前方法在设置什么参数
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("com.xxfc.platform.universal"))
//.apis(RequestHandlerSelectors.any())
.build()
.globalOperationParameters(pars)
.apiInfo(apiInfo());
}
ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("api swagger document")
.description("前后端联调swagger api 文档")
.version("2.1.5.5")
.build();
}
}
\ No newline at end of file
package com.xxfc.platform.universal.controller;
import com.alibaba.fastjson.JSONObject;
import com.github.wxiaoqi.security.common.rest.BaseController;
import com.github.wxiaoqi.security.common.util.process.ResultCode;
import com.github.wxiaoqi.security.common.util.result.JsonResultUtil;
import com.xxfc.platform.universal.biz.OrderRefundBiz;
import com.xxfc.platform.universal.entity.OrderRefund;
import org.springframework.stereotype.Controller;
import com.xxfc.platform.universal.vo.OrderRefundVo;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@Controller
@RequestMapping("orderRefund")
@RestController
@RequestMapping("refund")
public class OrderRefundController extends BaseController<OrderRefundBiz,OrderRefund> {
@RequestMapping(value = "/app/wx", method = RequestMethod.POST) //匹配的是href中的download请求
public JSONObject refund(@RequestBody OrderRefundVo orderRefundVo) {
try {
return baseBiz.refund(orderRefundVo);
}catch (Exception e){
e.getMessage();
return JsonResultUtil.createFailedResult(ResultCode.EXCEPTION_CODE, "出现异常");
}
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment