TransferLogic.php 23 KB

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