diff --git a/docs/dev-ops/docker-compose-environment-aliyun.yml b/docs/dev-ops/docker-compose-environment-aliyun.yml index 87bdbe0..65c1595 100644 --- a/docs/dev-ops/docker-compose-environment-aliyun.yml +++ b/docs/dev-ops/docker-compose-environment-aliyun.yml @@ -14,6 +14,7 @@ services: - "13306:3306" volumes: - ./mysql/sql:/docker-entrypoint-initdb.d + - ./mysql/my.cnf:/etc/mysql/conf.d/mysql.cnf:ro healthcheck: test: [ "CMD", "mysqladmin" ,"ping", "-h", "localhost" ] interval: 5s diff --git a/docs/dev-ops/docker-compose-environment.yml b/docs/dev-ops/docker-compose-environment.yml index 2a270b8..82ff19d 100644 --- a/docs/dev-ops/docker-compose-environment.yml +++ b/docs/dev-ops/docker-compose-environment.yml @@ -14,6 +14,7 @@ services: - "13306:3306" volumes: - ./mysql/sql:/docker-entrypoint-initdb.d + - ./mysql/my.cnf:/etc/mysql/conf.d/mysql.cnf:ro healthcheck: test: [ "CMD", "mysqladmin" ,"ping", "-h", "localhost" ] interval: 5s diff --git a/docs/dev-ops/mysql/my.cnf b/docs/dev-ops/mysql/my.cnf new file mode 100644 index 0000000..0768a14 --- /dev/null +++ b/docs/dev-ops/mysql/my.cnf @@ -0,0 +1,24 @@ +[client] +port = 3306 +default-character-set = utf8mb4 + +[mysqld] +user = mysql +port = 3306 +sql_mode = NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES + +default-storage-engine = InnoDB +default-authentication-plugin = mysql_native_password +character-set-server = utf8mb4 +collation-server = utf8mb4_unicode_ci +init_connect = 'SET NAMES utf8mb4' + +slow_query_log +#long_query_time = 3 +slow-query-log-file = /var/log/mysql/mysql.slow.log +log-error = /var/log/mysql/mysql.error.log + +default-time-zone = '+8:00' + +[mysql] +default-character-set = utf8mb4 \ No newline at end of file diff --git a/docs/dev-ops/mysql/sql/0713paymall.sql b/docs/dev-ops/mysql/sql/0713paymall.sql index 4ef4bc0..a7b24d6 100644 --- a/docs/dev-ops/mysql/sql/0713paymall.sql +++ b/docs/dev-ops/mysql/sql/0713paymall.sql @@ -11,7 +11,7 @@ Target Server Version : 80042 File Encoding : 65001 - Date: 13/07/2025 14:21:55 + Date: 15/07/2025 14:36:57 */ SET NAMES utf8mb4; @@ -32,17 +32,19 @@ CREATE TABLE `pay_order` ( `status` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '订单状态;create-创建完成、pay_wait-等待支付、pay_success-支付成功、deal_done-交易完成、close-订单关单', `pay_url` varchar(2014) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '支付信息', `pay_time` datetime NULL DEFAULT NULL COMMENT '支付时间', + `market_type` tinyint(1) NULL DEFAULT NULL COMMENT '营销类型;0无营销、1拼团营销', + `market_deduction_amount` decimal(8, 2) NULL DEFAULT NULL COMMENT '营销金额;优惠金额', + `pay_amount` decimal(8, 2) NOT NULL COMMENT '支付金额', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`) USING BTREE, UNIQUE INDEX `uq_order_id`(`order_id` ASC) USING BTREE, INDEX `idx_user_id_product_id`(`user_id` ASC, `product_id` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; +) ENGINE = InnoDB AUTO_INCREMENT = 11 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of pay_order -- ---------------------------- -INSERT INTO `pay_order` VALUES (5, 'smile01', '10001', '测试商品', '51403944017404', '2025-07-12 05:49:16', 1.68, 'PAY_WAIT', '
\n\n\n
\n', NULL, '2025-07-12 13:49:15', '2025-07-13 14:21:33'); -INSERT INTO `pay_order` VALUES (6, '10001', '10001', '测试商品', '66665553128265', '2025-07-13 06:19:11', 1.68, 'PAY_WAIT', '
\n\n\n
\n', NULL, '2025-07-13 14:19:11', '2025-07-13 14:21:21'); +INSERT INTO `pay_order` VALUES (18, 'smile02', '9890001', 'MyBatisBook', '855240590150', '2025-07-15 06:35:57', 100.00, 'PAY_WAIT', '
\n\n\n
\n', NULL, 1, 20.00, 80.00, '2025-07-15 14:35:56', '2025-07-15 14:35:57'); SET FOREIGN_KEY_CHECKS = 1; diff --git a/pay-mall-api/src/main/java/edu/whut/api/dto/CreatePayRequestDTO.java b/pay-mall-api/src/main/java/edu/whut/api/dto/CreatePayRequestDTO.java index 3473ab6..15c3394 100644 --- a/pay-mall-api/src/main/java/edu/whut/api/dto/CreatePayRequestDTO.java +++ b/pay-mall-api/src/main/java/edu/whut/api/dto/CreatePayRequestDTO.java @@ -9,5 +9,11 @@ public class CreatePayRequestDTO { private String userId; // 产品编号 private String productId; + // 拼团队伍 - 队伍ID + private String teamId; + // 活动ID,来自于页面调用拼团试算后,获得的活动ID信息 + private Long activityId; + // 营销类型 - 0无营销 + private Integer marketType = 0; } diff --git a/pay-mall-app/src/main/java/edu/whut/config/Retrofit2Config.java b/pay-mall-app/src/main/java/edu/whut/config/Retrofit2Config.java index 7fe223c..cc8f328 100644 --- a/pay-mall-app/src/main/java/edu/whut/config/Retrofit2Config.java +++ b/pay-mall-app/src/main/java/edu/whut/config/Retrofit2Config.java @@ -1,7 +1,9 @@ package edu.whut.config; +import edu.whut.infrastructure.gateway.IGroupBuyMarketService; import edu.whut.infrastructure.gateway.IWeixinApiService; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import retrofit2.Retrofit; @@ -14,25 +16,29 @@ public class Retrofit2Config { // 微信开放平台的基础 URL,后续所有接口都会在这个前缀下拼接路径 private static final String BASE_URL = "https://api.weixin.qq.com/"; - /** - * 创建一个 Retrofit 对象,并注册为 Spring Bean - * - baseUrl:设置所有请求的公共前缀 - * - addConverterFactory:添加 Jackson 转换器,用于 JSON <-> Java 对象的自动映射 - */ - @Bean - public Retrofit retrofit() { - return new Retrofit.Builder() - .baseUrl(BASE_URL) - .addConverterFactory(JacksonConverterFactory.create()).build(); - } + @Value("${app.config.group-buy-market.api-url}") + private String groupBuyMarketApiUrl; + /** * 通过 Retrofit 动态生成 IWeixinApiService 接口的实现, * 并注册为 Spring 容器中的 Bean,方便业务层直接注入使用 */ @Bean - public IWeixinApiService weixinApiService(Retrofit retrofit) { + public IWeixinApiService weixinApiService() { + Retrofit retrofit = new Retrofit.Builder() + .baseUrl(BASE_URL) + .addConverterFactory(JacksonConverterFactory.create()).build(); return retrofit.create(IWeixinApiService.class); } + @Bean + public IGroupBuyMarketService groupBuyMarketService() { + Retrofit retrofit = new Retrofit.Builder() + .baseUrl(groupBuyMarketApiUrl) + .addConverterFactory(JacksonConverterFactory.create()).build(); + + return retrofit.create(IGroupBuyMarketService.class); + } + } diff --git a/pay-mall-app/src/main/resources/application-dev.yml b/pay-mall-app/src/main/resources/application-dev.yml index 617cb4d..7675090 100644 --- a/pay-mall-app/src/main/resources/application-dev.yml +++ b/pay-mall-app/src/main/resources/application-dev.yml @@ -1,6 +1,20 @@ server: port: 8092 +# 应用配置 +app: + config: + # 版本,方便通过接口版本升级 + api-version: v1 + # 跨域,开发阶段可以设置为 * 不限制 + cross-origin: '*' + # SC 渠道配置 - 拼团对接渠道值、回调通知 + group-buy-market: + api-url: http://127.0.0.1:8091 + notify-url: http://127.0.0.1:8091/api/v1/test/group_buy_notify + source: s01 + chanel: c01 + # 线程池配置 thread: pool: diff --git a/pay-mall-app/src/main/resources/mybatis/mapper/pay_order_mapper.xml b/pay-mall-app/src/main/resources/mybatis/mapper/pay_order_mapper.xml index a0ef541..9042ba3 100644 --- a/pay-mall-app/src/main/resources/mybatis/mapper/pay_order_mapper.xml +++ b/pay-mall-app/src/main/resources/mybatis/mapper/pay_order_mapper.xml @@ -13,15 +13,18 @@ + + + insert into pay_order(user_id, product_id, product_name, order_id, order_time, - total_amount, status, create_time, update_time) + total_amount, status, market_type, market_deduction_amount, pay_amount, create_time, update_time) values(#{userId}, #{productId}, #{productName}, #{orderId}, #{orderTime}, - #{totalAmount}, #{status}, now(), now()) + #{totalAmount}, #{status}, #{marketType}, #{marketDeductionAmount}, #{payAmount}, now(), now()) - update pay_order set pay_url = #{payUrl}, status = #{status}, update_time = now() + update pay_order set pay_url = #{payUrl}, status = #{status}, + market_type = #{marketType}, market_deduction_amount = #{marketDeductionAmount}, pay_amount = #{payAmount}, + update_time = now() where order_id = #{orderId} diff --git a/pay-mall-app/src/test/java/edu/whut/test/domain/OrderServiceTest.java b/pay-mall-app/src/test/java/edu/whut/test/domain/OrderServiceTest.java index 45d147c..063d2ce 100644 --- a/pay-mall-app/src/test/java/edu/whut/test/domain/OrderServiceTest.java +++ b/pay-mall-app/src/test/java/edu/whut/test/domain/OrderServiceTest.java @@ -2,6 +2,7 @@ package edu.whut.test.domain; import com.alibaba.fastjson.JSON; import edu.whut.domain.order.model.entity.PayOrderEntity; import edu.whut.domain.order.model.entity.ShopCartEntity; +import edu.whut.domain.order.model.valobj.MarketTypeVO; import edu.whut.domain.order.service.IOrderService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -15,16 +16,20 @@ import javax.annotation.Resource; @Slf4j @RunWith(SpringRunner.class) @SpringBootTest -@RequiredArgsConstructor public class OrderServiceTest { - private final IOrderService orderService; + @Resource // 或者 @Autowired + private IOrderService orderService; @Test public void test_createOrder() throws Exception { ShopCartEntity shopCartEntity = new ShopCartEntity(); - shopCartEntity.setUserId("smile01"); - shopCartEntity.setProductId("10001"); + shopCartEntity.setUserId("smile02"); + shopCartEntity.setProductId("9890001"); + shopCartEntity.setTeamId(null); + shopCartEntity.setActivityId(100123L); + shopCartEntity.setMarketTypeVO(MarketTypeVO.GROUP_BUY_MARKET); + PayOrderEntity payOrderEntity = orderService.createOrder(shopCartEntity); log.info("请求参数:{}", JSON.toJSONString(shopCartEntity)); log.info("测试结果:{}", JSON.toJSONString(payOrderEntity)); diff --git a/pay-mall-domain/src/main/java/edu/whut/domain/order/adapter/port/IProductPort.java b/pay-mall-domain/src/main/java/edu/whut/domain/order/adapter/port/IProductPort.java index 33bc1a3..3fd0e55 100644 --- a/pay-mall-domain/src/main/java/edu/whut/domain/order/adapter/port/IProductPort.java +++ b/pay-mall-domain/src/main/java/edu/whut/domain/order/adapter/port/IProductPort.java @@ -1,9 +1,13 @@ package edu.whut.domain.order.adapter.port; +import edu.whut.domain.order.model.entity.MarketPayDiscountEntity; import edu.whut.domain.order.model.entity.ProductEntity; public interface IProductPort { ProductEntity queryProductByProductId(String productId); + MarketPayDiscountEntity lockMarketPayOrder(String userId, String teamId, Long activityId, String productId, String orderId); + + } diff --git a/pay-mall-domain/src/main/java/edu/whut/domain/order/model/aggregate/CreateOrderAggregate.java b/pay-mall-domain/src/main/java/edu/whut/domain/order/model/aggregate/CreateOrderAggregate.java index e34120a..0535bcd 100644 --- a/pay-mall-domain/src/main/java/edu/whut/domain/order/model/aggregate/CreateOrderAggregate.java +++ b/pay-mall-domain/src/main/java/edu/whut/domain/order/model/aggregate/CreateOrderAggregate.java @@ -22,13 +22,14 @@ public class CreateOrderAggregate { private OrderEntity orderEntity; - public static OrderEntity buildOrderEntity(String productId, String productName){ + public static OrderEntity buildOrderEntity(String productId, String productName, Integer marketType){ return OrderEntity.builder() .productId(productId) .productName(productName) - .orderId(RandomStringUtils.randomNumeric(14)) + .orderId(RandomStringUtils.randomNumeric(12)) .orderTime(new Date()) .orderStatusVO(OrderStatusVO.CREATE) + .marketType(marketType) .build(); } diff --git a/pay-mall-domain/src/main/java/edu/whut/domain/order/model/entity/MarketPayDiscountEntity.java b/pay-mall-domain/src/main/java/edu/whut/domain/order/model/entity/MarketPayDiscountEntity.java new file mode 100644 index 0000000..da118f3 --- /dev/null +++ b/pay-mall-domain/src/main/java/edu/whut/domain/order/model/entity/MarketPayDiscountEntity.java @@ -0,0 +1,26 @@ +package edu.whut.domain.order.model.entity; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; + +/** + * 营销支付优惠 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class MarketPayDiscountEntity { + + /** 原始价格 */ + private BigDecimal originalPrice; + /** 折扣金额 */ + private BigDecimal deductionPrice; + /** 支付金额 */ + private BigDecimal payPrice; + +} diff --git a/pay-mall-domain/src/main/java/edu/whut/domain/order/model/entity/OrderEntity.java b/pay-mall-domain/src/main/java/edu/whut/domain/order/model/entity/OrderEntity.java index bb3fb2c..4ecdfcb 100644 --- a/pay-mall-domain/src/main/java/edu/whut/domain/order/model/entity/OrderEntity.java +++ b/pay-mall-domain/src/main/java/edu/whut/domain/order/model/entity/OrderEntity.java @@ -21,5 +21,11 @@ public class OrderEntity { private BigDecimal totalAmount; private OrderStatusVO orderStatusVO; private String payUrl; + // 营销类型;0无营销、1拼团营销 + private Integer marketType; + // 营销金额;优惠金额 + private BigDecimal marketDeductionAmount; + // 支付金额 + private BigDecimal payAmount; } diff --git a/pay-mall-domain/src/main/java/edu/whut/domain/order/model/entity/PayOrderEntity.java b/pay-mall-domain/src/main/java/edu/whut/domain/order/model/entity/PayOrderEntity.java index ded0f1a..7a9246d 100644 --- a/pay-mall-domain/src/main/java/edu/whut/domain/order/model/entity/PayOrderEntity.java +++ b/pay-mall-domain/src/main/java/edu/whut/domain/order/model/entity/PayOrderEntity.java @@ -5,6 +5,8 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import java.math.BigDecimal; + @Data @Builder @AllArgsConstructor @@ -16,4 +18,11 @@ public class PayOrderEntity { private String payUrl; private OrderStatusVO orderStatus; + // 营销类型;0无营销、1拼团营销 + private Integer marketType; + // 营销金额;优惠金额 + private BigDecimal marketDeductionAmount; + // 支付金额 + private BigDecimal payAmount; + } diff --git a/pay-mall-domain/src/main/java/edu/whut/domain/order/model/entity/ShopCartEntity.java b/pay-mall-domain/src/main/java/edu/whut/domain/order/model/entity/ShopCartEntity.java index 6dd7f1a..fbdd3ab 100644 --- a/pay-mall-domain/src/main/java/edu/whut/domain/order/model/entity/ShopCartEntity.java +++ b/pay-mall-domain/src/main/java/edu/whut/domain/order/model/entity/ShopCartEntity.java @@ -1,5 +1,6 @@ package edu.whut.domain.order.model.entity; +import edu.whut.domain.order.model.valobj.MarketTypeVO; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -11,8 +12,19 @@ import lombok.NoArgsConstructor; @NoArgsConstructor public class ShopCartEntity { + // 用户ID private String userId; + // 商品ID private String productId; + // 拼团组队ID,可为空,为空的时,则为用户首次创建拼团 + private String teamId; + + // 活动ID,来自于页面调用拼团试算后,获得的活动ID信息 + private Long activityId; + + // 营销类型,无营销,拼团营销 + private MarketTypeVO marketTypeVO; + } diff --git a/pay-mall-domain/src/main/java/edu/whut/domain/order/model/valobj/MarketTypeVO.java b/pay-mall-domain/src/main/java/edu/whut/domain/order/model/valobj/MarketTypeVO.java new file mode 100644 index 0000000..0e9ab1e --- /dev/null +++ b/pay-mall-domain/src/main/java/edu/whut/domain/order/model/valobj/MarketTypeVO.java @@ -0,0 +1,32 @@ +package edu.whut.domain.order.model.valobj; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 营销类型 + */ +@Getter +@AllArgsConstructor +public enum MarketTypeVO { + + + NO_MARKET(0, "无营销"), + GROUP_BUY_MARKET(1, "拼团营销"), + ; + + private final Integer code; + private final String desc; + + + public static MarketTypeVO valueOf(Integer code) { + switch (code) { + case 0: + return NO_MARKET; + case 1: + return GROUP_BUY_MARKET; + } + throw new RuntimeException("err code not exist!"); + } + +} diff --git a/pay-mall-domain/src/main/java/edu/whut/domain/order/service/AbstractOrderService.java b/pay-mall-domain/src/main/java/edu/whut/domain/order/service/AbstractOrderService.java index 78ae7ec..0d38bad 100644 --- a/pay-mall-domain/src/main/java/edu/whut/domain/order/service/AbstractOrderService.java +++ b/pay-mall-domain/src/main/java/edu/whut/domain/order/service/AbstractOrderService.java @@ -4,10 +4,8 @@ import com.alipay.api.AlipayApiException; import edu.whut.domain.order.adapter.port.IProductPort; import edu.whut.domain.order.adapter.repository.IOrderRepository; import edu.whut.domain.order.model.aggregate.CreateOrderAggregate; -import edu.whut.domain.order.model.entity.OrderEntity; -import edu.whut.domain.order.model.entity.PayOrderEntity; -import edu.whut.domain.order.model.entity.ProductEntity; -import edu.whut.domain.order.model.entity.ShopCartEntity; +import edu.whut.domain.order.model.entity.*; +import edu.whut.domain.order.model.valobj.MarketTypeVO; import edu.whut.domain.order.model.valobj.OrderStatusVO; import lombok.extern.slf4j.Slf4j; @@ -32,44 +30,86 @@ public abstract class AbstractOrderService implements IOrderService { // 1. 查询当前用户是否存在掉单和未支付订单 OrderEntity unpaidOrderEntity = repository.queryUnPayOrder(shopCartEntity); - // 如果已有订单正在等待支付,直接复用 - if (null != unpaidOrderEntity && OrderStatusVO.PAY_WAIT.equals(unpaidOrderEntity.getOrderStatusVO())) { - log.info("创建订单-存在,已存在未支付订单。userId:{} productId:{} orderId:{}", shopCartEntity.getUserId(), shopCartEntity.getProductId(), unpaidOrderEntity.getOrderId()); + // 如果已有未支付订单且状态为支付等待,则直接复用 + if (unpaidOrderEntity != null && OrderStatusVO.PAY_WAIT.equals(unpaidOrderEntity.getOrderStatusVO())) { + log.info("创建订单-存在,已存在未支付订单。userId:{} productId:{} orderId:{}", + shopCartEntity.getUserId(), shopCartEntity.getProductId(), unpaidOrderEntity.getOrderId()); return PayOrderEntity.builder() .orderId(unpaidOrderEntity.getOrderId()) .payUrl(unpaidOrderEntity.getPayUrl()) .build(); - } else if (null != unpaidOrderEntity && OrderStatusVO.CREATE.equals(unpaidOrderEntity.getOrderStatusVO())) { - log.info("创建订单-存在,存在未创建'支付单'订单,创建支付单开始 userId:{} productId:{} orderId:{}", shopCartEntity.getUserId(), shopCartEntity.getProductId(), unpaidOrderEntity.getOrderId()); - PayOrderEntity payOrderEntity = doPrepayOrder(shopCartEntity.getUserId(), shopCartEntity.getProductId(), unpaidOrderEntity.getProductName(), unpaidOrderEntity.getOrderId(), unpaidOrderEntity.getTotalAmount()); + // 如果已有订单仅创建了记录但未生成支付单,则生成支付单 + } else if (unpaidOrderEntity != null && OrderStatusVO.CREATE.equals(unpaidOrderEntity.getOrderStatusVO())) { + log.info("创建订单-存在,存在未创建支付单订单,创建支付单开始 userId:{} productId:{} orderId:{}", + shopCartEntity.getUserId(), shopCartEntity.getProductId(), unpaidOrderEntity.getOrderId()); + Integer marketType = unpaidOrderEntity.getMarketType(); + BigDecimal marketDeductionAmount = unpaidOrderEntity.getMarketDeductionAmount(); + PayOrderEntity payOrderEntity; + + if (MarketTypeVO.GROUP_BUY_MARKET.getCode().equals(marketType) && marketDeductionAmount == null) { + MarketPayDiscountEntity discount = lockMarketPayOrder( + shopCartEntity.getUserId(), shopCartEntity.getTeamId(), + shopCartEntity.getActivityId(), shopCartEntity.getProductId(), + unpaidOrderEntity.getOrderId()); + payOrderEntity = doPrepayOrder( + shopCartEntity.getUserId(), shopCartEntity.getProductId(), + unpaidOrderEntity.getProductName(), unpaidOrderEntity.getOrderId(), + unpaidOrderEntity.getTotalAmount(), discount); + } else if (MarketTypeVO.GROUP_BUY_MARKET.getCode().equals(marketType)) { + payOrderEntity = doPrepayOrder( + shopCartEntity.getUserId(), shopCartEntity.getProductId(), + unpaidOrderEntity.getProductName(), unpaidOrderEntity.getOrderId(), + unpaidOrderEntity.getPayAmount()); + } else { + payOrderEntity = doPrepayOrder( + shopCartEntity.getUserId(), shopCartEntity.getProductId(), + unpaidOrderEntity.getProductName(), unpaidOrderEntity.getOrderId(), + unpaidOrderEntity.getTotalAmount()); + } + return PayOrderEntity.builder() .orderId(payOrderEntity.getOrderId()) .payUrl(payOrderEntity.getPayUrl()) .build(); } - // 2. 调用产品服务,查询商品详细信息 + // 2. 查询商品信息 ProductEntity productEntity = port.queryProductByProductId(shopCartEntity.getProductId()); - // 3. 构建基础 OrderEntity(含 orderId、时间等) - OrderEntity orderEntity = CreateOrderAggregate.buildOrderEntity(productEntity.getProductId(), productEntity.getProductName()); - - // 4. 组装聚合根:聚合中包含用户、商品和待持久化的订单实体 + // 3. 构建新的订单实体,并保存 + OrderEntity orderEntity = CreateOrderAggregate.buildOrderEntity( + productEntity.getProductId(), + productEntity.getProductName(), + shopCartEntity.getMarketTypeVO().getCode()); CreateOrderAggregate orderAggregate = CreateOrderAggregate.builder() .userId(shopCartEntity.getUserId()) .productEntity(productEntity) .orderEntity(orderEntity) .build(); - - // 5. 交由子类实现,保存订单聚合 this.doSaveOrder(orderAggregate); - PayOrderEntity payOrderEntity = doPrepayOrder(shopCartEntity.getUserId(), productEntity.getProductId(), productEntity.getProductName(), orderEntity.getOrderId(), productEntity.getPrice()); - log.info("创建订单-完成,生成支付单。userId: {} orderId: {} payUrl: {}", shopCartEntity.getUserId(), orderEntity.getOrderId(), payOrderEntity.getPayUrl()); + // 4. 如果是拼团,发起营销锁单 + MarketPayDiscountEntity marketPayDiscountEntity = null; + if (MarketTypeVO.GROUP_BUY_MARKET.equals(shopCartEntity.getMarketTypeVO())) { + marketPayDiscountEntity = this.lockMarketPayOrder( + shopCartEntity.getUserId(), shopCartEntity.getTeamId(), + shopCartEntity.getActivityId(), shopCartEntity.getProductId(), + orderEntity.getOrderId()); + } + // 5. 创建支付订单 + PayOrderEntity payOrderEntity = doPrepayOrder( + shopCartEntity.getUserId(), + productEntity.getProductId(), + productEntity.getProductName(), + orderEntity.getOrderId(), + productEntity.getPrice(), + marketPayDiscountEntity); + + log.info("创建订单-完成,生成支付单。userId: {} orderId: {} payUrl: {}", + shopCartEntity.getUserId(), orderEntity.getOrderId(), payOrderEntity.getPayUrl()); - // 6. 返回支付实体 return PayOrderEntity.builder() .orderId(orderEntity.getOrderId()) .payUrl(payOrderEntity.getPayUrl()) @@ -81,5 +121,10 @@ public abstract class AbstractOrderService implements IOrderService { */ protected abstract void doSaveOrder(CreateOrderAggregate orderAggregate); + protected abstract MarketPayDiscountEntity lockMarketPayOrder(String userId, String teamId, Long activityId, String productId, String orderId); + protected abstract PayOrderEntity doPrepayOrder(String userId, String productId, String productName, String orderId, BigDecimal totalAmount) throws AlipayApiException; + + protected abstract PayOrderEntity doPrepayOrder(String userId, String productId, String productName, String orderId, BigDecimal totalAmount, MarketPayDiscountEntity marketPayDiscountEntity) throws AlipayApiException; + } diff --git a/pay-mall-domain/src/main/java/edu/whut/domain/order/service/OrderService.java b/pay-mall-domain/src/main/java/edu/whut/domain/order/service/OrderService.java index 584a055..6335b3b 100644 --- a/pay-mall-domain/src/main/java/edu/whut/domain/order/service/OrderService.java +++ b/pay-mall-domain/src/main/java/edu/whut/domain/order/service/OrderService.java @@ -6,7 +6,9 @@ import com.alipay.api.request.AlipayTradePagePayRequest; import edu.whut.domain.order.adapter.port.IProductPort; import edu.whut.domain.order.adapter.repository.IOrderRepository; import edu.whut.domain.order.model.aggregate.CreateOrderAggregate; +import edu.whut.domain.order.model.entity.MarketPayDiscountEntity; import edu.whut.domain.order.model.entity.PayOrderEntity; +import edu.whut.domain.order.model.valobj.MarketTypeVO; import edu.whut.domain.order.model.valobj.OrderStatusVO; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; @@ -37,18 +39,31 @@ public class OrderService extends AbstractOrderService{ repository.doSaveOrder(orderAggregate); } + @Override + protected MarketPayDiscountEntity lockMarketPayOrder(String userId, String teamId, Long activityId, String productId, String orderId) { + return port.lockMarketPayOrder(userId, teamId, activityId, productId, orderId); + } + /** * 预支付订单 */ @Override protected PayOrderEntity doPrepayOrder(String userId, String productId, String productName, String orderId, BigDecimal totalAmount) throws AlipayApiException { + return doPrepayOrder(userId, productId, productName, orderId, totalAmount, null); + } + + @Override + protected PayOrderEntity doPrepayOrder(String userId, String productId, String productName, String orderId, BigDecimal totalAmount, MarketPayDiscountEntity marketPayDiscountEntity) throws AlipayApiException { + // 支付金额 + BigDecimal payAmount = null == marketPayDiscountEntity ? totalAmount : marketPayDiscountEntity.getPayPrice(); + AlipayTradePagePayRequest request = new AlipayTradePagePayRequest(); request.setNotifyUrl(notifyUrl); request.setReturnUrl(returnUrl); JSONObject bizContent = new JSONObject(); bizContent.put("out_trade_no", orderId); - bizContent.put("total_amount", totalAmount.toString()); + bizContent.put("total_amount", payAmount); bizContent.put("subject", productName); bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY"); request.setBizContent(bizContent.toString()); @@ -58,9 +73,13 @@ public class OrderService extends AbstractOrderService{ PayOrderEntity payOrderEntity = new PayOrderEntity(); payOrderEntity.setOrderId(orderId); payOrderEntity.setPayUrl(form); - //等待支付 payOrderEntity.setOrderStatus(OrderStatusVO.PAY_WAIT); + // 营销信息 + payOrderEntity.setMarketType(null == marketPayDiscountEntity ? MarketTypeVO.NO_MARKET.getCode() : MarketTypeVO.GROUP_BUY_MARKET.getCode()); + payOrderEntity.setMarketDeductionAmount(null == marketPayDiscountEntity ? BigDecimal.ZERO : marketPayDiscountEntity.getDeductionPrice()); + payOrderEntity.setPayAmount(payAmount); + repository.updateOrderPayInfo(payOrderEntity); return payOrderEntity; diff --git a/pay-mall-infrastructure/pom.xml b/pay-mall-infrastructure/pom.xml index 51919a5..293f11c 100644 --- a/pay-mall-infrastructure/pom.xml +++ b/pay-mall-infrastructure/pom.xml @@ -30,6 +30,11 @@ com.squareup.retrofit2 adapter-rxjava2 + + edu.whut + group-buying-sys-api + 1.0-SNAPSHOT + edu.whut diff --git a/pay-mall-infrastructure/src/main/java/edu/whut/infrastructure/adapter/port/ProductPort.java b/pay-mall-infrastructure/src/main/java/edu/whut/infrastructure/adapter/port/ProductPort.java index ac6b61e..960e23a 100644 --- a/pay-mall-infrastructure/src/main/java/edu/whut/infrastructure/adapter/port/ProductPort.java +++ b/pay-mall-infrastructure/src/main/java/edu/whut/infrastructure/adapter/port/ProductPort.java @@ -1,29 +1,97 @@ package edu.whut.infrastructure.adapter.port; +import com.alibaba.fastjson.JSON; +import edu.whut.api.dto.LockMarketPayOrderRequestDTO; +import edu.whut.api.dto.LockMarketPayOrderResponseDTO; +import edu.whut.api.response.Response; import edu.whut.domain.order.adapter.port.IProductPort; +import edu.whut.domain.order.model.entity.MarketPayDiscountEntity; import edu.whut.domain.order.model.entity.ProductEntity; +import edu.whut.infrastructure.gateway.IGroupBuyMarketService; import edu.whut.infrastructure.gateway.ProductRPC; import edu.whut.infrastructure.gateway.dto.ProductDTO; +import edu.whut.types.exception.AppException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +import retrofit2.Call; @Component +@RequiredArgsConstructor +@Slf4j public class ProductPort implements IProductPort { + @Value("${app.config.group-buy-market.source}") + private String source; + @Value("${app.config.group-buy-market.chanel}") + private String chanel; + @Value("${app.config.group-buy-market.notify-url}") + private String notifyUrl; + + private final IGroupBuyMarketService groupBuyMarketService; + private final ProductRPC productRPC; - public ProductPort(ProductRPC productRPC) { - this.productRPC = productRPC; - } + /** + * 根据产品ID查询基础商品信息 + */ @Override public ProductEntity queryProductByProductId(String productId) { - ProductDTO productDTO = productRPC.queryProductByProductId(productId); + // 调用远程产品服务获取 DTO + ProductDTO dto = productRPC.queryProductByProductId(productId); + // 转换为领域实体并返回 return ProductEntity.builder() - .productId(productDTO.getProductId()) - .productName(productDTO.getProductName()) - .productDesc(productDTO.getProductDesc()) - .price(productDTO.getPrice()) + .productId(dto.getProductId()) + .productName(dto.getProductName()) + .productDesc(dto.getProductDesc()) + .price(dto.getPrice()) .build(); } + /** + * 发起营销锁单请求,获取拼团优惠信息 + */ + @Override + public MarketPayDiscountEntity lockMarketPayOrder(String userId, String teamId, Long activityId, String productId, String orderId) { + // 请求参数 + LockMarketPayOrderRequestDTO requestDTO = new LockMarketPayOrderRequestDTO(); + requestDTO.setUserId(userId); + requestDTO.setTeamId(teamId); + requestDTO.setGoodsId(productId); + requestDTO.setActivityId(activityId); + requestDTO.setSource(source); + requestDTO.setChannel(chanel); + requestDTO.setOutTradeNo(orderId); + requestDTO.setNotifyUrl(notifyUrl); + + try { + // 发起 HTTP 请求,执行营销锁单 + Call> call = groupBuyMarketService.lockMarketPayOrder(requestDTO); + + // 获取结果 + Response response = call.execute().body(); + log.info("营销锁单{} requestDTO:{} responseDTO:{}", userId, JSON.toJSONString(requestDTO), JSON.toJSONString(response)); + if (null == response) return null; + + // 异常判断 + if (!"0000".equals(response.getCode())){ + throw new AppException(response.getCode(), response.getInfo()); + } + + LockMarketPayOrderResponseDTO responseDTO = response.getData(); + + // 获取拼团优惠 + return MarketPayDiscountEntity.builder() + .originalPrice(responseDTO.getOriginalPrice()) + .deductionPrice(responseDTO.getDeductionPrice()) + .payPrice(responseDTO.getPayPrice()) + .build(); + } catch (Exception e) { + log.error("营销锁单失败{}", userId, e); + return null; + } + } + } diff --git a/pay-mall-infrastructure/src/main/java/edu/whut/infrastructure/adapter/repository/OrderRepository.java b/pay-mall-infrastructure/src/main/java/edu/whut/infrastructure/adapter/repository/OrderRepository.java index 3fb327b..5db7252 100644 --- a/pay-mall-infrastructure/src/main/java/edu/whut/infrastructure/adapter/repository/OrderRepository.java +++ b/pay-mall-infrastructure/src/main/java/edu/whut/infrastructure/adapter/repository/OrderRepository.java @@ -8,6 +8,7 @@ import edu.whut.domain.order.model.entity.OrderEntity; import edu.whut.domain.order.model.entity.PayOrderEntity; import edu.whut.domain.order.model.entity.ProductEntity; import edu.whut.domain.order.model.entity.ShopCartEntity; +import edu.whut.domain.order.model.valobj.MarketTypeVO; import edu.whut.domain.order.model.valobj.OrderStatusVO; import edu.whut.infrastructure.dao.IOrderDao; import edu.whut.infrastructure.dao.po.PayOrder; @@ -16,6 +17,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; import javax.annotation.Resource; +import java.math.BigDecimal; import java.util.List; @Repository @@ -46,6 +48,10 @@ public class OrderRepository implements IOrderRepository { order.setOrderTime(orderEntity.getOrderTime()); order.setTotalAmount(productEntity.getPrice()); order.setStatus(orderEntity.getOrderStatusVO().getCode()); + order.setMarketType(MarketTypeVO.NO_MARKET.getCode()); + order.setMarketDeductionAmount(BigDecimal.ZERO); + order.setPayAmount(productEntity.getPrice()); + order.setMarketType(orderEntity.getMarketType()); // 插入数据库 orderDao.insert(order); @@ -74,6 +80,9 @@ public class OrderRepository implements IOrderRepository { .orderTime(order.getOrderTime()) .totalAmount(order.getTotalAmount()) .payUrl(order.getPayUrl()) + .marketType(order.getMarketType()) + .marketDeductionAmount(order.getMarketDeductionAmount()) + .payAmount(order.getPayAmount()) .build(); } @@ -88,6 +97,9 @@ public class OrderRepository implements IOrderRepository { .orderId(payOrderEntity.getOrderId()) .status(payOrderEntity.getOrderStatus().getCode()) .payUrl(payOrderEntity.getPayUrl()) + .marketType(payOrderEntity.getMarketType()) + .marketDeductionAmount(payOrderEntity.getMarketDeductionAmount()) + .payAmount(payOrderEntity.getPayAmount()) .build(); orderDao.updateOrderPayInfo(payOrderReq); } diff --git a/pay-mall-infrastructure/src/main/java/edu/whut/infrastructure/dao/po/PayOrder.java b/pay-mall-infrastructure/src/main/java/edu/whut/infrastructure/dao/po/PayOrder.java index 0d0002a..0dd7021 100644 --- a/pay-mall-infrastructure/src/main/java/edu/whut/infrastructure/dao/po/PayOrder.java +++ b/pay-mall-infrastructure/src/main/java/edu/whut/infrastructure/dao/po/PayOrder.java @@ -14,17 +14,35 @@ import java.util.Date; @NoArgsConstructor public class PayOrder { + // 自增ID private Long id; + // 用户ID private String userId; + // 商品ID private String productId; + // 商品名称 private String productName; + // 订单ID private String orderId; + // 下单时间 private Date orderTime; + // 订单金额 private BigDecimal totalAmount; + // 订单状态;create-创建完成、pay_wait-等待支付、pay_success-支付成功、deal_done-交易完成、close-订单关单 private String status; + // 支付信息 private String payUrl; + // 支付时间 private Date payTime; + // 营销类型;0无营销、1拼团营销 + private Integer marketType; + // 营销金额;优惠金额 + private BigDecimal marketDeductionAmount; + // 支付金额 + private BigDecimal payAmount; + // 创建时间 private Date createTime; + // 更新时间 private Date updateTime; } diff --git a/pay-mall-infrastructure/src/main/java/edu/whut/infrastructure/gateway/IGroupBuyMarketService.java b/pay-mall-infrastructure/src/main/java/edu/whut/infrastructure/gateway/IGroupBuyMarketService.java new file mode 100644 index 0000000..24f4388 --- /dev/null +++ b/pay-mall-infrastructure/src/main/java/edu/whut/infrastructure/gateway/IGroupBuyMarketService.java @@ -0,0 +1,24 @@ +package edu.whut.infrastructure.gateway; + +import edu.whut.api.dto.LockMarketPayOrderRequestDTO; +import edu.whut.api.dto.LockMarketPayOrderResponseDTO; +import edu.whut.api.response.Response; +import retrofit2.Call; +import retrofit2.http.Body; +import retrofit2.http.POST; + +/** + * 拼团营销 + */ +public interface IGroupBuyMarketService { + + /** + * 营销锁单 + * + * @param requestDTO 锁单商品信息 + * @return 锁单结果信息 + */ + @POST("api/v1/gbm/trade/lock_market_pay_order") + Call> lockMarketPayOrder(@Body LockMarketPayOrderRequestDTO requestDTO); + +} diff --git a/pay-mall-infrastructure/src/main/java/edu/whut/infrastructure/gateway/ProductRPC.java b/pay-mall-infrastructure/src/main/java/edu/whut/infrastructure/gateway/ProductRPC.java index 069a43e..ac1b7ad 100644 --- a/pay-mall-infrastructure/src/main/java/edu/whut/infrastructure/gateway/ProductRPC.java +++ b/pay-mall-infrastructure/src/main/java/edu/whut/infrastructure/gateway/ProductRPC.java @@ -14,9 +14,9 @@ public class ProductRPC { public ProductDTO queryProductByProductId(String productId){ ProductDTO productVO = new ProductDTO(); productVO.setProductId(productId); - productVO.setProductName("测试商品"); - productVO.setProductDesc("这是一个测试商品"); - productVO.setPrice(new BigDecimal("1.68")); + productVO.setProductName("MyBatisBook"); + productVO.setProductDesc("MyBatisBook"); + productVO.setPrice(new BigDecimal("100.00")); return productVO; } diff --git a/pay-mall-trigger/src/main/java/edu/whut/trigger/http/AliPayController.java b/pay-mall-trigger/src/main/java/edu/whut/trigger/http/AliPayController.java index 62bff4f..347cdd1 100644 --- a/pay-mall-trigger/src/main/java/edu/whut/trigger/http/AliPayController.java +++ b/pay-mall-trigger/src/main/java/edu/whut/trigger/http/AliPayController.java @@ -6,14 +6,13 @@ import edu.whut.api.dto.CreatePayRequestDTO; import edu.whut.api.response.Response; import edu.whut.domain.order.model.entity.PayOrderEntity; import edu.whut.domain.order.model.entity.ShopCartEntity; +import edu.whut.domain.order.model.valobj.MarketTypeVO; import edu.whut.domain.order.service.IOrderService; import edu.whut.types.common.Constants; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.*; - -import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import java.util.HashMap; import java.util.Map; @@ -43,10 +42,14 @@ public class AliPayController implements IPayService { log.info("商品下单,根据商品ID创建支付单开始 userId:{} productId:{}", createPayRequestDTO.getUserId(), createPayRequestDTO.getUserId()); String userId = createPayRequestDTO.getUserId(); String productId = createPayRequestDTO.getProductId(); + String teamId = createPayRequestDTO.getTeamId(); + Integer marketType = createPayRequestDTO.getMarketType(); // 下单 PayOrderEntity payOrderEntity = orderService.createOrder(ShopCartEntity.builder() .userId(userId) .productId(productId) + .teamId(teamId) + .marketTypeVO(MarketTypeVO.valueOf(marketType)) .build()); log.info("商品下单,根据商品ID创建支付单完成 userId:{} productId:{} orderId:{}", userId, productId, payOrderEntity.getOrderId());