新增:微信Native支付
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
package com.gxwebsoft.common.core.utils;
|
||||
|
||||
import com.wechat.pay.java.core.Config;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
public class WxNativeUtil {
|
||||
|
||||
private static final Map<Integer, Config> tenantConfigs = new HashMap<>();
|
||||
|
||||
public static void addConfig(Integer tenantId, Config config) {
|
||||
tenantConfigs.put(tenantId, config);
|
||||
}
|
||||
|
||||
public static Config getConfig(Integer tenantId) {
|
||||
return tenantConfigs.get(tenantId);
|
||||
}
|
||||
}
|
||||
@@ -275,4 +275,47 @@ public class MainController extends BaseController {
|
||||
tokenExpireTime, configProperties.getTokenKey());
|
||||
return success("登录成功", new LoginResult(access_token, user));
|
||||
}
|
||||
|
||||
@ApiOperation("会员注册")
|
||||
@PostMapping("/register")
|
||||
public ApiResult<LoginResult> register(@RequestBody LoginParam param, HttpServletRequest request) {
|
||||
final String phone = param.getPhone();
|
||||
final Integer tenantId = param.getTenantId();
|
||||
final String code = param.getCode();
|
||||
|
||||
User user = userService.getByUsername(phone, tenantId);
|
||||
// 验证码校验
|
||||
String key = "code:" + param.getPhone();
|
||||
if(!code.equals(redisUtil.get(key))){
|
||||
String message = "验证码不正确";
|
||||
loginRecordService.saveAsync(phone, LoginRecord.TYPE_ERROR, message, tenantId,request);
|
||||
return fail(message, null);
|
||||
}
|
||||
if (user == null) {
|
||||
String message = "账号不存在";
|
||||
loginRecordService.saveAsync(phone, LoginRecord.TYPE_ERROR, message, tenantId,request);
|
||||
return fail(message, null);
|
||||
}
|
||||
if (!user.getStatus().equals(0)) {
|
||||
String message = "账号被冻结";
|
||||
loginRecordService.saveAsync(phone, LoginRecord.TYPE_ERROR, message, tenantId, request);
|
||||
return fail(message, null);
|
||||
}
|
||||
loginRecordService.saveAsync(phone, LoginRecord.TYPE_LOGIN, null, tenantId, request);
|
||||
|
||||
// 设置过期时间
|
||||
Long tokenExpireTime = configProperties.getTokenExpireTime();
|
||||
final JSONObject register = cacheClient.getSettingInfo("register", tenantId);
|
||||
if(register != null){
|
||||
final String ExpireTime = register.getString("tokenExpireTime");
|
||||
if (ExpireTime != null) {
|
||||
tokenExpireTime = Long.valueOf(ExpireTime);
|
||||
}
|
||||
}
|
||||
|
||||
// 签发token
|
||||
String access_token = JwtUtil.buildToken(new JwtSubject(phone, tenantId),
|
||||
tokenExpireTime, configProperties.getTokenKey());
|
||||
return success("登录成功", new LoginResult(access_token, user));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.gxwebsoft.common.system.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 支付方式
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2024-05-11 12:39:11
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@ApiModel(value = "Payment对象", description = "支付方式")
|
||||
@TableName("sys_payment")
|
||||
public class Payment implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ApiModelProperty(value = "ID")
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Integer id;
|
||||
|
||||
@ApiModelProperty(value = "支付方式")
|
||||
private String name;
|
||||
|
||||
@ApiModelProperty(value = "类型")
|
||||
private Integer type;
|
||||
|
||||
@ApiModelProperty(value = "标识")
|
||||
private String code;
|
||||
|
||||
@ApiModelProperty(value = "支付图标")
|
||||
private String image;
|
||||
|
||||
@ApiModelProperty(value = "微信商户号类型 1普通商户2子商户")
|
||||
private Integer wechatType;
|
||||
|
||||
@ApiModelProperty(value = "应用ID")
|
||||
private String appId;
|
||||
|
||||
@ApiModelProperty(value = "商户号")
|
||||
private String mchId;
|
||||
|
||||
@ApiModelProperty(value = "设置APIv3密钥")
|
||||
private String apiKey;
|
||||
|
||||
@ApiModelProperty(value = "证书文件 (CERT)")
|
||||
private String apiclientCert;
|
||||
|
||||
@ApiModelProperty(value = "证书文件 (KEY)")
|
||||
private String apiclientKey;
|
||||
|
||||
@ApiModelProperty(value = "商户证书序列号")
|
||||
private String merchantSerialNumber;
|
||||
|
||||
@ApiModelProperty(value = "支付结果通知")
|
||||
private String notifyUrl;
|
||||
|
||||
@ApiModelProperty(value = "备注")
|
||||
private String comments;
|
||||
|
||||
@ApiModelProperty(value = "文章排序(数字越小越靠前)")
|
||||
private Integer sortNumber;
|
||||
|
||||
@ApiModelProperty(value = "状态, 0未启用, 1启用")
|
||||
private Boolean status;
|
||||
|
||||
@ApiModelProperty(value = "是否删除, 0否, 1是")
|
||||
@TableLogic
|
||||
private Integer deleted;
|
||||
|
||||
@ApiModelProperty(value = "租户id")
|
||||
private Integer tenantId;
|
||||
|
||||
@ApiModelProperty(value = "注册时间")
|
||||
private Date createTime;
|
||||
|
||||
@ApiModelProperty(value = "修改时间")
|
||||
private Date updateTime;
|
||||
|
||||
}
|
||||
@@ -143,7 +143,6 @@ public class OrderController extends BaseController {
|
||||
return fail("核销失败",null);
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('sys:order:save')")
|
||||
@ApiOperation("下单")
|
||||
@PostMapping()
|
||||
public ApiResult<?> save(@RequestBody Order order, HttpServletRequest request) {
|
||||
@@ -172,13 +171,13 @@ public class OrderController extends BaseController {
|
||||
// 已下过订单
|
||||
if(order.getOrderId() != null){
|
||||
Order order1 = orderService.getByIdRel(order.getOrderId());
|
||||
order1.setPayType(order.getPayType());
|
||||
order1.setOpenid(loginUser.getOpenid());
|
||||
order.setOrderNo(Long.toString(IdUtil.getSnowflakeNextId()));
|
||||
System.out.println("已下过订单 = " + order1);
|
||||
// 微信统一下单接口
|
||||
if (order.getPayType().equals(1)) {
|
||||
return success("下单成功", orderService.createWxOrder(order1));
|
||||
}
|
||||
order1.setOrderNo(Long.toString(IdUtil.getSnowflakeNextId()));
|
||||
order1.setAccessToken(access_token);
|
||||
order1.setLoginUser(loginUser);
|
||||
// 调起支付
|
||||
return onPay(order1);
|
||||
}
|
||||
|
||||
// 创建订单
|
||||
@@ -190,6 +189,8 @@ public class OrderController extends BaseController {
|
||||
order.setRealName(loginUser.getRealName());
|
||||
order.setPhone(loginUser.getPhone());
|
||||
order.setUserId(loginUser.getUserId());
|
||||
order.setLoginUser(loginUser);
|
||||
order.setAccessToken(access_token);
|
||||
order.setExpirationTime(DateUtil.offset(DateUtil.date(), DateField.MINUTE, 10));
|
||||
if (order.getOrderInfo() != null) {
|
||||
order.setTotalNum(order.getOrderInfo().size());
|
||||
@@ -257,29 +258,39 @@ public class OrderController extends BaseController {
|
||||
userCardService.save(userCard);
|
||||
order.setCardId(userCard.getId());
|
||||
}
|
||||
|
||||
// TODO 余额支付
|
||||
if (order.getPayType().equals(0)) {
|
||||
order.setAccessToken(access_token);
|
||||
order.setLoginUser(loginUser);
|
||||
return success("下单成功", orderService.payByBalance(order));
|
||||
}
|
||||
|
||||
// TODO 微信统一下单接口
|
||||
if (order.getPayType().equals(1)) {
|
||||
return success("下单成功", orderService.createWxOrder(order));
|
||||
}
|
||||
|
||||
// TODO 会员卡支付
|
||||
if(order.getPayType().equals(2)){
|
||||
order.setAccessToken(access_token);
|
||||
order.setLoginUser(loginUser);
|
||||
return success("下单成功", orderService.payByUserCard(order));
|
||||
}
|
||||
// 调起支付
|
||||
return onPay(order);
|
||||
}
|
||||
return fail("下单失败");
|
||||
}
|
||||
|
||||
private ApiResult<?> onPay(Order order) {
|
||||
// TODO 余额支付
|
||||
if (order.getPayType().equals(0)) {
|
||||
order.setAccessToken(order.getAccessToken());
|
||||
return success("下单成功", orderService.payByBalance(order));
|
||||
}
|
||||
|
||||
// TODO 微信统一下单接口
|
||||
if (order.getPayType().equals(1)) {
|
||||
return success("下单成功", orderService.createWxOrder(order));
|
||||
}
|
||||
|
||||
// // TODO 微信Native支付
|
||||
// if(order.getPayType().equals(102)){
|
||||
// order.setAccessToken(access_token);
|
||||
// order.setLoginUser(loginUser);
|
||||
// return success("下单成功", orderService.payByWxNative(order));
|
||||
// }
|
||||
|
||||
// TODO 会员卡支付
|
||||
if(order.getPayType().equals(6)){
|
||||
order.setAccessToken(order.getAccessToken());
|
||||
return success("下单成功", orderService.payByUserCard(order));
|
||||
}
|
||||
return fail("支付失败");
|
||||
}
|
||||
|
||||
@ApiOperation("修改订单")
|
||||
@PutMapping()
|
||||
public ApiResult<?> update(@RequestBody Order order) {
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
package com.gxwebsoft.shop.controller;
|
||||
|
||||
import cn.hutool.core.date.DateField;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.gxwebsoft.common.core.config.ConfigProperties;
|
||||
import com.gxwebsoft.common.core.utils.CommonUtil;
|
||||
import com.gxwebsoft.common.core.utils.RedisUtil;
|
||||
import com.gxwebsoft.common.core.utils.WxNativeUtil;
|
||||
import com.gxwebsoft.common.core.web.ApiResult;
|
||||
import com.gxwebsoft.common.core.web.BaseController;
|
||||
import com.gxwebsoft.common.system.entity.Setting;
|
||||
import com.gxwebsoft.common.system.entity.User;
|
||||
import com.gxwebsoft.shop.entity.Order;
|
||||
import com.gxwebsoft.common.system.entity.Payment;
|
||||
import com.gxwebsoft.common.system.param.SettingParam;
|
||||
import com.gxwebsoft.shop.service.OrderService;
|
||||
import com.gxwebsoft.common.system.service.SettingService;
|
||||
import com.wechat.pay.java.core.Config;
|
||||
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
|
||||
import com.wechat.pay.java.service.payments.nativepay.NativePayService;
|
||||
import com.wechat.pay.java.service.payments.nativepay.model.Amount;
|
||||
import com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest;
|
||||
import com.wechat.pay.java.service.payments.nativepay.model.PrepayResponse;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
@Api(tags = "微信Native支付接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/shop/wx-native-pay")
|
||||
public class WxNativePayController extends BaseController {
|
||||
@Value("${spring.profiles.active}")
|
||||
String active;
|
||||
public static String merchantId = "1246610101";
|
||||
public static String privateKeyPath = "/Users/gxwebsoft/JAVA/com.gxwebsoft.core/src/main/resources/cert/apiclient_key.pem";
|
||||
public static String merchantSerialNumber = "48749613B40AA8F1D768583FC352358E13EB5AF0";
|
||||
public static String apiV3Key = "zGufUcqa7ovgxRL0kF5OlPr482EZwtn9";
|
||||
@Resource
|
||||
private RedisUtil redisUtil;
|
||||
private Config wxPayConfig;
|
||||
@Resource
|
||||
private ConfigProperties config;
|
||||
@Resource
|
||||
private SettingService settingService;
|
||||
@Resource
|
||||
private OrderService orderService;
|
||||
|
||||
|
||||
@ApiOperation("生成付款码")
|
||||
@PostMapping("/codeUrl")
|
||||
public ApiResult<?> getCodeUrl(@RequestBody Order order) {
|
||||
final User loginUser = getLoginUser();
|
||||
if(loginUser == null){
|
||||
return fail("请先登录");
|
||||
}
|
||||
String key = "Payment:wxPay:".concat(getTenantId().toString());
|
||||
final Payment payment = redisUtil.get(key, Payment.class);
|
||||
if (payment == null) {
|
||||
return fail("微信支付未配置");
|
||||
}
|
||||
// 获取微信小程序配置信息
|
||||
final String string = redisUtil.get("mp-weixin:".concat(getTenantId().toString()));
|
||||
if(string == null){
|
||||
return fail("微信小程序未配置");
|
||||
}
|
||||
final JSONObject jsonObject = JSONObject.parseObject(string);
|
||||
final String appId = jsonObject.getString("appId");
|
||||
|
||||
// 处理订单数据
|
||||
order.setTenantId(loginUser.getTenantId());
|
||||
order.setUserId(loginUser.getUserId());
|
||||
order.setRealName(loginUser.getRealName());
|
||||
order.setPhone(loginUser.getPhone());
|
||||
order.setExpirationTime(DateUtil.offset(DateUtil.date(), DateField.MONTH, order.getTotalNum()));
|
||||
|
||||
// 使用自动更新平台证书的RSA配置
|
||||
// 一个商户号只能初始化一个配置,否则会因为重复的下载任务报错
|
||||
|
||||
// 构建service
|
||||
NativePayService service = new NativePayService.Builder().config(this.getWxPayConfig()).build();
|
||||
// request.setXxx(val)设置所需参数,具体参数可见Request定义
|
||||
PrepayRequest request = new PrepayRequest();
|
||||
// 计算金额
|
||||
order.setMoney(new BigDecimal("0.01")); // order.getPayPrice().toString()
|
||||
long orderNo = IdUtil.getSnowflakeNextId();
|
||||
order.setOrderNo(Long.toString(orderNo));
|
||||
BigDecimal decimal = order.getMoney();
|
||||
final BigDecimal multiply = decimal.multiply(new BigDecimal(100));
|
||||
// 将 BigDecimal 转换为 Integer
|
||||
Integer money = multiply.intValue();
|
||||
Amount amount = new Amount();
|
||||
amount.setTotal(money);
|
||||
request.setAmount(amount);
|
||||
request.setAppid(appId);
|
||||
request.setMchid(payment.getMchId());
|
||||
request.setDescription(order.getComments());
|
||||
request.setNotifyUrl("https://modules.gxwebsoft.com/api/shop/wx-native-pay/notify/" + getTenantId());
|
||||
request.setOutTradeNo(order.getOrderNo());
|
||||
// 调用下单方法,得到应答
|
||||
PrepayResponse response = service.prepay(request);
|
||||
// 使用微信扫描 code_url 对应的二维码,即可体验Native支付
|
||||
// System.out.println(response.getCodeUrl());
|
||||
// 生成指定url对应的二维码到文件,宽和高都是300像素
|
||||
// QrCodeUtil.generate(response.getCodeUrl(), 300, 300, FileUtil.file("/Users/gxwebsoft/Documents/uploads/wx-native-qrcode.jpg"));
|
||||
orderService.save(order);
|
||||
|
||||
return success("生成付款码", response.getCodeUrl());
|
||||
}
|
||||
|
||||
|
||||
private Config getWxPayConfig() {
|
||||
// 获取租户ID
|
||||
final Integer tenantId = getTenantId();
|
||||
Config build = WxNativeUtil.getConfig(tenantId);
|
||||
if (build != null) {
|
||||
return build;
|
||||
}
|
||||
String key = "Payment:wxPay:".concat(tenantId.toString());
|
||||
final Payment payment = redisUtil.get(key, Payment.class);
|
||||
System.out.println("payment = " + payment);
|
||||
String apiclientKey = config.getUploadPath().concat("/file").concat(payment.getApiclientKey());
|
||||
// 开发环境配置
|
||||
if(active.equals("dev")){
|
||||
apiclientKey = privateKeyPath;
|
||||
}
|
||||
Config config = new RSAAutoCertificateConfig.Builder()
|
||||
.merchantId(payment.getMchId())
|
||||
.privateKeyFromPath(apiclientKey)
|
||||
.merchantSerialNumber(payment.getMerchantSerialNumber())
|
||||
.apiV3Key(payment.getApiKey())
|
||||
.build();
|
||||
System.out.println("config = " + config);
|
||||
WxNativeUtil.addConfig(tenantId, config);
|
||||
return config;
|
||||
}
|
||||
|
||||
@ApiModelProperty("异步通知")
|
||||
@PostMapping("/notify/{tenantId}")
|
||||
public String wxNotify(@RequestHeader Map<String, String> header, @RequestBody String body, @PathVariable("tenantId") Integer tenantId) {
|
||||
System.out.println("异步通知*************** = ");
|
||||
System.out.println("request header = " + header);
|
||||
System.out.println("request body = " + body);
|
||||
System.out.println("tenantId = " + tenantId);
|
||||
|
||||
// 获取支付配置信息用于解密
|
||||
final SettingParam param = new SettingParam();
|
||||
param.setSettingKey("payment");
|
||||
param.setTenantId(tenantId);
|
||||
final String uploadPath = config.getUploadPath(); // 服务器本地路径
|
||||
|
||||
return "fail";
|
||||
}
|
||||
}
|
||||
@@ -50,4 +50,6 @@ public interface OrderService extends IService<Order> {
|
||||
Boolean payByBalance(Order order);
|
||||
|
||||
Boolean payByUserCard(Order order);
|
||||
|
||||
// String payByWxNative(Order order);
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@ import com.wechat.pay.java.service.payments.jsapi.model.Amount;
|
||||
import com.wechat.pay.java.service.payments.jsapi.model.Payer;
|
||||
import com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest;
|
||||
import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse;
|
||||
import com.wechat.pay.java.service.payments.nativepay.NativePayService;
|
||||
import com.wechat.pay.java.service.payments.nativepay.model.PrepayResponse;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
@@ -90,6 +92,8 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
|
||||
@Resource
|
||||
private UserBalanceLogService userBalanceLogService;
|
||||
@Resource
|
||||
private SettingService settingService;
|
||||
@Resource
|
||||
private RedisUtil redisUtil;
|
||||
public static String privateKeyPath = "/Users/gxwebsoft/JAVA/com.gxwebsoft.core/src/main/resources/cert/apiclient_key.pem";
|
||||
public static String privateCertPath = "/Users/gxwebsoft/JAVA/com.gxwebsoft.core/src/main/resources/cert/apiclient_cert.pem";
|
||||
@@ -370,5 +374,48 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
|
||||
orderService.updateById(order);
|
||||
return true;
|
||||
}
|
||||
//
|
||||
// @Override
|
||||
// public String payByWxNative(Order order) {
|
||||
// String key = "Payment:wxPay:".concat(order.getTenantId().toString());
|
||||
// final Payment payment = redisUtil.get(key, Payment.class);
|
||||
// if (payment == null) {
|
||||
// throw new BusinessException("微信支付未配置");
|
||||
// }
|
||||
// // 获取微信小程序配置信息
|
||||
// JSONObject setting = settingService.getBySettingKey("mp-weixin");
|
||||
// final String appId = setting.getString("appId");
|
||||
// final String appSecret = setting.getString("appSecret");
|
||||
//
|
||||
// // 使用自动更新平台证书的RSA配置
|
||||
// // 一个商户号只能初始化一个配置,否则会因为重复的下载任务报错
|
||||
//
|
||||
// // 构建service
|
||||
//// NativePayService service = new NativePayService.Builder().config(this.getWxPayConfig()).build();
|
||||
// // request.setXxx(val)设置所需参数,具体参数可见Request定义
|
||||
// com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest request = new com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest();
|
||||
// // 计算金额
|
||||
// order.setMoney(new BigDecimal(0.01));
|
||||
// order.setOrderNo(CommonUtil.createOrderNo());
|
||||
// BigDecimal decimal = order.getMoney();
|
||||
// final BigDecimal multiply = decimal.multiply(new BigDecimal(100));
|
||||
// // 将 BigDecimal 转换为 Integer
|
||||
// Integer money = multiply.intValue();
|
||||
// com.wechat.pay.java.service.payments.nativepay.model.Amount amount = new com.wechat.pay.java.service.payments.nativepay.model.Amount();
|
||||
// amount.setTotal(money);
|
||||
// request.setAmount(amount);
|
||||
// request.setAppid(appId);
|
||||
// request.setMchid(payment.getMchId());
|
||||
// request.setDescription("企业版-1年授权");
|
||||
//// request.setNotifyUrl("https://server.gxwebsoft.com/api/system/wx-native-pay/notify/" + getTenantId());
|
||||
// request.setOutTradeNo(order.getOrderNo());
|
||||
// // 调用下单方法,得到应答
|
||||
//// PrepayResponse response = service.prepay(request);
|
||||
// // 使用微信扫描 code_url 对应的二维码,即可体验Native支付
|
||||
//// System.out.println(response.getCodeUrl());
|
||||
// // 生成指定url对应的二维码到文件,宽和高都是300像素
|
||||
//// QrCodeUtil.generate(response.getCodeUrl(), 300, 300, FileUtil.file("/Users/gxwebsoft/Documents/uploads/wx-native-qrcode.jpg"));
|
||||
//// return response.getCodeUrl();
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ public class OrderTask extends BaseController {
|
||||
/**
|
||||
* 自动处理过期订单任务
|
||||
*/
|
||||
@Scheduled(fixedDelay = 2000, initialDelay = 2000)
|
||||
// @Scheduled(fixedDelay = 2000, initialDelay = 2000)
|
||||
@Transactional(rollbackFor = {Exception.class})
|
||||
public void settledExpirationOrder() {
|
||||
final OrderParam p = new OrderParam();
|
||||
|
||||
Reference in New Issue
Block a user