MexOrderLogic.php 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. <?php
  2. namespace App\Module\Mex\Logic;
  3. use App\Module\Mex\Models\MexOrder;
  4. use App\Module\Mex\Models\MexPriceConfig;
  5. use App\Module\Mex\Enums\OrderType;
  6. use App\Module\Mex\Enums\OrderStatus;
  7. use App\Module\Mex\Events\OrderCreatedEvent;
  8. use App\Module\Fund\Enums\FUND_CURRENCY_TYPE;
  9. use App\Module\Mex\Logic\FundLogic;
  10. use App\Module\GameItems\Services\ItemService;
  11. use App\Module\GameItems\Enums\FREEZE_REASON_TYPE;
  12. use Illuminate\Support\Facades\DB;
  13. /**
  14. * 农贸市场订单逻辑
  15. *
  16. * 处理订单相关的核心业务逻辑
  17. */
  18. class MexOrderLogic
  19. {
  20. /**
  21. * 创建卖出订单
  22. *
  23. * @param int $userId 用户ID
  24. * @param int $itemId 商品ID
  25. * @param int $quantity 数量
  26. * @param float $price 价格(信任Fund模块的数据处理)
  27. * @param FUND_CURRENCY_TYPE|null $currencyType 币种类型,默认使用钻石
  28. * @return array 操作结果
  29. */
  30. public static function createSellOrder(int $userId, int $itemId, int $quantity, float $price, ?FUND_CURRENCY_TYPE $currencyType = null): array
  31. {
  32. // 获取币种类型,默认使用钻石
  33. $currencyType = $currencyType ?? FundLogic::getDefaultCurrency();
  34. // 验证价格配置是否存在(不验证价格范围,挂单阶段无价格验证)
  35. $priceConfig = MexPriceConfig::where('item_id', $itemId)->where('is_enabled', true)->first();
  36. if (!$priceConfig) {
  37. return ['success' => false, 'message' => '商品未配置价格信息'];
  38. }
  39. // 直接计算总金额,信任Fund模块的精度处理
  40. $totalAmount = $price * $quantity;
  41. return DB::transaction(function () use ($userId, $itemId, $quantity, $price, $currencyType, $totalAmount) {
  42. try {
  43. // 1. 验证用户是否有足够的物品
  44. $checkResult = ItemService::checkItemQuantity($userId, $itemId, $quantity);
  45. if (!$checkResult->success) {
  46. return ['success' => false, 'message' => $checkResult->message];
  47. }
  48. // 2. 创建订单记录
  49. $order = MexOrder::create([
  50. 'user_id' => $userId,
  51. 'item_id' => $itemId,
  52. 'currency_type' => $currencyType->value,
  53. 'order_type' => OrderType::SELL,
  54. 'quantity' => $quantity,
  55. 'price' => $price,
  56. 'total_amount' => $totalAmount,
  57. 'status' => OrderStatus::PENDING,
  58. ]);
  59. // 3. 冻结用户物品
  60. $freezeResult = ItemService::freezeItem(
  61. $userId,
  62. $itemId,
  63. null, // 统一属性物品,不需要实例ID
  64. $quantity,
  65. "农贸市场卖出订单冻结,订单ID:{$order->id}",
  66. [
  67. 'reason_type' => FREEZE_REASON_TYPE::TRADE_ORDER->value,
  68. 'source_id' => $order->id,
  69. 'source_type' => 'mex_sell_order',
  70. ]
  71. );
  72. if (!$freezeResult['success']) {
  73. throw new \Exception('冻结物品失败:' . $freezeResult['message']);
  74. }
  75. // 4. 触发订单创建事件
  76. event(new OrderCreatedEvent($order));
  77. return [
  78. 'success' => true,
  79. 'order_id' => $order->id,
  80. 'freeze_log_id' => $freezeResult['freeze_log_id'] ?? null,
  81. 'message' => '卖出订单创建成功,等待撮合'
  82. ];
  83. } catch (\Exception $e) {
  84. return ['success' => false, 'message' => '创建订单失败:' . $e->getMessage()];
  85. }
  86. });
  87. }
  88. /**
  89. * 创建买入订单
  90. *
  91. * @param int $userId 用户ID
  92. * @param int $itemId 商品ID
  93. * @param int $quantity 数量
  94. * @param float $price 价格(信任Fund模块的数据处理)
  95. * @param FUND_CURRENCY_TYPE|null $currencyType 币种类型,默认使用钻石
  96. * @return array 操作结果
  97. */
  98. public static function createBuyOrder(int $userId, int $itemId, int $quantity, float $price, ?FUND_CURRENCY_TYPE $currencyType = null): array
  99. {
  100. // 获取币种类型,默认使用钻石
  101. $currencyType = $currencyType ?? FundLogic::getDefaultCurrency();
  102. // 验证价格配置是否存在(不验证价格范围和保护阈值,挂单阶段无价格验证)
  103. $priceConfig = MexPriceConfig::where('item_id', $itemId)->where('is_enabled', true)->first();
  104. if (!$priceConfig) {
  105. return ['success' => false, 'message' => '商品未配置价格信息'];
  106. }
  107. // 直接计算总金额,信任Fund模块的精度处理
  108. $totalAmount = $price * $quantity;
  109. try {
  110. $order = MexOrder::create([
  111. 'user_id' => $userId,
  112. 'item_id' => $itemId,
  113. 'currency_type' => $currencyType->value,
  114. 'order_type' => OrderType::BUY,
  115. 'quantity' => $quantity,
  116. 'price' => $price,
  117. 'total_amount' => $totalAmount,
  118. 'status' => OrderStatus::PENDING,
  119. 'frozen_amount' => $totalAmount,
  120. ]);
  121. // 触发订单创建事件
  122. event(new OrderCreatedEvent($order));
  123. return [
  124. 'success' => true,
  125. 'order_id' => $order->id,
  126. 'message' => '买入订单创建成功,等待撮合'
  127. ];
  128. } catch (\Exception $e) {
  129. return ['success' => false, 'message' => '创建订单失败:' . $e->getMessage()];
  130. }
  131. }
  132. /**
  133. * 取消订单
  134. *
  135. * @param int $userId 用户ID
  136. * @param int $orderId 订单ID
  137. * @return array 操作结果
  138. */
  139. public static function cancelOrder(int $userId, int $orderId): array
  140. {
  141. $order = MexOrder::where('id', $orderId)->where('user_id', $userId)->first();
  142. if (!$order) {
  143. return ['success' => false, 'message' => '订单不存在'];
  144. }
  145. if ($order->status !== OrderStatus::PENDING) {
  146. return ['success' => false, 'message' => '只能取消等待中的订单'];
  147. }
  148. try {
  149. $order->update(['status' => OrderStatus::CANCELLED]);
  150. return ['success' => true, 'message' => '订单已取消'];
  151. } catch (\Exception $e) {
  152. return ['success' => false, 'message' => '取消订单失败:' . $e->getMessage()];
  153. }
  154. }
  155. /**
  156. * 获取用户订单列表
  157. *
  158. * @param int $userId 用户ID
  159. * @param int $page 页码
  160. * @param int $pageSize 每页数量
  161. * @return array 订单列表
  162. */
  163. public static function getUserOrders(int $userId, int $page = 1, int $pageSize = 20): array
  164. {
  165. $orders = MexOrder::where('user_id', $userId)
  166. ->orderBy('created_at', 'desc')
  167. ->paginate($pageSize, ['*'], 'page', $page);
  168. return [
  169. 'orders' => $orders->items(),
  170. 'total' => $orders->total(),
  171. 'page' => $page,
  172. 'page_size' => $pageSize,
  173. ];
  174. }
  175. /**
  176. * 获取订单详情
  177. *
  178. * @param int $userId 用户ID
  179. * @param int $orderId 订单ID
  180. * @return array|null 订单详情
  181. */
  182. public static function getOrderDetail(int $userId, int $orderId): ?array
  183. {
  184. $order = MexOrder::where('id', $orderId)->where('user_id', $userId)->first();
  185. return $order ? $order->toArray() : null;
  186. }
  187. /**
  188. * 获取待撮合的买入订单
  189. * 根据文档要求:二级排序(价格DESC + 时间ASC),移除数量排序
  190. *
  191. * @param int $itemId 商品ID
  192. * @param int $limit 限制数量
  193. * @return array 订单列表
  194. */
  195. public static function getPendingBuyOrders(int $itemId, int $limit = 100): array
  196. {
  197. $orders = MexOrder::where('item_id', $itemId)
  198. ->where('order_type', OrderType::BUY)
  199. ->where('status', OrderStatus::PENDING)
  200. ->orderBy('price', 'desc') // 价格优先(高价优先)
  201. ->orderBy('created_at', 'asc') // 时间优先(早下单优先)
  202. ->limit($limit)
  203. ->get();
  204. return $orders->toArray();
  205. }
  206. }