TransferLogic.php 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681
  1. <?php
  2. namespace App\Module\Transfer\Logics;
  3. use App\Module\Transfer\Enums\TransferStatus;
  4. use App\Module\Transfer\Enums\TransferType;
  5. use App\Module\Transfer\Events\FeeCalculatedEvent;
  6. use App\Module\Transfer\Models\TransferApp;
  7. use App\Module\Transfer\Models\TransferOrder;
  8. use App\Module\Transfer\Services\FeeService;
  9. use App\Module\Transfer\Validations\TransferInValidation;
  10. use App\Module\Transfer\Validations\TransferOutValidation;
  11. use App\Module\Fund\Services\FundService;
  12. use App\Module\Transfer\Logics\OrderLogic;
  13. use App\Module\Transfer\Logics\CallbackLogic;
  14. use Illuminate\Support\Str;
  15. use Illuminate\Support\Facades\Log;
  16. /**
  17. * Transfer模块核心业务逻辑
  18. */
  19. class TransferLogic
  20. {
  21. /**
  22. * 创建转出订单(对外接口)
  23. *
  24. * @param int $transferAppId 划转应用ID
  25. * @param int $userId 用户ID
  26. * @param string $amount 转出金额
  27. * @param string $password 安全密码
  28. * @param string|null $googleCode 谷歌验证码
  29. * @param string|null $outUserId 外部用户ID
  30. * @param string|null $remark 备注
  31. * @param array $callbackData 回调数据
  32. * @return TransferOrder
  33. * @throws \Exception
  34. */
  35. public static function createTransferOut(
  36. int $transferAppId,
  37. int $userId,
  38. string $amount,
  39. string $password,
  40. ?string $googleCode = null,
  41. ?string $outUserId = null,
  42. ?string $remark = null,
  43. array $callbackData = []
  44. ): TransferOrder
  45. {
  46. $data = [
  47. 'transfer_app_id' => $transferAppId,
  48. 'user_id' => $userId,
  49. 'amount' => $amount,
  50. 'password' => $password,
  51. 'google_code' => $googleCode,
  52. 'out_user_id' => $outUserId,
  53. 'remark' => $remark,
  54. 'callback_data' => $callbackData,
  55. ];
  56. return self::createTransferOutFromArray($data);
  57. }
  58. /**
  59. * 创建第三方应用转出订单(跳过密码验证)
  60. * 我 =》 三方
  61. *
  62. * @param int $transferAppId 划转应用ID
  63. * @param int $userId 用户ID
  64. * @param string $amount 转出金额(外部金额,第三方系统的金额)
  65. * @param string|null $outUserId 外部用户ID
  66. * @param string|null $remark 备注
  67. * @param array $callbackData 回调数据
  68. * @return TransferOrder
  69. * @throws \Exception
  70. */
  71. public static function createTransferOutForThirdParty(
  72. int $transferAppId,
  73. int $userId,
  74. string $amount,
  75. string $out_order_id,
  76. ?string $outUserId = null,
  77. ?string $remark = null,
  78. array $callbackData = []
  79. ): TransferOrder
  80. {
  81. $data = [
  82. 'transfer_app_id' => $transferAppId,
  83. 'user_id' => $userId,
  84. 'out_order_id' => $out_order_id,
  85. 'amount' => $amount,
  86. 'out_user_id' => $outUserId,
  87. 'remark' => $remark,
  88. 'callback_data' => $callbackData,
  89. ];
  90. return self::createTransferOutFromArrayForThirdParty($data);
  91. }
  92. /**
  93. * 创建第三方应用转出订单(内部实现,跳过密码验证)
  94. * 我 =》 三方
  95. * 注意:第三方应用传入的金额是外部金额,需要转换为内部金额进行处理
  96. *
  97. * @param array $data 订单数据
  98. * @return TransferOrder
  99. * @throws \Exception
  100. */
  101. public static function createTransferOutFromArrayForThirdParty(array $data): TransferOrder
  102. {
  103. // 获取应用配置(需要先获取以便进行金额转换)
  104. /**
  105. * @var TransferApp $app
  106. */
  107. $app = TransferApp::findOrFail($data['transfer_app_id']);
  108. if (!$app->is_enabled) {
  109. throw new \Exception('应用已禁用');
  110. }
  111. // 验证汇率是否合理
  112. if (bccomp((string)$app->exchange_rate, '0', 10) <= 0) {
  113. throw new \Exception('汇率配置错误:汇率必须大于0');
  114. }
  115. // 第三方应用金额处理:传入的是外部金额,需要转换为内部金额
  116. $outAmount = (string)$data['amount']; // 外部金额(第三方系统金额)
  117. $amount = bcmul($outAmount, (string)$app->exchange_rate, 10); // 内部金额 = 外部金额 × 汇率
  118. // 验证外部金额是否合理
  119. if (bccomp($outAmount, '0', 10) <= 0) {
  120. throw new \Exception('转出金额必须大于0');
  121. }
  122. // 验证内部金额计算结果是否合理
  123. if (bccomp($amount, '0', 10) <= 0) {
  124. throw new \Exception('汇率计算结果异常:内部金额必须大于0');
  125. }
  126. // 修改验证数据:将外部金额替换为内部金额,这样验证器就能正确验证余额
  127. $validationData = $data;
  128. $validationData['amount'] = $amount; // 传递内部金额给验证器
  129. // 使用第三方应用专用验证类(跳过密码验证)
  130. $validation = new \App\Module\Transfer\Validations\TransferOutThirdPartyValidation($validationData);
  131. $validation->validated();
  132. // 计算手续费(基于内部金额)
  133. $FeeCalculatedEvent = \App\Module\Transfer\Services\FeeService::calculateOutFee($app, $amount, [ 'user_id' => $data['user_id'] ]);
  134. $fund = new FundService($data['user_id'],$app->fund_id);
  135. if(!$fund->check($FeeCalculatedEvent->totleAmount)){
  136. throw new \Exception('错误,余额不足');
  137. }
  138. // 创建订单
  139. /**
  140. * @var TransferOrder $order
  141. */
  142. $order = TransferOrder::create([
  143. 'transfer_app_id' => $app->id,
  144. 'out_id' => $app->out_id2 ?? 0,
  145. 'out_order_id' => $data['out_order_id'] ?? null,
  146. 'out_user_id' => $data['out_user_id'] ?? null,
  147. 'user_id' => $data['user_id'],
  148. 'currency_id' => $app->currency_id,
  149. 'fund_id' => $app->fund_id,
  150. 'type' => TransferType::OUT,
  151. 'status' => TransferStatus::CREATED,
  152. 'out_amount' => $outAmount,
  153. 'amount' => $FeeCalculatedEvent->amount,
  154. 'exchange_rate' => $app->exchange_rate,
  155. 'fee_rate' => $FeeCalculatedEvent->feeRate,
  156. 'fee_amount' => $FeeCalculatedEvent->feeAmount,
  157. 'actual_amount' => $FeeCalculatedEvent->actualAmount,
  158. 'totle_amount' => $FeeCalculatedEvent->totleAmount,
  159. 'callback_data' => $data['callback_data'] ?? [],
  160. 'remark' => $data['remark'] ?? null,
  161. ]);
  162. // 验证fund_to_uid必须存在
  163. if (empty($app->fund_to_uid)) {
  164. $order->updateStatus(TransferStatus::FAILED, '应用配置错误:未配置转入目标账户');
  165. throw new \Exception('应用配置错误:未配置转入目标账户(fund_to_uid)');
  166. }
  167. // 验证目标账户是否存在
  168. $targetFundService = new FundService($app->fund_to_uid, $app->fund_id);
  169. if (!$targetFundService->getAccount()) {
  170. $order->updateStatus(TransferStatus::FAILED, '目标账户不存在');
  171. throw new \Exception('目标账户不存在');
  172. }
  173. // 预先验证用户余额是否充足
  174. $userFundService = new FundService($data['user_id'], $app->fund_id);
  175. if ($userFundService->balance() < $order->actual_amount) {
  176. $order->updateStatus(TransferStatus::FAILED, '用户余额不足');
  177. throw new \Exception('用户余额不足');
  178. }
  179. // 使用trade方法从用户转账到目标账户(完整转出金额,不扣除手续费)
  180. $tradeResult = $userFundService->trade(
  181. $app->fund_to_uid,
  182. $order->amount, // 转出完整金额(不扣除手续费)
  183. 'transfer_out',
  184. $order->id,
  185. "转出到{$app->title}"
  186. );
  187. // 如果有手续费,处理手续费收取
  188. if ($order->hasFee()) {
  189. // 获取手续费收取账户UID(默认为1)
  190. $feeAccountUid = $app->getFeeAccountUid();
  191. $feeTradeResult = $userFundService->trade(
  192. $feeAccountUid,
  193. $order->fee_amount,
  194. 'transfer_out_fee',
  195. $order->id,
  196. "转出手续费-{$app->title}"
  197. );
  198. if (is_string($feeTradeResult)) {
  199. Log::warning('Transfer out fee collection failed', [
  200. 'order_id' => $order->id,
  201. 'fee_amount' => $order->fee_amount,
  202. 'fee_account_uid' => $feeAccountUid,
  203. 'error' => $feeTradeResult
  204. ]);
  205. } else {
  206. Log::info('Transfer out fee collected successfully', [
  207. 'order_id' => $order->id,
  208. 'fee_amount' => $order->fee_amount,
  209. 'fee_account_uid' => $feeAccountUid,
  210. 'configured_account' => $app->fee_account_uid
  211. ]);
  212. }
  213. }
  214. if (is_string($tradeResult)) {
  215. $order->updateStatus(TransferStatus::FAILED, '资金转移失败: ' . $tradeResult);
  216. throw new \Exception('余额不足或资金操作失败: ' . $tradeResult);
  217. }
  218. // 检查是否需要调用外部API
  219. if (!empty($app->order_out_create_url)) {
  220. // 配置了外部转出API,调用外部API处理
  221. OrderLogic::processTransferOut($order);
  222. } else {
  223. // 没有配置外部API,直接完成订单
  224. $order->updateStatus(TransferStatus::COMPLETED);
  225. }
  226. return $order;
  227. }
  228. /**
  229. * 创建转出订单(内部实现)
  230. * 我 =》 三方
  231. * 处理流程:
  232. * 1. 验证请求数据
  233. * 2. 获取应用配置
  234. * 3. 创建转出订单
  235. * 4. 验证fund_to_uid必须存在,进行用户间资金转移
  236. * 5. 根据是否配置外部API决定后续处理(调用外部API或直接完成)
  237. *
  238. * @param array $data 订单数据
  239. * @return TransferOrder
  240. * @throws \Exception
  241. */
  242. public static function createTransferOutFromArray(array $data): TransferOrder
  243. {
  244. // 验证请求数据
  245. $validation = new TransferOutValidation($data);
  246. $validation->validated();
  247. // 获取应用配置
  248. $app = TransferApp::findOrFail($data['transfer_app_id']);
  249. if (!$app->is_enabled) {
  250. throw new \Exception('应用已禁用');
  251. }
  252. // 计算金额(转出:内部金额转换为外部金额)
  253. $amount = (string)$data['amount']; // 内部金额
  254. // 验证汇率是否合理
  255. if (bccomp((string)$app->exchange_rate, '0', 10) <= 0) {
  256. throw new \Exception('汇率配置错误:汇率必须大于0');
  257. }
  258. // 验证内部金额是否合理
  259. if (bccomp($amount, '0', 10) <= 0) {
  260. throw new \Exception('转出金额必须大于0');
  261. }
  262. $outAmount = bcdiv($amount, (string)$app->exchange_rate, 10); // 外部金额 = 内部金额 ÷ 汇率
  263. // 验证计算结果是否合理
  264. if (bccomp($outAmount, '0', 10) <= 0) {
  265. throw new \Exception('汇率计算结果异常:外部金额必须大于0');
  266. }
  267. // 计算手续费
  268. $feeInfo = FeeService::calculateOutFee($app, $amount, [ 'user_id' => $data['user_id'] ]);
  269. // 生成外部订单ID
  270. $outOrderId = self::generateOutOrderId('OUT');
  271. // 创建订单
  272. $order = TransferOrder::create([
  273. 'transfer_app_id' => $app->id,
  274. 'out_id' => $app->out_id2 ?? 0,
  275. 'out_order_id' => $outOrderId,
  276. 'out_user_id' => $data['out_user_id'] ?? null,
  277. 'user_id' => $data['user_id'],
  278. 'currency_id' => $app->currency_id,
  279. 'fund_id' => $app->fund_id,
  280. 'type' => TransferType::OUT,
  281. 'status' => TransferStatus::CREATED,
  282. 'out_amount' => $outAmount,
  283. 'amount' => $amount,
  284. 'exchange_rate' => $app->exchange_rate,
  285. 'fee_rate' => $feeInfo->feeRate,
  286. 'fee_amount' => $feeInfo->feeAmount,
  287. 'actual_amount' => $feeInfo->actualAmount,
  288. 'totle_amount' => $feeInfo->totleAmount,
  289. 'callback_data' => $data['callback_data'] ?? [],
  290. 'remark' => $data['remark'] ?? null,
  291. ]);
  292. // 验证fund_to_uid必须存在
  293. if (empty($app->fund_to_uid)) {
  294. $order->updateStatus(TransferStatus::FAILED, '应用配置错误:未配置转入目标账户');
  295. throw new \Exception('应用配置错误:未配置转入目标账户(fund_to_uid)');
  296. }
  297. // 验证目标账户是否存在
  298. $targetFundService = new FundService($app->fund_to_uid, $app->fund_id);
  299. if (!$targetFundService->getAccount()) {
  300. $order->updateStatus(TransferStatus::FAILED, '目标账户不存在');
  301. throw new \Exception('目标账户不存在');
  302. }
  303. // 预先验证用户余额是否充足(转出金额 + 手续费)
  304. $userFundService = new FundService($data['user_id'], $app->fund_id);
  305. $totalRequired = bcadd($order->amount, $order->fee_amount, 10);
  306. if (bccomp((string)$userFundService->balance(), $totalRequired, 10) < 0) {
  307. $order->updateStatus(TransferStatus::FAILED, '用户余额不足');
  308. throw new \Exception('用户余额不足');
  309. }
  310. // 使用trade方法从用户转账到目标账户(完整转出金额,不扣除手续费)
  311. $tradeResult = $userFundService->trade(
  312. $app->fund_to_uid,
  313. $order->amount, // 转出完整金额(不扣除手续费)
  314. 'transfer_out',
  315. $order->id,
  316. "转出到{$app->title}"
  317. );
  318. // 如果有手续费,处理手续费收取
  319. if ($order->hasFee()) {
  320. // 获取手续费收取账户UID(默认为1)
  321. $feeAccountUid = $app->getFeeAccountUid();
  322. $feeTradeResult = $userFundService->trade(
  323. $feeAccountUid,
  324. $order->fee_amount,
  325. 'transfer_out_fee',
  326. $order->id,
  327. "转出手续费-{$app->title}"
  328. );
  329. if (is_string($feeTradeResult)) {
  330. Log::warning('Transfer out fee collection failed', [
  331. 'order_id' => $order->id,
  332. 'fee_amount' => $order->fee_amount,
  333. 'fee_account_uid' => $feeAccountUid,
  334. 'error' => $feeTradeResult
  335. ]);
  336. } else {
  337. Log::info('Transfer out fee collected successfully', [
  338. 'order_id' => $order->id,
  339. 'fee_amount' => $order->fee_amount,
  340. 'fee_account_uid' => $feeAccountUid,
  341. 'configured_account' => $app->fee_account_uid
  342. ]);
  343. }
  344. }
  345. if (is_string($tradeResult)) {
  346. $order->updateStatus(TransferStatus::FAILED, '资金转移失败: ' . $tradeResult);
  347. throw new \Exception('余额不足或资金操作失败: ' . $tradeResult);
  348. }
  349. // 检查是否需要调用外部API
  350. if (!empty($app->order_out_create_url)) {
  351. // 配置了外部转出API,调用外部API处理
  352. OrderLogic::processTransferOut($order);
  353. } else {
  354. // 没有配置外部API,直接完成订单
  355. $order->updateStatus(TransferStatus::COMPLETED);
  356. }
  357. return $order;
  358. }
  359. /**
  360. * 创建转入订单(对外接口)
  361. * 三方 =》 我
  362. *
  363. * @param int $transferAppId 划转应用ID
  364. * @param int $userId 用户ID
  365. * @param string $businessId 业务订单ID
  366. * @param string $amount 转入金额
  367. * @param string|null $outUserId 外部用户ID
  368. * @param string|null $remark 备注
  369. * @param array $callbackData 回调数据
  370. * @return TransferOrder
  371. * @throws \Exception
  372. */
  373. public static function createTransferIn(
  374. int $transferAppId,
  375. int $userId,
  376. string $out_order_id,
  377. string $amount,
  378. ?string $outUserId = null,
  379. ?string $remark = null,
  380. array $callbackData = []
  381. ): TransferOrder
  382. {
  383. $data = [
  384. 'transfer_app_id' => $transferAppId,
  385. 'user_id' => $userId,
  386. 'out_order_id' => $out_order_id,
  387. 'amount' => $amount,
  388. 'out_user_id' => $outUserId,
  389. 'remark' => $remark,
  390. 'callback_data' => $callbackData,
  391. ];
  392. return self::createTransferInFromArray($data, $userId);
  393. }
  394. /**
  395. * 创建转入订单(内部实现)
  396. * 三方 =》 我
  397. * 处理流程:
  398. * 1. 验证请求数据
  399. * 2. 获取应用配置
  400. * 3. 创建转入订单
  401. * 4. 验证fund_in_uid必须存在,从指定账户转账到用户
  402. * 5. 根据是否配置回调URL决定后续处理(发送回调或直接完成)
  403. *
  404. * @param array $data 订单数据
  405. * @return TransferOrder
  406. * @throws \Exception
  407. */
  408. public static function createTransferInFromArray(array $data, $user_id): TransferOrder
  409. {
  410. // 验证请求数据
  411. $validation = new TransferInValidation($data);
  412. $validation->validated();
  413. // 获取应用配置
  414. /**
  415. * @var TransferApp $app
  416. */
  417. $app = TransferApp::findOrFail($data['transfer_app_id']);
  418. if (!$app->is_enabled) {
  419. throw new \Exception('应用已禁用');
  420. }
  421. // 检查外部订单ID是否已存在
  422. $existingOrder = TransferOrder::where('out_order_id', $data['out_order_id'])
  423. ->where('out_id', $app->out_id2 ?? 0)
  424. ->first();
  425. if ($existingOrder) {
  426. throw new \Exception('订单已存在');
  427. }
  428. // 计算金额(转入:外部金额转换为内部金额)
  429. $outAmount = (string)$data['amount']; // 外部金额
  430. $amount = bcmul($outAmount, (string)$app->exchange_rate, 10); // 内部金额 = 外部金额 × 汇率
  431. // 计算手续费
  432. $feeInfo = FeeService::calculateInFee($app, $amount, [
  433. 'user_id' => $user_id
  434. ]);
  435. // 创建订单
  436. /**
  437. * @var TransferOrder $order
  438. */
  439. $order = TransferOrder::create([
  440. 'transfer_app_id' => $app->id,
  441. 'out_id' => $app->out_id2 ?? 0,
  442. 'out_order_id' => $data['out_order_id'] ?? null,
  443. 'out_user_id' => $data['out_user_id'] ?? null,
  444. 'user_id' => $data['user_id'],
  445. 'currency_id' => $app->currency_id,
  446. 'fund_id' => $app->fund_id,
  447. 'type' => TransferType::IN,
  448. 'status' => TransferStatus::CREATED,
  449. 'out_amount' => $outAmount,
  450. 'amount' => $feeInfo->amount,
  451. 'exchange_rate' => $app->exchange_rate,
  452. 'fee_rate' => $feeInfo->feeRate,
  453. 'fee_amount' => $feeInfo->feeAmount,
  454. 'actual_amount' => $feeInfo->actualAmount,
  455. 'totle_amount' => $feeInfo->totleAmount,
  456. 'callback_data' => $data['callback_data'] ?? [],
  457. 'remark' => $data['remark'] ?? null,
  458. ]);
  459. // 验证fund_in_uid必须存在
  460. if (empty($app->fund_in_uid)) {
  461. $order->updateStatus(TransferStatus::FAILED, '应用配置错误:未配置转入来源账户');
  462. throw new \Exception('应用配置错误:未配置转入来源账户(fund_in_uid)');
  463. }
  464. // 验证来源账户是否存在
  465. $sourceFundService = new FundService($app->fund_in_uid, $app->fund_id);
  466. if (!$sourceFundService->getAccount()) {
  467. $order->updateStatus(TransferStatus::FAILED, '来源账户不存在');
  468. throw new \Exception('来源账户不存在');
  469. }
  470. // 验证来源账户余额是否充足
  471. if ($sourceFundService->balance() < $order->totle_amount) {
  472. $order->updateStatus(TransferStatus::FAILED, '来源账户余额不足');
  473. throw new \Exception('来源账户余额不足');
  474. }
  475. // 使用trade方法从来源账户转账到用户账户(扣除手续费后的实际金额)
  476. $tradeResult = $sourceFundService->trade(
  477. $data['user_id'],
  478. $order->actual_amount, // 转入实际到账金额(扣除手续费后)
  479. 'transfer_in',
  480. $order->id,
  481. "从{$app->title}转入"
  482. );
  483. // 如果有手续费,处理手续费收取
  484. if ($order->hasFee()) {
  485. // 获取手续费收取账户UID(默认为1)
  486. $feeAccountUid = $app->getFeeAccountUid();
  487. $feeTradeResult = $sourceFundService->trade(
  488. $feeAccountUid,
  489. $order->fee_amount,
  490. 'transfer_in_fee',
  491. $order->id,
  492. "转入手续费-{$app->title}"
  493. );
  494. if (is_string($feeTradeResult)) {
  495. Log::warning('Transfer in fee collection failed', [
  496. 'order_id' => $order->id,
  497. 'fee_amount' => $order->fee_amount,
  498. 'fee_account_uid' => $feeAccountUid,
  499. 'error' => $feeTradeResult
  500. ]);
  501. } else {
  502. Log::info('Transfer in fee collected successfully', [
  503. 'order_id' => $order->id,
  504. 'fee_amount' => $order->fee_amount,
  505. 'fee_account_uid' => $feeAccountUid,
  506. 'configured_account' => $app->fee_account_uid
  507. ]);
  508. }
  509. }
  510. if (is_string($tradeResult)) {
  511. $order->updateStatus(TransferStatus::FAILED, '资金转移失败: ' . $tradeResult);
  512. throw new \Exception('来源账户资金不足或操作失败: ' . $tradeResult);
  513. }
  514. // 检查是否需要发送回调通知
  515. if ($app->supportsCallback()) {
  516. // 配置了回调URL,更新状态为处理中并发送回调
  517. $order->updateStatus(TransferStatus::PROCESSING);
  518. CallbackLogic::sendCallback($order);
  519. } else {
  520. // 没有配置回调URL,直接完成订单
  521. $order->updateStatus(TransferStatus::COMPLETED);
  522. }
  523. return $order;
  524. }
  525. /**
  526. * 处理回调结果
  527. *
  528. * @param array $callbackData 回调数据
  529. * @return bool
  530. */
  531. public static function processCallback(array $callbackData): bool
  532. {
  533. // 查找订单
  534. $order = TransferOrder::where('out_order_id', $callbackData['out_order_id'])
  535. ->where('out_id', $callbackData['out_id'])
  536. ->first();
  537. if (!$order) {
  538. Log::warning('Transfer callback order not found', $callbackData);
  539. return false;
  540. }
  541. // 更新订单状态
  542. $status = $callbackData['success'] ? TransferStatus::COMPLETED : TransferStatus::FAILED;
  543. $message = $callbackData['message'] ?? null;
  544. return $order->updateStatus($status, $message);
  545. }
  546. /**
  547. * 生成外部订单ID
  548. *
  549. * @param string $prefix 前缀
  550. * @return string
  551. */
  552. private static function generateOutOrderId(string $prefix = 'TR'): string
  553. {
  554. return $prefix . date('YmdHis') . Str::random(6);
  555. }
  556. /**
  557. * 重试订单处理
  558. *
  559. * @param int $orderId 订单ID
  560. * @return bool
  561. */
  562. public static function retryOrder(int $orderId): bool
  563. {
  564. $order = TransferOrder::findOrFail($orderId);
  565. if (!$order->canRetry()) {
  566. throw new \Exception('订单状态不允许重试');
  567. }
  568. // 重置状态为已创建
  569. $order->updateStatus(TransferStatus::CREATED);
  570. // 根据订单类型重新处理
  571. if ($order->isTransferOut()) {
  572. OrderLogic::processTransferOut($order);
  573. } else {
  574. // 转入订单重试主要是重新发送回调
  575. if ($order->transferApp->supportsCallback()) {
  576. CallbackLogic::sendCallback($order);
  577. }
  578. }
  579. return true;
  580. }
  581. /**
  582. * 手动完成订单
  583. *
  584. * @param int $orderId 订单ID
  585. * @param string $remark 备注
  586. * @return bool
  587. */
  588. public static function manualComplete(int $orderId, string $remark = ''): bool
  589. {
  590. $order = TransferOrder::findOrFail($orderId);
  591. if ($order->isFinalStatus()) {
  592. throw new \Exception('订单已处于最终状态');
  593. }
  594. // 更新状态为已完成
  595. $order->updateStatus(TransferStatus::COMPLETED, $remark);
  596. return true;
  597. }
  598. }