oying-common/src/main/java/com/oying/utils/DateUtil.java
@@ -3,6 +3,7 @@ import java.sql.Timestamp; import java.time.*; import java.time.format.DateTimeFormatter; import java.util.Calendar; import java.util.Date; /** @@ -14,6 +15,19 @@ public static final DateTimeFormatter DFY_MD_HMS = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); public static final DateTimeFormatter DFY_MD = DateTimeFormatter.ofPattern("yyyy-MM-dd"); public static final DateTimeFormatter SDF_YMDHMS = DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); /** * Timestamp增加分钟 */ public static Timestamp addMinute(Timestamp time, int month) { // 创建 Calendar 对象并设置为当前日期和时间的值 Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(time.getTime()); // 将MINUTE字段添加到当前日期和时间 calendar.add(Calendar.MINUTE, month); return new Timestamp(calendar.getTime().getTime()); } /** * LocalDateTime 转时间戳 @@ -73,7 +87,7 @@ * 日期 格式化 * * @param localDateTime / * @param patten / * @param patten / * @return / */ public static String localDateTimeFormat(LocalDateTime localDateTime, String patten) { @@ -85,7 +99,7 @@ * 日期 格式化 * * @param localDateTime / * @param df / * @param df / * @return / */ public static String localDateTimeFormat(LocalDateTime localDateTime, DateTimeFormatter df) { @@ -104,6 +118,7 @@ /** * 获取当前时间 * * @return 、 */ public static Timestamp getTimeStamp() { oying-common/src/main/java/com/oying/utils/RedisUtils.java
@@ -5,11 +5,14 @@ import com.google.common.collect.Sets; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.*; import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.stereotype.Component; import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -21,7 +24,11 @@ @SuppressWarnings({"unchecked","all"}) public class RedisUtils { private static final Logger log = LoggerFactory.getLogger(RedisUtils.class); @Value("${jwt.generate-order-sn}") private String generateOrderSn; @Value("${wx.enabled}") private Boolean wxEnabled; private static final String T = "-T-"; private RedisTemplate<Object, Object> redisTemplate; public RedisUtils(RedisTemplate<Object, Object> redisTemplate) { @@ -31,6 +38,34 @@ } /** * 生成18位订单编号:8位日期+4位秒+7位以上自增id * * @param i * @return */ public String generateOrderSn(Integer i) { StringBuilder sb = new StringBuilder(); String date = new SimpleDateFormat("yyyyMMdd").format(new Date()); String key = generateOrderSn + date; Long increment = increment(key); sb.append(date); sb.append(String.format("%04d", i)); String incrementStr = increment.toString(); if (incrementStr.length() <= 7) { sb.append(String.format("%07d", increment)); } else { sb.append(incrementStr); } if (wxEnabled) { // 生产环境 return sb.toString(); } else { // 测试环境 return T + sb.toString(); } } /** * 判断key是否过期 * * @param key oying-common/src/main/java/com/oying/utils/enums/GenerateEnum.java
New file @@ -0,0 +1,38 @@ package com.oying.utils.enums; import lombok.AllArgsConstructor; import lombok.Getter; /** * @author xin * @description * @date 2024/10/30 下午4:17 */ @Getter @AllArgsConstructor public enum GenerateEnum { ORDER(10, "订单编号"), RETURN_ORDER(20, "退单编号"), BUSINESS_NO(30,"未使用"), FREE_DEPOSIT(40,"未使用"), PENETRATE_ID(50,"未使用"), RETURN_FREE_DEPOSIT(60,"未使用"), CONTRACT_NUM(70,"未使用"), EIGHT(80,"未使用"), NINE(90,"未使用"), UNKNOWN(0, "未知枚举"); private final Integer key; private final String value; public static GenerateEnum find(Integer key) { for (GenerateEnum value : GenerateEnum.values()) { if (key.equals(value.getKey())) { return value; } } return UNKNOWN; } } oying-system/src/main/java/com/oying/modules/hwc/service/SwiftPassService.java
@@ -23,7 +23,7 @@ * * @see [类、类#方法、类#成员] */ HwcResponse pay(String ip, Integer total, String timeExpire, String description, String openId, HwcResponse pay(String ip, String total, String timeExpire, String description, String openId, String orderNum, PayTypeEnum status) throws IOException; /** oying-system/src/main/java/com/oying/modules/hwc/service/impl/SwiftPassServiceImpl.java
@@ -8,6 +8,7 @@ import com.oying.modules.hwc.utils.SignUtils; import com.oying.modules.hwc.utils.XmlUtils; import com.oying.modules.security.config.SwiftPassProperties; import com.oying.modules.security.config.WeiXinProperties; import com.oying.modules.sh.service.OrderReturnService; import com.oying.modules.sh.service.OrderService; import com.oying.utils.enums.PayTypeEnum; @@ -22,7 +23,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @@ -49,10 +49,11 @@ private final SwiftPassProperties properties; private final OrderService orderService; private final OrderReturnService returnService; private final WeiXinProperties weiXinProperties; @Override @Transactional(rollbackFor = Exception.class) public HwcResponse pay(String ip, Integer total, String timeExpire, String description, String openId, public HwcResponse pay(String ip, String total, String timeExpire, String description, String openId, String orderNum, PayTypeEnum status) throws IOException { SortedMap<String, String> map = new TreeMap<>(); map.put("service", service_pay); @@ -64,8 +65,8 @@ map.put("out_trade_no", orderNum); map.put("body", description); map.put("sub_openid", openId); map.put("sub_appid", properties.getAppId()); map.put("total_fee", String.valueOf(total)); map.put("sub_appid", weiXinProperties.getAppId()); map.put("total_fee", total); map.put("mch_create_ip", ip); map.put("notify_url", properties.getNotifyUrl()); map.put("time_expire", timeExpire); oying-system/src/main/java/com/oying/modules/security/config/SecurityProperties.java
@@ -55,6 +55,11 @@ */ private Long renew; /** * 自定义redis key */ private String generateOrderSn; public String getTokenStartWith() { return tokenStartWith + " "; } oying-system/src/main/java/com/oying/modules/security/config/SwiftPassProperties.java
@@ -29,8 +29,6 @@ private String isRaw; // 是否小程序支付 private String isMinipg; // AppID private String appId; // 请求url private String reqUrl; // 支付通知地址 oying-system/src/main/java/com/oying/modules/security/config/WeiXinProperties.java
@@ -26,6 +26,6 @@ private String getPhoneNumber; /*POST 该接口用于发送订阅消息。*/ private String sendMessage; /* 是否生成环境*/ /* 是否生产环境*/ private boolean enabled; } oying-system/src/main/java/com/oying/modules/security/rest/AuthController.java
@@ -174,7 +174,7 @@ //创建用户 User user = new User(); user.setUsername(phone); user.setNickName("LYHD-" + phone); user.setNickName("OYING-" + phone); user.setUserType(ConstantsKey.BUYER); user.setEnabled(true); getRole(user); oying-system/src/main/java/com/oying/modules/sh/domain/OrderProductSnapshot.java
@@ -37,7 +37,6 @@ @ApiModelProperty(value = "商品ID") private Long productId; @NotBlank @ApiModelProperty(value = "商品编号") private String productCode; @@ -57,7 +56,6 @@ @ApiModelProperty(value = "主图片") private String productMainImage; @NotBlank @ApiModelProperty(value = "商品描述") private String productDescription; @@ -84,6 +82,7 @@ @ApiModelProperty(value = "实付金额") private BigDecimal actuallyPayPrice; @NotBlank @ApiModelProperty(value = "状态") private String payState; oying-system/src/main/java/com/oying/modules/sh/domain/OrderReturnProductSnapshot.java
@@ -37,7 +37,6 @@ @ApiModelProperty(value = "商品ID") private Long productId; @NotBlank @ApiModelProperty(value = "商品编号") private String productCode; oying-system/src/main/java/com/oying/modules/sh/domain/request/ProductOrder.java
@@ -16,5 +16,5 @@ @ApiModelProperty(value = "商品ID") private String productId; @ApiModelProperty(value = "数量") private Integer productCount; private Integer count; } oying-system/src/main/java/com/oying/modules/sh/rest/OrderController.java
@@ -19,6 +19,7 @@ import io.swagger.annotations.*; import java.io.IOException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; @@ -68,8 +69,8 @@ @PostMapping("submit") @Log("小程序:提交订单") @ApiOperation("小程序:提交订单") public ResponseEntity<Object> submitOrder(@Validated @RequestBody SubmitOrder submit) { return new ResponseEntity<>(R.success(orderService.submitOrder(submit)), HttpStatus.CREATED); public ResponseEntity<Object> submitOrder(@Validated @RequestBody SubmitOrder submit, HttpServletRequest request) { return new ResponseEntity<>(R.success(orderService.submitOrder(submit, request)), HttpStatus.CREATED); } @DeleteMapping oying-system/src/main/java/com/oying/modules/sh/service/OrderService.java
@@ -5,6 +5,7 @@ import java.util.List; import java.io.IOException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; @@ -40,7 +41,7 @@ OrderInfo generatorOrder(GeneratorOrder generator); Order submitOrder(SubmitOrder submit); Order submitOrder(SubmitOrder submit, HttpServletRequest request); /** * 创建 oying-system/src/main/java/com/oying/modules/sh/service/impl/OrderServiceImpl.java
@@ -1,17 +1,24 @@ package com.oying.modules.sh.service.impl; import com.alibaba.fastjson2.JSONObject; import com.oying.exception.BadRequestException; import com.oying.modules.hwc.service.SwiftPassService; import com.oying.modules.pc.product.domain.Product; import com.oying.modules.pc.product.domain.enums.ProductStatusEnum; import com.oying.modules.pc.product.service.ProductService; import com.oying.modules.sh.domain.Order; import com.oying.modules.sh.domain.OrderProductSnapshot; import com.oying.modules.sh.domain.request.GeneratorOrder; import com.oying.modules.sh.domain.request.ProductOrder; import com.oying.modules.sh.domain.request.SubmitOrder; import com.oying.modules.sh.domain.vo.OrderInfo; import com.oying.modules.sh.domain.vo.ProductInfo; import com.oying.modules.sh.service.OrderProductSnapshotService; import com.oying.modules.sh.service.UserAddressService; import com.oying.utils.FileUtil; import com.oying.utils.*; import com.oying.utils.enums.GenerateEnum; import com.oying.utils.enums.PayStateEnum; import com.oying.utils.enums.PayTypeEnum; import lombok.RequiredArgsConstructor; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; @@ -20,14 +27,13 @@ import com.oying.modules.sh.mapper.OrderMapper; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.oying.utils.PageUtil; import java.math.BigDecimal; import java.sql.Timestamp; import java.util.*; import java.io.IOException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.oying.utils.PageResult; /** * @author lixin @@ -41,6 +47,9 @@ private final OrderMapper orderMapper; private final UserAddressService userAddressService; private final ProductService productService; private final SwiftPassService swiftPassService; private final OrderProductSnapshotService productSnapshotService; private final RedisUtils redisUtils; @Override public PageResult<Order> queryAll(OrderQueryCriteria criteria, Page<Object> page) { @@ -53,13 +62,53 @@ } @Override public Order submitOrder(SubmitOrder submit) { @Transactional(rollbackFor = Exception.class) public synchronized Order submitOrder(SubmitOrder submit, HttpServletRequest request) { if (!submit.getPayType().equals(PayTypeEnum.HWC)) { throw new BadRequestException("支付类型暂未开放"); } String orderNum = redisUtils.generateOrderSn(GenerateEnum.ORDER.getKey()); // 总金额 BigDecimal amount = BigDecimal.ZERO; GeneratorOrder generator = new GeneratorOrder(); generator.setProducts(submit.getProducts()); generator.setStoreId(submit.getStoreId()); OrderInfo info = generatorOrder(generator); // 商品快照 List<OrderProductSnapshot> snapshots = new ArrayList<>(); for (ProductInfo productInfo : info.getProducts()) { Product product = productInfo.getProduct(); OrderProductSnapshot snapshot = new OrderProductSnapshot(); snapshot.setOrderNum(orderNum); snapshot.setStoreId(submit.getStoreId()); snapshot.setProductId(product.getProductId()); snapshot.setProductBarcode(product.getBarcode()); snapshot.setProductName(product.getName()); snapshot.setProductTitle(product.getTitle()); snapshot.setProductMainImage(product.getMainImageUrl()); snapshot.setParamData(JSONObject.toJSONString(productInfo.getProductLabels())); snapshot.setUnitPrice(product.getPrice()); snapshot.setDetailCount(productInfo.getCount()); snapshot.setOriginalPrice(product.getPrice()); snapshot.setPaidPrice(product.getPrice()); BigDecimal decimal = BigDecimalUtils.multiply(product.getPrice(), productInfo.getCount()); amount = BigDecimalUtils.add(amount, decimal); snapshot.setActuallyPayPrice(decimal); snapshot.setPayState(PayStateEnum.NOTPAY.getKey()); snapshots.add(snapshot); } // 下单后30分钟失效 Timestamp time = DateUtil.addMinute(DateUtil.getTimeStamp(), 30); String expire = DateUtil.localDateTimeFormat(time.toLocalDateTime(), DateUtil.SDF_YMDHMS); swiftPassService.pay(StringUtils.getIp(request), BigDecimalUtils.yuanToCents(amount).toString(), expire,SecurityUtils.getCurrentUserId()); productSnapshotService.saveBatch(snapshots); return null; } @Override @Transactional(rollbackFor = Exception.class) public OrderInfo generatorOrder(GeneratorOrder criteria) { public synchronized OrderInfo generatorOrder(GeneratorOrder criteria) { List<ProductInfo> products = new ArrayList<>(); BigDecimal amount = BigDecimal.ZERO; for (ProductOrder productOrder : criteria.getProducts()) { @@ -74,18 +123,18 @@ ProductStatusEnum statusEnum = ProductStatusEnum.get(product.getStatus()); throw new BadRequestException(product.getName() + ":" + (statusEnum != null ? statusEnum.getReasonPhrase() : "状态")); } if (productOrder.getProductCount() > product.getStockQuantity()) { if (productOrder.getCount() > product.getStockQuantity()) { throw new BadRequestException("商品库存不足"); } if (productOrder.getProductCount() < product.getMinPurchaseQuantity()) { if (productOrder.getCount() < product.getMinPurchaseQuantity()) { throw new BadRequestException("起售数量不足"); } ProductInfo info = new ProductInfo(); info.setProduct(product); info.setProductLabels(product.getLabels()); info.setCount(productOrder.getProductCount()); info.setCount(productOrder.getCount()); products.add(info); amount = amount.add(product.getPrice().multiply(BigDecimal.valueOf(productOrder.getProductCount()))); amount = BigDecimalUtils.add(amount, BigDecimalUtils.multiply(product.getPrice(), productOrder.getCount())); } OrderInfo info = new OrderInfo(); info.setProducts(products); oying-system/src/main/resources/config/application-dev.yml
@@ -89,6 +89,8 @@ online-key: "online_token:" # 验证码 code-key: "captcha_code:" # 自定义redis key generate-order-sn: oying:generate:sn # token 续期检查时间范围(默认30分钟,单位毫秒),在token即将过期的一段时间内用户操作了,则给用户的token续期 detect: 1800000 # 续期时间范围,默认1小时,单位毫秒 @@ -149,27 +151,25 @@ # 汇旺财 swift-pass: # 密钥 key: ny4u8YpBoKegSrkggT4mtV7PbBku1BKb key: vJB6683s4NtQtXBgHTvE1hWOUtcre7nj # 私钥 mch-private-key: MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC2YNuGqSAFcIVOc9mnmIbnVfQoyyMBoyGqTD3beiy/UsXZrGF4OIrPEevwXnwHktURupjHAKvt92PL4L9wTz7cknaQkF0NH5saOdY3sL88xH7F3qSgIwHPvkRileFeEHx+pKnXBms8yiliDqPk0MV3iRRoXKhRnojrWAYJhyz+1iJ13qWXCWcpr9hf77Uu/r1N7wsJKN4Z75M2qQC6LgRTaHoBroLGVtld4PM30ee6o9RR8yiFVx4FDVukinGR15nTsrNh+sUx3piVXqYrq23UPjUWVtJlf9cEvfNVXCMsa71LGEzUl0zxDfeyXRQhel3E27p2oovEYvhl5s+pZhtXAgMBAAECggEBAKXZLKinccwgh8lRBrQmuz7x5ieWms3pWDryPCozTy/pCKyq2rQlzu0BIiqi8W09tG773eTSEodDOkhzqANsvQB+XD2YWlYN0NEnmUtpem67TYGItvI9A2+0WaHBJIGSwnO0MBKu7qQIP1vBbn8s7vWF/b3tzM70ORSzJkJR37QY435+ZG9rKMKyL/tuGW7E5AgrHmyl2aK9me3vgUrZct2e9l5XLrn9oU1niEmbDbFARhphXDCEPIvFiu7WtFDBmp/jEOc4u/y/sYMjS1RWpO9iOyTZDO8Mi9iofXugmoot4Tn0lBckeR6YVRb/I3EcVADoMfk1iaN1aWv1u+Ska8ECgYEA3ZFYMvo5y7SztrEbyLpWGsp6aM4a5ght8p52/rFhSHD5CGS7Uywhp4LxxIjLTRBNa/UkBzOq1Xt1+PVDNHf0E1yYZqEv9OXWC0yiCsnwO6b7o9hc3VnnX4jw7FkS0+pjfgCK4/A9hFj9xUI+qX3L3+pOCseioUeYofqTc2bn7ncCgYEA0rhvvNO30EisLACdOIem8XgbL4LjKJ47FnYS/TEZ9cDhJW5G2pAxgyFsqhN/GuTVg8uSQ7a6PlcBxzTuOwdZg05RcCAdmsXUdGjVbKk2i4CBp0K1j4QYfr8Wt18x0RLfzIQDrZQajWssVGjo8sfMn8qmjbRNbMmokRTflGIkEiECgYEAxChaYn9fzbYEaRixlWtKsdtSthjKfZ8239ZlWSVnEEBcaY7svTzT6r6mFq5Y9rgZIxvbsriOZQQxtKBGyFvubXnLvwizMWiNsGE/ELgphFZYcH0r8hgXHGBbk5NkdNMNFE+cpyJZBCPZP9tfKKUjavC/+RE3LPv66GS0SDXx3g8CgYAYOIT3cmqFcWUA5c0si0MgLEsLqgLMT2vBSC8klTlDqzj14XgZdUuLpBLmdbk9cSIttP+J8v3zXnLg3++mL1EVq7HmrnpYqPajrs0hYU3YuRuuCxfteCSMpRBKYZHLU10QF/iIQibPLIt65FgqV9boYxXD4f6oS4Gps3mDh8hXoQKBgBKojPBWgp4/WBMfh8nUzFzTiW7ACyG2jAxGtu0dtqfwx2LkesUKGhD1JGlxdDQ6jSv9tbhAgbZjTAULSo03626ZaiSDF+nCwFsobd3wRiij8LiL9amW5t6XBeX6bS5SCh45GbgnNBtzBqUI19/CGH09YXNpCYnADsY4fab4wNpR # 公钥 plat-public-key: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtmDbhqkgBXCFTnPZp5iG51X0KMsjAaMhqkw923osv1LF2axheDiKzxHr8F58B5LVEbqYxwCr7fdjy+C/cE8+3JJ2kJBdDR+bGjnWN7C/PMR+xd6koCMBz75EYpXhXhB8fqSp1wZrPMopYg6j5NDFd4kUaFyoUZ6I61gGCYcs/tYidd6llwlnKa/YX++1Lv69Te8LCSjeGe+TNqkAui4EU2h6Aa6CxlbZXeDzN9HnuqPUUfMohVceBQ1bpIpxkdeZ07KzYfrFMd6YlV6mK6tt1D41FlbSZX/XBL3zVVwjLGu9SxhM1JdM8Q33sl0UIXpdxNu6dqKLxGL4ZebPqWYbVwIDAQAB # 门店编号 mch-id: 1030232127 mch-id: 1030238092 # 签名方式 sign-type: MD5 # 原生JS is-raw: 1 # 是否小程序支付 is-minipg: 1 # AppID app-id: wx5d2be84f99104b92 # 请求url req-url: https://pay.hstypay.com/v2/pay/gateway # 支付通知地址 notify-url: http://mini.cqliyan.cn/lyhd/api/swiftPass/alipayCallback notify-url: http://1.95.124.88:8088/api/swiftPass/alipayCallback # 退款通知地址 refund-url: https://localhost/oying/api/swiftPass/returnNotify refund-url: https://1.95.124.88:8088/api/swiftPass/returnNotify obs: access_key_id: RZ1UIOZDZ58DD4NWPD6Q