提交 bde70595 authored 作者: hzh's avatar hzh

订单模块功能实现

上级 d505c3ca
package org.dromara.order.api;
import org.dromara.order.api.domain.OrderPay;
import org.dromara.order.api.domain.RemoteSaveOrder;
/**
......@@ -16,6 +15,6 @@ public interface RemoteOrderService {
* @param order 订单信息
* @return 订单支付信息
*/
OrderPay createOrder(RemoteSaveOrder order);
String createOrder(RemoteSaveOrder order);
}
......@@ -4,6 +4,7 @@ import lombok.Data;
import org.dromara.order.api.enums.FeeType;
import org.dromara.order.api.enums.OrderType;
import org.dromara.order.api.enums.Source;
import org.dromara.order.api.enums.TradeType;
import java.math.BigDecimal;
import java.util.List;
......@@ -33,7 +34,10 @@ public class RemoteSaveOrder {
* 其他信息
*/
private Object otherInfo;
/**
* 订单交易类型
*/
private TradeType tradeType;
/**
* 费用列表
*/
......
package org.dromara.order.api.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 订单状态
*
* @author wenhe
*/
@Getter
@AllArgsConstructor
public enum OrderStatus {
//待支付
WAIT_PAY("WAIT_PAY", "待支付"),
//已支付
PAYED("PAYED", "已支付");
private final String code;
private final String desc;
}
package org.dromara.order.api.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 订单交易类型
*
* @author hzh
*/
@Getter
@AllArgsConstructor
public enum TradeType {
/**
* 公众号支付
*/
JSAPI,
/**
* 扫码支付
*/
NATIVE,
/**
* POS
*/
POS,
/**
* 银行转账
*/
BANK_TRANSFER
}
......@@ -97,6 +97,18 @@
<artifactId>ruoyi-common-core</artifactId>
</dependency>
<dependency>
<groupId>com.github.javen205</groupId>
<artifactId>IJPay-WxPay</artifactId>
<version>2.9.9</version>
</dependency>
<dependency>
<groupId>com.github.javen205</groupId>
<artifactId>IJPay-Core</artifactId>
<version>2.9.9</version>
</dependency>
</dependencies>
<build>
......
......@@ -48,6 +48,16 @@ public abstract class AbstractBaseService<V, B, T> implements IBaseService<V, B,
return flag;
}
@Override
public Boolean batchInsertByList(List<B> list) {
Class<T> clazz = (Class<T>) GenericsUtils.getSuperClassGenricType(this.getClass(), 2);
List<T> addList = MapstructUtils.convert(list, clazz);
for (T t : addList) {
validEntityBeforeSave(t);
}
return mapper().insertBatch(addList);
}
@Override
public Boolean updateByBo(B bo) {
Class<T> clazz = (Class<T>) GenericsUtils.getSuperClassGenricType(this.getClass(), 2);
......
......@@ -46,6 +46,13 @@ public interface IBaseService<V, B, T> {
*/
Boolean insertByBo(B bo);
/**
* 批量新增
* @param list list
* @return 是否新增成功
*/
Boolean batchInsertByList(List<B> list);
/**
* 修改
*
......
package org.dromara.order.config;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
/**
* <p>IJPay 让支付触手可及,封装了微信支付、支付宝支付、银联支付常用的支付方式以及各种常用的接口。</p>
*
* <p>不依赖任何第三方 mvc 框架,仅仅作为工具使用简单快速完成支付模块的开发,可轻松嵌入到任何系统里。 </p>
*
* <p>IJPay 交流群: 723992875、864988890</p>
*
* <p>Node.js 版: <a href="https://gitee.com/javen205/TNWX">https://gitee.com/javen205/TNWX</a></p>
*
* <p>微信配置 Bean</p>
*
* @author Javen
*/
@Getter
@Setter
@ToString
@RefreshScope
@Component
@ConfigurationProperties(prefix = "pay.wechat.v3")
public class WechatPayConfiguration {
/**
* 应用编号
*/
private String appId;
/**
* 商户号
*/
private String mchId;
/**
* 商户平台「API安全」中的 APIv3 密钥
*/
private String apiKey3;
/**
* API 证书中的 key.pem
*/
private String keyPath;
/**
* API 证书中的 cert.pem
*/
private String certPath;
/**
* 微信平台证书
*/
private String platformCertPath;
/**
* 应用域名,回调中会使用此参数
*/
private String domain;
/**
* 回调函数的接口路径
*/
private String notify;
}
package org.dromara.order.domain;
import lombok.Data;
/**
* @author hzh
* @date 2024-12-05
**/
@Data
public class PayInfo {
}
......@@ -5,6 +5,7 @@ import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.mybatis.core.domain.BaseEntity;
......@@ -21,6 +22,7 @@ import java.util.Date;
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
@AutoMapper(target = Order.class, reverseConvertGenerate = false)
public class OrderBo extends BaseEntity {
......
......@@ -5,6 +5,7 @@ import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.mybatis.core.domain.BaseEntity;
......@@ -20,6 +21,7 @@ import java.math.BigDecimal;
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
@AutoMapper(target = OrderFee.class, reverseConvertGenerate = false)
public class OrderFeeBo extends BaseEntity {
......
......@@ -5,6 +5,7 @@ import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.mybatis.core.domain.BaseEntity;
......@@ -21,6 +22,7 @@ import java.util.Date;
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
@AutoMapper(target = OrderTrade.class, reverseConvertGenerate = false)
public class OrderTradeBo extends BaseEntity {
......@@ -67,15 +69,15 @@ public class OrderTradeBo extends BaseEntity {
private BigDecimal payAmount;
/**
* 实际支付金额(分)
* 实际支付金额
*/
@NotNull(message = "实际支付金额(分)不能为空", groups = {AddGroup.class, EditGroup.class})
@NotNull(message = "实际支付金额", groups = {AddGroup.class, EditGroup.class})
private BigDecimal actualPayAmount;
/**
* 优惠金额(分)
* 优惠金额
*/
@NotNull(message = "优惠金额(分)不能为空", groups = {AddGroup.class, EditGroup.class})
@NotNull(message = "优惠金额", groups = {AddGroup.class, EditGroup.class})
private BigDecimal discountAmount;
/**
......
package org.dromara.order.dubbo;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.RequiredArgsConstructor;
import org.apache.dubbo.config.annotation.DubboService;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.order.api.RemoteOrderService;
import org.dromara.order.api.domain.OrderPay;
import org.dromara.order.api.domain.RemoteSaveOrder;
import org.dromara.order.api.enums.OrderStatus;
import org.dromara.order.domain.bo.OrderBo;
import org.dromara.order.domain.bo.OrderFeeBo;
import org.dromara.order.domain.bo.OrderTradeBo;
import org.dromara.order.domain.vo.OrderFeeVo;
import org.dromara.order.domain.vo.OrderTradeVo;
import org.dromara.order.domain.vo.OrderVo;
import org.dromara.order.service.IOrderFeeService;
import org.dromara.order.service.IOrderService;
import org.dromara.order.service.IOrderTradeService;
import org.dromara.order.service.util.IPayStrategy;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* @author hzh
* @date 2024-12-05
......@@ -16,8 +41,92 @@ import org.springframework.stereotype.Service;
@Service
@DubboService
public class RemoteOrderServiceImpl implements RemoteOrderService {
private IOrderService orderService;
private IOrderTradeService orderTradeService;
private IOrderFeeService orderFeeService;
@Value("${pay.expire.minute:5}")
private int expireMinute;
@GlobalTransactional(rollbackFor = Exception.class)
@Override
public OrderPay createOrder(RemoteSaveOrder order) {
return null;
public String createOrder(RemoteSaveOrder remoteOrder) {
//查询订单信息
Long userId = LoginHelper.getUserId();
//获取订单信息
OrderVo order = orderService.queryList(new OrderBo()
.setUserId(userId)
.setOriginOrderNo(remoteOrder.getOriginOrderNo())
.setSource(remoteOrder.getSource().getSource())
.setOrderType(remoteOrder.getOrderType().getType())
).stream().findFirst().orElseGet(() -> {
//创建订单
OrderBo bo = new OrderBo()
.setUserId(LoginHelper.getUserId())
.setOrderNo(System.currentTimeMillis() + "")
.setSource(remoteOrder.getSource().getSource())
.setOrderType(remoteOrder.getOrderType().getType())
.setOriginOrderNo(remoteOrder.getOriginOrderNo())
.setOtherInfo(JSONObject.toJSONString(remoteOrder.getOtherInfo()))
.setStatus(OrderStatus.WAIT_PAY.getCode());
orderService.insertByBo(bo);
return MapstructUtils.convert(bo, OrderVo.class);
});
//判断订单是否已支付
if (StringUtils.equals(order.getStatus(), order.getStatus())) {
throw new RuntimeException("订单已支付");
}
//获取订单支付信息
List<OrderFeeVo> feeList = Optional.ofNullable(orderFeeService.queryList(new OrderFeeBo()
.setOrderNo(order.getOrderNo())))
.orElseGet(() -> {
//创建订单费用信息
List<RemoteSaveOrder.Fee> list = remoteOrder.getFeeList();
List<OrderFeeBo> ofList = list.stream().map(fee -> {
return new OrderFeeBo()
.setOrderNo(order.getOrderNo())
.setFeeDesc(fee.getFeeDesc())
.setFeeAmount(fee.getFeeAmount())
.setFeeType(fee.getFeeType().getCode())
.setOtherInfo(fee.getOtherInfo())
.setRemark(fee.getRemark());
}).collect(Collectors.toList());
orderFeeService.batchInsertByList(ofList);
return MapstructUtils.convert(ofList, OrderFeeVo.class);
});
//获取总金额
assert feeList != null;
BigDecimal amount = feeList.stream().map(OrderFeeVo::getFeeAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
//删除历史支付订单
List<Long> tradeIds = StreamUtils.toList(orderTradeService.queryList(new OrderTradeBo().setOrderNo(order.getOrderNo())), OrderTradeVo::getId);
orderTradeService.deleteWithValidByIds(tradeIds, false);
Date expireTime = DateUtil.offsetMinute(new Date(), expireMinute);
//创建支付订单
OrderTradeBo ot = new OrderTradeBo()
.setUserId(order.getUserId())
.setOrderNo(order.getOrderNo())
//生成支付订单号
.setOrderPayNo(System.currentTimeMillis() + "")
.setAmount(amount)
.setPayAmount(amount)
.setActualPayAmount(amount)
.setDiscountAmount(BigDecimal.ZERO)
.setTradeType(remoteOrder.getTradeType().name())
.setExpireTime(expireTime)
.setPayOpenId(LoginHelper.getOpenId());
orderTradeService.insertByBo(ot);
//跟新支付信息
order.setAmount(ot.getAmount());
order.setPayAmount(ot.getPayAmount());
order.setActualPayAmount(ot.getActualPayAmount());
order.setDiscountAmount(ot.getDiscountAmount());
orderService.updateByBo(MapstructUtils.convert(order, OrderBo.class));
//获取支付订单
return IPayStrategy.pay(JSON.toJSONString(ot), feeList, remoteOrder.getTradeType().name());
}
}
package org.dromara.order.service.util;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.order.domain.vo.OrderFeeVo;
import java.util.List;
/**
* 下单策略
*/
public interface IPayStrategy {
String BASE_NAME = "PayStrategy";
/**
* 支付
*
* @param tradeBody 交易对象
* @param feeList 费用集合
* @param tradeType 支付类型
* @return 支付信息
*/
static String pay(String tradeBody, List<OrderFeeVo> feeList, String tradeType) {
// 授权类型和客户端id
String beanName = tradeType + BASE_NAME;
if (!SpringUtils.containsBean(beanName)) {
throw new ServiceException("支付类型不正确!");
}
IPayStrategy instance = SpringUtils.getBean(beanName);
return instance.pay(tradeBody, feeList);
}
/**
* 获得支付信息
*
* @param tradeBody 交易对象
* @param feeList 费用集合
* @return 登录验证信息
*/
String pay(String tradeBody, List<OrderFeeVo> feeList);
}
package org.dromara.order.service.util.impl;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.http.HttpStatus;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.ijpay.core.IJPayHttpResponse;
import com.ijpay.core.enums.RequestMethodEnum;
import com.ijpay.core.kit.PayKit;
import com.ijpay.core.kit.WxPayKit;
import com.ijpay.core.utils.DateTimeZoneUtil;
import com.ijpay.wxpay.WxPayApi;
import com.ijpay.wxpay.enums.WxDomainEnum;
import com.ijpay.wxpay.enums.v3.BasePayApiEnum;
import com.ijpay.wxpay.model.v3.Amount;
import com.ijpay.wxpay.model.v3.Payer;
import com.ijpay.wxpay.model.v3.UnifiedOrderModel;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.order.config.WechatPayConfiguration;
import org.dromara.order.domain.bo.OrderTradeBo;
import org.dromara.order.domain.vo.OrderFeeVo;
import org.dromara.order.service.util.IPayStrategy;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Map;
/**
* 微信公众号支付策略
*
* @author hzh
* @date 2024-12-05
**/
@Slf4j
@Service("JSAPI" + IPayStrategy.BASE_NAME)
@RequiredArgsConstructor
public class JsapiPayStrategy implements IPayStrategy {
private WechatPayConfiguration config;
@Override
public String pay(String tradeBody, List<OrderFeeVo> feeList) {
OrderTradeBo ot = JsonUtils.parseObject(tradeBody, OrderTradeBo.class);
try {
String timeExpire = DateTimeZoneUtil.dateToTimeZone(ot.getExpireTime().getTime());
//获取支付金额
int amount = new BigDecimal("100").multiply(new BigDecimal("100")).intValue();
UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
.setAppid(config.getAppId())
.setMchid(config.getMchId())
.setDescription("测试")
.setOut_trade_no(ot.getOrderPayNo())
.setTime_expire(timeExpire)
.setAttach("")
.setNotify_url(config.getNotify())
.setAmount(new Amount().setTotal(amount))
.setPayer(new Payer().setOpenid(ot.getPayOpenId()));
log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
IJPayHttpResponse response = WxPayApi.v3(
RequestMethodEnum.POST,
WxDomainEnum.CHINA.toString(),
BasePayApiEnum.JS_API_PAY.toString(),
config.getMchId(),
getSerialNumber(),
null,
config.getKeyPath(),
JSONUtil.toJsonStr(unifiedOrderModel));
log.info("微信小程序统一下单响应 {}", response);
// 根据证书序列号查询对应的证书来验证签名结果
boolean verifySignature = WxPayKit.verifySignature(response, config.getPlatformCertPath());
log.info("verifySignature: {}", verifySignature);
if (response.getStatus() == HttpStatus.HTTP_OK && verifySignature) {
String body = response.getBody();
JSONObject jsonObject = JSONUtil.parseObj(body);
String prepayId = jsonObject.getStr("prepay_id");
Map<String, String> map = WxPayKit.jsApiCreateSign(config.getAppId(), prepayId, config.getKeyPath());
return JSONUtil.toJsonStr(map);
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("下单失败");
}
throw new RuntimeException("下单失败");
}
/**
* 商户 API 证书序列号
*/
private String serialNo;
/**
* 获取证书序列号
*
* @return 证书序列号
*/
private String getSerialNumber() {
if (StringUtils.isEmpty(serialNo)) {
// 获取证书序列号
X509Certificate certificate = PayKit.getCertificate(config.getCertPath());
if (null != certificate) {
serialNo = certificate.getSerialNumber().toString(16).toUpperCase();
// 提前两天检查证书是否有效
boolean isValid = PayKit.checkCertificateIsValid(certificate, config.getMchId(), -2);
log.info("证书是否可用 {} 证书有效期为 {}", isValid, DateUtil.format(certificate.getNotAfter(), DatePattern.NORM_DATETIME_PATTERN));
}
}
return serialNo;
}
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论