order.mt.service.ts 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583
  1. import { GenericCrudService } from '@d8d/shared-crud';
  2. import { DataSource, Repository, In } from 'typeorm';
  3. import { OrderMt } from '../entities/order.mt.entity';
  4. import { OrderGoodsMt } from '../entities/order-goods.mt.entity';
  5. import { OrderRefundMt } from '../entities/order-refund.mt.entity';
  6. import { GoodsMt } from '@d8d/goods-module-mt';
  7. import { DeliveryAddressMt } from '@d8d/delivery-address-module-mt';
  8. import { PaymentMtService } from '@d8d/mini-payment-mt';
  9. import { CreditBalanceService } from '@d8d/credit-balance-module-mt';
  10. import { PaymentMtEntity, PaymentStatus } from '@d8d/mini-payment-mt';
  11. import type { CreateOrderRequest } from '../schemas/create-order.schema';
  12. import { createServiceLogger } from '@d8d/shared-utils';
  13. export class OrderMtService extends GenericCrudService<OrderMt> {
  14. private orderGoodsRepository: Repository<OrderGoodsMt>;
  15. private goodsRepository: Repository<GoodsMt>;
  16. private deliveryAddressRepository: Repository<DeliveryAddressMt>;
  17. private orderRefundRepository: Repository<OrderRefundMt>;
  18. private paymentMtService: PaymentMtService;
  19. private creditBalanceService: CreditBalanceService;
  20. private logger: ReturnType<typeof createServiceLogger>;
  21. constructor(dataSource: DataSource) {
  22. super(dataSource, OrderMt, {
  23. tenantOptions: { enabled: true, tenantIdField: 'tenantId' }
  24. });
  25. this.orderGoodsRepository = dataSource.getRepository(OrderGoodsMt);
  26. this.goodsRepository = dataSource.getRepository(GoodsMt);
  27. this.deliveryAddressRepository = dataSource.getRepository(DeliveryAddressMt);
  28. this.orderRefundRepository = dataSource.getRepository(OrderRefundMt);
  29. this.paymentMtService = new PaymentMtService(dataSource);
  30. this.creditBalanceService = new CreditBalanceService(dataSource);
  31. this.logger = createServiceLogger('order-mt-service');
  32. }
  33. /**
  34. * 创建订单
  35. * @param data 创建订单请求数据
  36. * @param userId 用户ID
  37. * @param tenantId 租户ID
  38. * @returns 创建的订单信息
  39. */
  40. async createOrder(data: CreateOrderRequest, userId: number, tenantId: number): Promise<{
  41. success: boolean;
  42. orderId: number;
  43. orderNo: string;
  44. amount: number;
  45. payAmount: number;
  46. message: string;
  47. }> {
  48. const queryRunner = this.dataSource.createQueryRunner();
  49. await queryRunner.connect();
  50. await queryRunner.startTransaction();
  51. try {
  52. const { addressId, productOwn, consumeFrom, products, remark } = data;
  53. // 记录开始创建订单
  54. this.logger.info('开始创建订单', { tenantId, userId, productCount: products.length, addressId });
  55. // 验证商品信息并计算总价
  56. let totalAmount = 0;
  57. let totalCostAmount = 0;
  58. const goodsInfo = [];
  59. for (const item of products) {
  60. const goods = await this.goodsRepository.findOne({
  61. where: { id: item.id, tenantId }
  62. });
  63. if (!goods) {
  64. throw new Error(`商品ID ${item.id} 不存在`);
  65. }
  66. if (goods.stock < item.num) {
  67. throw new Error(`商品 ${goods.name} 库存不足`);
  68. }
  69. const itemAmount = goods.price * item.num;
  70. const itemCostAmount = goods.costPrice * item.num;
  71. totalAmount += itemAmount;
  72. totalCostAmount += itemCostAmount;
  73. goodsInfo.push({
  74. goods,
  75. quantity: item.num,
  76. amount: itemAmount,
  77. costAmount: itemCostAmount
  78. });
  79. }
  80. // 收集需要查询的父商品ID
  81. const parentGoodsIds = new Set<number>();
  82. for (const info of goodsInfo) {
  83. if (info.goods.spuId > 0) {
  84. parentGoodsIds.add(info.goods.spuId);
  85. }
  86. }
  87. // 批量查询父商品信息
  88. const parentGoodsMap = new Map<number, GoodsMt>();
  89. if (parentGoodsIds.size > 0) {
  90. const parentGoods = await this.goodsRepository.find({
  91. where: { id: In([...parentGoodsIds]), tenantId }
  92. });
  93. parentGoods.forEach(g => parentGoodsMap.set(g.id, g));
  94. }
  95. // 获取收货地址信息
  96. let deliveryAddress = null;
  97. if (addressId) {
  98. deliveryAddress = await this.deliveryAddressRepository.findOne({
  99. where: { id: addressId, userId, tenantId }
  100. });
  101. if (!deliveryAddress) {
  102. throw new Error('收货地址不存在');
  103. }
  104. }
  105. // 生成订单号
  106. const orderNo = this.generateOrderNo();
  107. // 创建订单
  108. // 计算订单过期时间(24小时后)
  109. const expireTime = new Date();
  110. expireTime.setHours(expireTime.getHours() + 24);
  111. const createData ={
  112. tenantId,
  113. orderNo,
  114. userId,
  115. amount: totalAmount,
  116. costAmount: totalCostAmount,
  117. payAmount: totalAmount, // 这里可以根据优惠规则计算实际支付金额
  118. orderType: 1, // 实物订单
  119. payType: consumeFrom === '积分兑换' ? 1 : 2, // 根据消费来源设置支付类型
  120. payState: 0, // 未支付
  121. state: 0, // 未发货
  122. addressId: addressId || 0,
  123. merchantId: goodsInfo[0]?.goods.merchantId || 0,
  124. supplierId: goodsInfo[0]?.goods.supplierId || 0,
  125. remark: remark || '',
  126. createdBy: userId,
  127. updatedBy: userId,
  128. expireTime, // 设置订单过期时间
  129. // 设置收货地址信息(如果提供)
  130. ...(deliveryAddress && {
  131. receiverMobile: deliveryAddress.phone,
  132. recevierName: deliveryAddress.name,
  133. recevierProvince: deliveryAddress.receiverProvince,
  134. recevierCity: deliveryAddress.receiverCity,
  135. recevierDistrict: deliveryAddress.receiverDistrict,
  136. recevierTown: deliveryAddress.receiverTown,
  137. address: deliveryAddress.address
  138. })
  139. };
  140. this.logger.info('createData:',createData);
  141. const order = this.repository.create(createData);
  142. this.logger.info('order:',order);
  143. this.logger.info('订单创建成功', { tenantId, userId, orderNo: order.orderNo, amount: order.amount, payAmount: order.payAmount });
  144. const savedOrder = await queryRunner.manager.save(order);
  145. // 创建订单商品明细
  146. const orderGoodsList = goodsInfo.map(info => {
  147. let goodsName = info.goods.name;
  148. if (info.goods.spuId > 0) {
  149. const parentGoods = parentGoodsMap.get(info.goods.spuId);
  150. if (parentGoods) {
  151. goodsName = `${parentGoods.name} ${info.goods.name}`;
  152. }
  153. }
  154. return {
  155. tenantId,
  156. orderId: savedOrder.id,
  157. orderNo,
  158. goodsId: info.goods.id,
  159. goodsName,
  160. imageFileId: info.goods.imageFileId,
  161. goodsType: info.goods.goodsType,
  162. supplierId: info.goods.supplierId,
  163. costPrice: info.goods.costPrice,
  164. price: info.goods.price,
  165. num: info.quantity,
  166. freightAmount: 0,
  167. state: 0,
  168. createdBy: userId,
  169. updatedBy: userId,
  170. expressName: null,
  171. expressNo: null
  172. };
  173. });
  174. this.logger.info('orderGoodsList:',orderGoodsList);
  175. this.logger.info('订单商品明细创建成功', { tenantId, orderId: savedOrder.id, goodsCount: orderGoodsList.length });
  176. await queryRunner.manager.save(OrderGoodsMt, orderGoodsList);
  177. // 更新商品库存
  178. for (const item of goodsInfo) {
  179. await queryRunner.manager.update(GoodsMt, item.goods.id, {
  180. stock: () => `stock - ${item.quantity}`,
  181. salesNum: () => `sales_num + ${item.quantity}`
  182. });
  183. }
  184. await queryRunner.commitTransaction();
  185. this.logger.info('订单创建流程完成', { tenantId, userId, orderId: savedOrder.id, orderNo: savedOrder.orderNo, totalAmount: savedOrder.amount });
  186. return {
  187. success: true,
  188. orderId: savedOrder.id,
  189. orderNo: savedOrder.orderNo,
  190. amount: savedOrder.amount,
  191. payAmount: savedOrder.payAmount,
  192. message: '订单创建成功'
  193. };
  194. } catch (error) {
  195. await queryRunner.rollbackTransaction();
  196. this.logger.error('订单创建失败', { tenantId, userId, error: error instanceof Error ? error.message : String(error) });
  197. throw error;
  198. } finally {
  199. await queryRunner.release();
  200. }
  201. }
  202. /**
  203. * 取消订单
  204. * @param tenantId 租户ID
  205. * @param orderId 订单ID
  206. * @param reason 取消原因
  207. * @param userId 用户ID
  208. * @returns Promise<void>
  209. */
  210. async cancelOrder(tenantId: number, orderId: number, reason: string, userId: number): Promise<void> {
  211. const queryRunner = this.dataSource.createQueryRunner();
  212. await queryRunner.connect();
  213. await queryRunner.startTransaction();
  214. try {
  215. // 查询订单信息
  216. const order = await this.repository.findOne({
  217. where: { id: orderId, tenantId, userId }
  218. });
  219. if (!order) {
  220. throw new Error('订单不存在');
  221. }
  222. // 验证订单状态(仅允许取消未发货且支付状态为0或2的订单)
  223. if (order.state !== 0) {
  224. throw new Error('当前订单状态不允许取消');
  225. }
  226. // 验证支付状态
  227. if (order.payState !== 0 && order.payState !== 2) {
  228. throw new Error('当前订单状态不允许取消');
  229. }
  230. // 对于已支付订单(支付状态=2),需要触发退款流程
  231. if (order.payState === 2) {
  232. this.logger.debug('订单已支付,开始触发退款流程', { tenantId, orderId, orderNo: order.orderNo });
  233. try {
  234. // 调用支付模块的退款功能
  235. const refundResult = await this.paymentMtService.refund(
  236. tenantId,
  237. order.orderNo,
  238. order.payAmount,
  239. `订单取消:${reason}`
  240. );
  241. this.logger.debug('退款处理成功', { tenantId, orderId, orderNo: order.orderNo, refundId: refundResult.refundId });
  242. // 创建退款记录
  243. await this.createRefundRecord(tenantId, order, refundResult, userId);
  244. } catch (error) {
  245. this.logger.warn('退款处理失败', { tenantId, orderId, orderNo: order.orderNo, error: error instanceof Error ? error.message : String(error) });
  246. // 退款失败不影响订单取消,但记录错误信息
  247. // 可以在这里添加重试机制或通知管理员
  248. }
  249. }
  250. // 更新订单状态为5(订单关闭),记录取消时间和原因
  251. await queryRunner.manager.update(OrderMt, { id: orderId, tenantId }, {
  252. payState: 5, // 订单关闭
  253. cancelReason: reason,
  254. cancelTime: new Date(),
  255. updatedBy: userId,
  256. updatedAt: new Date()
  257. });
  258. // 如果是额度支付订单(payType === 3),需要恢复额度
  259. if (order.payType === 3) {
  260. this.logger.debug('额度支付订单取消,开始恢复额度', { tenantId, orderId, orderNo: order.orderNo });
  261. try {
  262. await this.creditBalanceService.restoreBalanceForCancelOrder(
  263. tenantId,
  264. userId,
  265. order.orderNo,
  266. order.payAmount,
  267. userId
  268. );
  269. this.logger.debug('额度恢复成功', { tenantId, orderId, orderNo: order.orderNo });
  270. } catch (error) {
  271. this.logger.warn('额度恢复失败', { tenantId, orderId, orderNo: order.orderNo, error: error instanceof Error ? error.message : String(error) });
  272. // 额度恢复失败不影响订单取消,但记录错误信息
  273. }
  274. }
  275. // 如果订单是未支付状态,需要恢复商品库存
  276. if (order.payState === 0) {
  277. // 查询订单商品明细
  278. const orderGoods = await this.orderGoodsRepository.find({
  279. where: { orderId, tenantId }
  280. });
  281. // 恢复商品库存
  282. for (const item of orderGoods) {
  283. await queryRunner.manager.update(GoodsMt, { id: item.goodsId, tenantId }, {
  284. stock: () => `stock + ${item.num}`,
  285. salesNum: () => `sales_num - ${item.num}`
  286. });
  287. }
  288. }
  289. await queryRunner.commitTransaction();
  290. } catch (error) {
  291. await queryRunner.rollbackTransaction();
  292. throw error;
  293. } finally {
  294. await queryRunner.release();
  295. }
  296. }
  297. /**
  298. * 确认收货
  299. * @param tenantId 租户ID
  300. * @param orderId 订单ID
  301. * @param userId 用户ID
  302. * @returns 更新后的订单信息
  303. */
  304. async confirmReceipt(tenantId: number, orderId: number, userId: number): Promise<{
  305. orderId: number;
  306. state: number;
  307. updatedAt: Date;
  308. }> {
  309. const queryRunner = this.dataSource.createQueryRunner();
  310. await queryRunner.connect();
  311. await queryRunner.startTransaction();
  312. try {
  313. // 查询订单信息
  314. const order = await this.repository.findOne({
  315. where: { id: orderId, tenantId, userId }
  316. });
  317. if (!order) {
  318. throw new Error('订单不存在');
  319. }
  320. // 验证订单状态:只有已发货的订单才能确认收货
  321. if (order.state !== 1) {
  322. throw new Error('只有已发货的订单才能确认收货');
  323. }
  324. // 验证支付状态:只有已支付的订单才能确认收货
  325. if (order.payState !== 2) {
  326. throw new Error('只有已支付的订单才能确认收货');
  327. }
  328. // 更新订单状态为收货成功 (state = 2)
  329. await queryRunner.manager.update(OrderMt, { id: orderId, tenantId }, {
  330. state: 2, // 收货成功
  331. updatedBy: userId,
  332. updatedAt: new Date()
  333. });
  334. // 提交事务
  335. await queryRunner.commitTransaction();
  336. // 返回更新后的订单信息
  337. const updatedOrder = await this.repository.findOne({
  338. where: { id: orderId, tenantId }
  339. });
  340. if (!updatedOrder) {
  341. throw new Error('订单更新后查询失败');
  342. }
  343. return {
  344. orderId: updatedOrder.id,
  345. state: updatedOrder.state,
  346. updatedAt: updatedOrder.updatedAt
  347. };
  348. } catch (error) {
  349. await queryRunner.rollbackTransaction();
  350. throw error;
  351. } finally {
  352. await queryRunner.release();
  353. }
  354. }
  355. /**
  356. * 生成订单号
  357. * 格式:年月日时分秒 + 6位随机数
  358. */
  359. private generateOrderNo(): string {
  360. const now = new Date();
  361. const year = now.getFullYear();
  362. const month = String(now.getMonth() + 1).padStart(2, '0');
  363. const day = String(now.getDate()).padStart(2, '0');
  364. const hour = String(now.getHours()).padStart(2, '0');
  365. const minute = String(now.getMinutes()).padStart(2, '0');
  366. const second = String(now.getSeconds()).padStart(2, '0');
  367. const random = String(Math.floor(Math.random() * 1000000)).padStart(6, '0');
  368. return `ORD${year}${month}${day}${hour}${minute}${second}${random}`;
  369. }
  370. /**
  371. * 创建退款记录
  372. */
  373. private async createRefundRecord(
  374. tenantId: number,
  375. order: OrderMt,
  376. refundResult: any,
  377. userId: number
  378. ): Promise<void> {
  379. const refundRecord = new OrderRefundMt();
  380. refundRecord.tenantId = tenantId;
  381. refundRecord.orderNo = order.orderNo;
  382. refundRecord.refundOrderNo = refundResult.outRefundNo;
  383. refundRecord.refundAmount = refundResult.refundAmount;
  384. refundRecord.state = 2; // 退款成功
  385. refundRecord.remark = `退款成功,微信退款流水号: ${refundResult.refundId}`;
  386. refundRecord.createdBy = userId;
  387. refundRecord.updatedBy = userId;
  388. await this.orderRefundRepository.save(refundRecord);
  389. this.logger.debug('退款记录创建成功', { tenantId, orderNo: order.orderNo, refundOrderNo: refundResult.outRefundNo });
  390. }
  391. /**
  392. * 根据订单ID查询支付记录
  393. * @param tenantId 租户ID
  394. * @param orderId 订单ID
  395. * @param userId 用户ID(可选,用于权限验证)
  396. * @returns 支付记录信息,如果不存在则返回null
  397. */
  398. async getPaymentRecordByOrderId(
  399. tenantId: number,
  400. orderId: number,
  401. userId?: number
  402. ): Promise<PaymentMtEntity | null> {
  403. try {
  404. // 获取支付实体仓库
  405. const paymentRepository = this.dataSource.getRepository(PaymentMtEntity);
  406. // 构建查询条件
  407. const whereCondition: any = {
  408. externalOrderId: orderId,
  409. tenantId
  410. };
  411. // 如果提供了用户ID,则添加用户ID过滤(确保用户只能查询自己的支付记录)
  412. if (userId !== undefined) {
  413. whereCondition.userId = userId;
  414. }
  415. // 查询支付记录
  416. const paymentRecord = await paymentRepository.findOne({
  417. where: whereCondition,
  418. order: { createdAt: 'DESC' } // 按创建时间倒序,获取最新的支付记录
  419. });
  420. if (!paymentRecord) {
  421. this.logger.debug('未找到订单的支付记录', { tenantId, orderId, userId });
  422. return null;
  423. }
  424. this.logger.debug('找到订单的支付记录', {
  425. tenantId,
  426. orderId,
  427. paymentId: paymentRecord.id,
  428. outTradeNo: paymentRecord.outTradeNo,
  429. wechatTransactionId: paymentRecord.wechatTransactionId,
  430. paymentStatus: paymentRecord.paymentStatus,
  431. totalAmount: paymentRecord.totalAmount
  432. });
  433. return paymentRecord;
  434. } catch (error) {
  435. this.logger.error('查询订单支付记录失败', { tenantId, orderId, error: error instanceof Error ? error.message : String(error) });
  436. throw new Error(`查询支付记录失败: ${error instanceof Error ? error.message : '未知错误'}`);
  437. }
  438. }
  439. /**
  440. * 根据订单ID查询支付记录(简化版,返回必要信息)
  441. * @param tenantId 租户ID
  442. * @param orderId 订单ID
  443. * @param userId 用户ID(可选)
  444. * @returns 支付记录的关键信息
  445. */
  446. async getPaymentInfoByOrderId(
  447. tenantId: number,
  448. orderId: number,
  449. userId?: number
  450. ): Promise<{
  451. outTradeNo: string | null;
  452. wechatTransactionId: string | null;
  453. paymentStatus: string;
  454. exists: boolean;
  455. paymentId?: number;
  456. totalAmount?: number;
  457. createdAt?: Date;
  458. hasWechatTransactionId: boolean;
  459. paymentStatusDetail: string;
  460. }> {
  461. try {
  462. const paymentRecord = await this.getPaymentRecordByOrderId(tenantId, orderId, userId);
  463. if (!paymentRecord) {
  464. return {
  465. outTradeNo: null,
  466. wechatTransactionId: null,
  467. paymentStatus: '未找到支付记录',
  468. exists: false,
  469. hasWechatTransactionId: false,
  470. paymentStatusDetail: '支付记录不存在'
  471. };
  472. }
  473. const hasWechatTransactionId = !!paymentRecord.wechatTransactionId;
  474. let paymentStatusDetail: string = paymentRecord.paymentStatus || '未知状态';
  475. // 添加详细的支付状态说明
  476. if (!hasWechatTransactionId) {
  477. if (paymentRecord.paymentStatus === PaymentStatus.PAID) {
  478. paymentStatusDetail = '已支付(等待微信回调更新交易ID)';
  479. } else if (paymentRecord.paymentStatus === PaymentStatus.PROCESSING) {
  480. paymentStatusDetail = '支付处理中(微信交易ID将在支付成功后生成)';
  481. } else if (paymentRecord.paymentStatus === PaymentStatus.PENDING) {
  482. paymentStatusDetail = '待支付(尚未完成支付)';
  483. }
  484. }
  485. return {
  486. outTradeNo: paymentRecord.outTradeNo || null,
  487. wechatTransactionId: paymentRecord.wechatTransactionId || null,
  488. paymentStatus: paymentRecord.paymentStatus || '未知状态',
  489. exists: true,
  490. paymentId: paymentRecord.id,
  491. totalAmount: paymentRecord.totalAmount,
  492. createdAt: paymentRecord.createdAt,
  493. hasWechatTransactionId,
  494. paymentStatusDetail
  495. };
  496. } catch (error) {
  497. this.logger.error('获取订单支付信息失败', { tenantId, orderId, error: error instanceof Error ? error.message : String(error) });
  498. return {
  499. outTradeNo: null,
  500. wechatTransactionId: null,
  501. paymentStatus: '查询失败',
  502. exists: false,
  503. hasWechatTransactionId: false,
  504. paymentStatusDetail: `查询失败: ${error instanceof Error ? error.message : '未知错误'}`
  505. };
  506. }
  507. }
  508. }