From b473efc09477dc0c4b818474f17a99e4223c272a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Sun, 30 Jun 2024 13:32:42 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9A=E5=BE=AE=E4=BF=A1Na?= =?UTF-8?q?tive=E6=94=AF=E4=BB=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/core/utils/WxNativeUtil.java | 20 +++ .../system/controller/MainController.java | 43 +++++ .../common/system/entity/Payment.java | 90 ++++++++++ .../shop/controller/OrderController.java | 63 ++++--- .../controller/WxNativePayController.java | 162 ++++++++++++++++++ .../gxwebsoft/shop/service/OrderService.java | 2 + .../shop/service/impl/OrderServiceImpl.java | 47 +++++ .../com/gxwebsoft/shop/task/OrderTask.java | 2 +- 8 files changed, 402 insertions(+), 27 deletions(-) create mode 100644 src/main/java/com/gxwebsoft/common/core/utils/WxNativeUtil.java create mode 100644 src/main/java/com/gxwebsoft/common/system/entity/Payment.java create mode 100644 src/main/java/com/gxwebsoft/shop/controller/WxNativePayController.java diff --git a/src/main/java/com/gxwebsoft/common/core/utils/WxNativeUtil.java b/src/main/java/com/gxwebsoft/common/core/utils/WxNativeUtil.java new file mode 100644 index 0000000..52a8d2c --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/utils/WxNativeUtil.java @@ -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 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); + } +} diff --git a/src/main/java/com/gxwebsoft/common/system/controller/MainController.java b/src/main/java/com/gxwebsoft/common/system/controller/MainController.java index 1c4246c..6c5950e 100644 --- a/src/main/java/com/gxwebsoft/common/system/controller/MainController.java +++ b/src/main/java/com/gxwebsoft/common/system/controller/MainController.java @@ -275,4 +275,47 @@ public class MainController extends BaseController { tokenExpireTime, configProperties.getTokenKey()); return success("登录成功", new LoginResult(access_token, user)); } + + @ApiOperation("会员注册") + @PostMapping("/register") + public ApiResult 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)); + } } diff --git a/src/main/java/com/gxwebsoft/common/system/entity/Payment.java b/src/main/java/com/gxwebsoft/common/system/entity/Payment.java new file mode 100644 index 0000000..17069cb --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/entity/Payment.java @@ -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; + +} diff --git a/src/main/java/com/gxwebsoft/shop/controller/OrderController.java b/src/main/java/com/gxwebsoft/shop/controller/OrderController.java index ffd4b94..5d8c4d1 100644 --- a/src/main/java/com/gxwebsoft/shop/controller/OrderController.java +++ b/src/main/java/com/gxwebsoft/shop/controller/OrderController.java @@ -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) { diff --git a/src/main/java/com/gxwebsoft/shop/controller/WxNativePayController.java b/src/main/java/com/gxwebsoft/shop/controller/WxNativePayController.java new file mode 100644 index 0000000..a75df11 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/controller/WxNativePayController.java @@ -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 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"; + } +} diff --git a/src/main/java/com/gxwebsoft/shop/service/OrderService.java b/src/main/java/com/gxwebsoft/shop/service/OrderService.java index 301b3cd..c14ad32 100644 --- a/src/main/java/com/gxwebsoft/shop/service/OrderService.java +++ b/src/main/java/com/gxwebsoft/shop/service/OrderService.java @@ -50,4 +50,6 @@ public interface OrderService extends IService { Boolean payByBalance(Order order); Boolean payByUserCard(Order order); + +// String payByWxNative(Order order); } diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/OrderServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/OrderServiceImpl.java index 4edb3f3..ef3993e 100644 --- a/src/main/java/com/gxwebsoft/shop/service/impl/OrderServiceImpl.java +++ b/src/main/java/com/gxwebsoft/shop/service/impl/OrderServiceImpl.java @@ -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 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 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(); +// } } diff --git a/src/main/java/com/gxwebsoft/shop/task/OrderTask.java b/src/main/java/com/gxwebsoft/shop/task/OrderTask.java index 69a9e57..1334f42 100644 --- a/src/main/java/com/gxwebsoft/shop/task/OrderTask.java +++ b/src/main/java/com/gxwebsoft/shop/task/OrderTask.java @@ -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();