MexAccountLogic.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. <?php
  2. namespace App\Module\Mex\Logic;
  3. use App\Module\Fund\Services\FundService;
  4. use App\Module\Fund\Enums\FUND_TYPE;
  5. use App\Module\Fund\Enums\FUND_CURRENCY_TYPE;
  6. use App\Module\Game\Enums\REWARD_SOURCE_TYPE;
  7. use App\Module\GameItems\Services\ItemService;
  8. use App\Module\Mex\Logic\MexTransactionLogic;
  9. use App\Module\Mex\Logic\MexWarehouseLogic;
  10. use App\Module\Mex\Logic\FundLogic;
  11. use App\Module\Mex\Enums\TransactionType;
  12. use Illuminate\Support\Facades\DB;
  13. use Illuminate\Support\Facades\Log;
  14. /**
  15. * 农贸市场账户流转逻辑
  16. *
  17. * 处理用户与仓库账户之间的资金和物品流转
  18. * 仓库账户USER_ID为15,调控账户USER_ID为16
  19. * 支持多币种交易,默认使用钻石币种
  20. */
  21. class MexAccountLogic
  22. {
  23. // 仓库账户ID
  24. const WAREHOUSE_USER_ID = 15;
  25. // 调控账户ID
  26. const REGULATION_USER_ID = 16;
  27. /**
  28. * 处理用户卖出订单的账户流转
  29. * 用户卖出:资金从仓库转出到用户、物品从用户转入仓库
  30. *
  31. * @param int $userId 用户ID
  32. * @param int $itemId 商品ID
  33. * @param int $quantity 数量
  34. * @param string $price 单价
  35. * @param string $totalAmount 总金额
  36. * @param int $orderId 订单ID
  37. * @param FUND_CURRENCY_TYPE|null $currencyType 币种类型,默认使用钻石
  38. * @return array 操作结果
  39. */
  40. public static function processSellOrder(int $userId, int $itemId, int $quantity, string $price, string $totalAmount, int $orderId, ?FUND_CURRENCY_TYPE $currencyType = null): array
  41. {
  42. try {
  43. DB::beginTransaction();
  44. // 获取币种类型,默认使用钻石
  45. $currencyType = $currencyType ?? FundLogic::getDefaultCurrency();
  46. // 获取对应的账户类型
  47. $availableAccountType = FundLogic::getAvailableAccountType($currencyType);
  48. if (!$availableAccountType) {
  49. DB::rollBack();
  50. return ['success' => false, 'message' => '不支持的币种类型'];
  51. }
  52. // 1. 验证用户是否有足够的物品
  53. $checkResult = ItemService::checkItemQuantity($userId, $itemId, $quantity);
  54. if (!$checkResult->success) {
  55. DB::rollBack();
  56. return ['success' => false, 'message' => $checkResult->message];
  57. }
  58. // 2. 从用户扣除物品
  59. $consumeResult = ItemService::consumeItem($userId, $itemId, null, $quantity, [
  60. 'reason' => 'mex_sell',
  61. 'order_id' => $orderId,
  62. 'remark' => "农贸市场卖出物品,订单ID:{$orderId}"
  63. ]);
  64. if (!$consumeResult['success']) {
  65. DB::rollBack();
  66. return ['success' => false, 'message' => '扣除用户物品失败:' . ($consumeResult['message'] ?? '未知错误')];
  67. }
  68. // 3. 仓库账户是虚拟账户,不实际存储物品,只更新统计数据
  69. // 物品已从用户扣除,直接进入虚拟仓库统计
  70. // 4. 从仓库账户转出资金给用户
  71. $warehouseFundService = new FundService(self::WAREHOUSE_USER_ID, $availableAccountType->value);
  72. $precision = $currencyType->getPrecision();
  73. $fundAmount = (int)bcmul($totalAmount, bcpow('10', $precision), 0); // 根据币种精度转换
  74. $transferResult = $warehouseFundService->trade(
  75. $userId,
  76. $fundAmount,
  77. 'mex_sell',
  78. $orderId,
  79. "农贸市场卖出收款,订单ID:{$orderId}"
  80. );
  81. if (is_string($transferResult)) {
  82. DB::rollBack();
  83. return ['success' => false, 'message' => '资金转移失败:' . $transferResult];
  84. }
  85. // 5. 更新仓库统计
  86. $warehouseResult = MexWarehouseLogic::addStock($itemId, $quantity, $totalAmount);
  87. if (!$warehouseResult) {
  88. DB::rollBack();
  89. return ['success' => false, 'message' => '更新仓库统计失败'];
  90. }
  91. // 6. 创建成交记录
  92. $transactionResult = MexTransactionLogic::createTransaction([
  93. 'sell_order_id' => $orderId,
  94. 'buyer_id' => self::WAREHOUSE_USER_ID,
  95. 'seller_id' => $userId,
  96. 'item_id' => $itemId,
  97. 'currency_type' => $currencyType->value,
  98. 'quantity' => $quantity,
  99. 'price' => $price,
  100. 'total_amount' => $totalAmount,
  101. 'transaction_type' => TransactionType::USER_SELL,
  102. 'is_admin_operation' => false,
  103. ]);
  104. if (!$transactionResult) {
  105. DB::rollBack();
  106. return ['success' => false, 'message' => '创建成交记录失败'];
  107. }
  108. DB::commit();
  109. return [
  110. 'success' => true,
  111. 'message' => '卖出订单处理成功',
  112. 'transaction_id' => $transactionResult->id,
  113. 'fund_transfer' => $transferResult,
  114. 'item_consume' => $consumeResult,
  115. 'warehouse_add' => $addResult
  116. ];
  117. } catch (\Exception $e) {
  118. DB::rollBack();
  119. Log::error('Mex卖出订单处理失败', [
  120. 'user_id' => $userId,
  121. 'item_id' => $itemId,
  122. 'quantity' => $quantity,
  123. 'order_id' => $orderId,
  124. 'error' => $e->getMessage(),
  125. 'trace' => $e->getTraceAsString()
  126. ]);
  127. return ['success' => false, 'message' => '系统错误:' . $e->getMessage()];
  128. }
  129. }
  130. /**
  131. * 处理用户买入订单的账户流转
  132. * 用户买入:资金从用户转入仓库、物品从仓库转出到用户
  133. *
  134. * @param int $userId 用户ID
  135. * @param int $itemId 商品ID
  136. * @param int $quantity 数量
  137. * @param string $price 单价
  138. * @param string $totalAmount 总金额
  139. * @param int $orderId 订单ID
  140. * @param FUND_CURRENCY_TYPE|null $currencyType 币种类型,默认使用钻石
  141. * @return array 操作结果
  142. */
  143. public static function processBuyOrder(int $userId, int $itemId, int $quantity, string $price, string $totalAmount, int $orderId, ?FUND_CURRENCY_TYPE $currencyType = null): array
  144. {
  145. // 检查事务是否已开启
  146. \UCore\Db\Helper::check_tr();
  147. try {
  148. // 获取币种类型,默认使用钻石
  149. $currencyType = $currencyType ?? FundLogic::getDefaultCurrency();
  150. // 获取对应的账户类型
  151. $availableAccountType = FundLogic::getAvailableAccountType($currencyType);
  152. if (!$availableAccountType) {
  153. throw new \Exception('不支持的币种类型');
  154. }
  155. // 1. 验证仓库是否有足够的物品
  156. if (!MexWarehouseLogic::checkStockSufficient($itemId, $quantity)) {
  157. throw new \Exception('仓库库存不足');
  158. }
  159. // 2. 验证用户是否有足够的资金
  160. $userFundService = new FundService($userId, $availableAccountType->value);
  161. $precision = $currencyType->getPrecision();
  162. $fundAmount = (int)bcmul($totalAmount, bcpow('10', $precision), 0); // 根据币种精度转换
  163. if ($userFundService->balance() < $fundAmount) {
  164. throw new \Exception('用户资金不足');
  165. }
  166. // 3. 从用户转出资金到仓库
  167. $transferResult = $userFundService->trade(
  168. self::WAREHOUSE_USER_ID,
  169. $fundAmount,
  170. 'mex_buy',
  171. $orderId,
  172. "农贸市场买入付款,订单ID:{$orderId}"
  173. );
  174. if (is_string($transferResult)) {
  175. throw new \Exception('资金转移失败:' . $transferResult);
  176. }
  177. // 4. 从仓库扣除物品
  178. $consumeResult = ItemService::consumeItem(self::WAREHOUSE_USER_ID, $itemId, null, $quantity, [
  179. 'reason' => 'mex_warehouse_sell',
  180. 'order_id' => $orderId,
  181. 'remark' => "农贸市场仓库出售物品,订单ID:{$orderId}"
  182. ]);
  183. if (!$consumeResult['success']) {
  184. throw new \Exception('仓库扣除物品失败:' . ($consumeResult['message'] ?? '未知错误'));
  185. }
  186. // 5. 给用户添加物品
  187. $addResult = ItemService::addItem($userId, $itemId, $quantity, [
  188. 'reason' => 'mex_buy',
  189. 'source_type' => REWARD_SOURCE_TYPE::MEX_BUY,
  190. 'source_id' => $orderId,
  191. 'order_id' => $orderId,
  192. 'remark' => "农贸市场买入物品,订单ID:{$orderId}"
  193. ]);
  194. if (!$addResult['success']) {
  195. throw new \Exception('用户添加物品失败:' . ($addResult['message'] ?? '未知错误'));
  196. }
  197. // 6. 更新仓库统计
  198. $warehouseResult = MexWarehouseLogic::reduceStock($itemId, $quantity, $totalAmount);
  199. if (!$warehouseResult) {
  200. throw new \Exception('更新仓库统计失败');
  201. }
  202. // 7. 创建成交记录
  203. $transactionResult = MexTransactionLogic::createTransaction([
  204. 'buy_order_id' => $orderId,
  205. 'buyer_id' => $userId,
  206. 'seller_id' => self::WAREHOUSE_USER_ID,
  207. 'item_id' => $itemId,
  208. 'currency_type' => $currencyType->value,
  209. 'quantity' => $quantity,
  210. 'price' => $price,
  211. 'total_amount' => $totalAmount,
  212. 'transaction_type' => TransactionType::USER_BUY,
  213. 'is_admin_operation' => false,
  214. ]);
  215. if (!$transactionResult) {
  216. throw new \Exception('创建成交记录失败');
  217. }
  218. return [
  219. 'success' => true,
  220. 'message' => '买入订单处理成功',
  221. 'transaction_id' => $transactionResult->id,
  222. 'fund_transfer' => $transferResult,
  223. 'item_consume' => $consumeResult,
  224. 'user_add' => $addResult
  225. ];
  226. } catch (\Exception $e) {
  227. Log::error('Mex买入订单处理失败', [
  228. 'user_id' => $userId,
  229. 'item_id' => $itemId,
  230. 'quantity' => $quantity,
  231. 'order_id' => $orderId,
  232. 'error' => $e->getMessage(),
  233. 'trace' => $e->getTraceAsString()
  234. ]);
  235. throw $e; // 重新抛出异常,不隐藏错误
  236. }
  237. }
  238. /**
  239. * 检查仓库账户资金余额
  240. *
  241. * @param FUND_CURRENCY_TYPE|null $currencyType 币种类型,默认使用钻石
  242. * @return int 余额(按币种精度存储的整数)
  243. */
  244. public static function getWarehouseFundBalance(?FUND_CURRENCY_TYPE $currencyType = null): int
  245. {
  246. $currencyType = $currencyType ?? FundLogic::getDefaultCurrency();
  247. $availableAccountType = FundLogic::getAvailableAccountType($currencyType);
  248. if (!$availableAccountType) {
  249. return 0;
  250. }
  251. $warehouseFundService = new FundService(self::WAREHOUSE_USER_ID, $availableAccountType->value);
  252. return $warehouseFundService->balance();
  253. }
  254. /**
  255. * 检查调控账户资金余额
  256. *
  257. * @param FUND_CURRENCY_TYPE|null $currencyType 币种类型,默认使用钻石
  258. * @return int 余额(按币种精度存储的整数)
  259. */
  260. public static function getRegulationFundBalance(?FUND_CURRENCY_TYPE $currencyType = null): int
  261. {
  262. $currencyType = $currencyType ?? FundLogic::getDefaultCurrency();
  263. $availableAccountType = FundLogic::getAvailableAccountType($currencyType);
  264. if (!$availableAccountType) {
  265. return 0;
  266. }
  267. $regulationFundService = new FundService(self::REGULATION_USER_ID, $availableAccountType->value);
  268. return $regulationFundService->balance();
  269. }
  270. }