config = HuifuConfig::getInstance(); $this->client = new HuifuClient(); } public function executeByOrderId($orderId) { $order = Order::get($orderId); if (!$order || $order['pay_type'] !== 'huifu_wechat') { return false; } $exists = SplitOrder::where('order_id', $orderId)->where('channel', 'huifu')->where('status', 'in', [1, 2])->find(); if ($exists) { return true; } SplitOrder::where('order_id', $orderId)->where('channel', 'huifu')->where('status', 4)->delete(); $calculator = new SplitCalculator(); $items = $calculator->calculate($orderId); if (empty($items)) { $rewardCount = \addons\shopro\model\commission\Reward::where('order_id', $orderId)->where('status', '>=', 0)->count(); $regionalCount = \addons\shopro\model\regional_agent\Reward::where('order_id', $orderId)->where('status', '>=', 0)->count(); if ($rewardCount + $regionalCount > 0) { \think\Log::warning('[shopro_huifu] split skipped order_id=' . $orderId . ' reason=receiver_missing_or_no_huifu_id'); } return false; } $payFee = number_format((float) $order['pay_fee'], 2, '.', ''); $acctInfos = $this->buildAcctInfos($items, $payFee); $splitAmount = '0.00'; foreach ($acctInfos as $info) { $splitAmount = bcadd($splitAmount, $info['div_amt'], 2); } $outOrderNo = 'HFPS' . date('YmdHis') . $orderId; $ext = HuifuPayService::getPayExt($orderId); Db::startTrans(); try { $splitOrder = SplitOrder::create([ 'order_id' => $orderId, 'order_sn' => $order['order_sn'], 'channel' => 'huifu', 'transaction_id' => $order['transaction_id'], 'out_order_no' => $outOrderNo, 'total_amount' => $order['pay_fee'], 'split_amount' => $splitAmount, 'status' => 1, ]); foreach ($items as $item) { SplitOrderReceiver::create([ 'split_order_id' => $splitOrder->id, 'receiver_id' => $item['receiver_id'], 'role_type' => $item['role_type'], 'amount' => $item['amount'], 'description' => $item['description'], 'status' => 0, ]); } $confirmResult = $this->confirm($ext, $acctInfos, $outOrderNo); $splitOrder->status = 2; $splitOrder->confirm_hf_seq_id = $confirmResult['hf_seq_id'] ?? ($confirmResult['confirm_hf_seq_id'] ?? ''); $splitOrder->hf_req_seq_id = $outOrderNo; $splitOrder->wx_result = json_encode($confirmResult, JSON_UNESCAPED_UNICODE); $splitOrder->save(); SplitOrderReceiver::where('split_order_id', $splitOrder->id)->update(['status' => 1]); HuifuPayService::savePayExt($orderId, $order['order_sn'], [ 'confirm_hf_seq_id' => $splitOrder->confirm_hf_seq_id, 'confirm_status' => 'S', ]); Db::commit(); return true; } catch (\Exception $e) { Db::rollback(); $this->markFailed($orderId, $outOrderNo, $order, $items, $e->getMessage()); return false; } } public function retry($splitOrderId) { $splitOrder = SplitOrder::with('receivers')->get($splitOrderId); if (!$splitOrder || $splitOrder['channel'] !== 'huifu') { return false; } return $this->executeByOrderId($splitOrder['order_id']); } protected function confirm($ext, array $acctInfos, $confirmReqSeqId) { SdkLoader::requireRequest('V2TradePaymentDelaytransConfirmRequest.php'); $request = new \BsPaySdk\request\V2TradePaymentDelaytransConfirmRequest(); $request->setReqDate(date('Ymd')); $request->setReqSeqId($confirmReqSeqId); $request->setHuifuId($this->config->get('huifu_id')); $acctSplitBunch = json_encode(['acct_infos' => $acctInfos], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); $request->setExtendInfo([ 'org_req_date' => $ext['req_date'], 'org_req_seq_id' => $ext['req_seq_id'], 'org_hf_seq_id' => $ext['hf_seq_id'] ?? '', 'acct_split_bunch' => $acctSplitBunch, ]); $result = $this->client->postRequest($request); return $this->client->parseResponse($result); } protected function buildAcctInfos(array $items, $payFee) { $infos = []; $allocated = '0.00'; foreach ($items as $item) { $amt = number_format((float) $item['amount'], 2, '.', ''); if (bccomp($amt, '0.00', 2) <= 0) { continue; } $infos[] = [ 'huifu_id' => $item['receiver_account'], 'div_amt' => $amt, ]; $allocated = bcadd($allocated, $amt, 2); } $platformHuifuId = (new SplitConfig())->all()['platform_huifu_id'] ?? $this->config->get('huifu_id'); $remain = bcsub($payFee, $allocated, 2); if (bccomp($remain, '0.00', 2) > 0) { $infos[] = [ 'huifu_id' => $platformHuifuId, 'div_amt' => $remain, ]; } $sum = '0.00'; foreach ($infos as $info) { $sum = bcadd($sum, $info['div_amt'], 2); } if (bccomp($sum, $payFee, 2) !== 0) { throw new \RuntimeException('分账金额之和必须等于订单实付'); } return $infos; } public function querySplit($hfSeqId) { SdkLoader::requireRequest('V2TradeTransSplitQueryRequest.php'); $request = new \BsPaySdk\request\V2TradeTransSplitQueryRequest(); $request->setHuifuId($this->config->get('huifu_id')); $request->setHfSeqId($hfSeqId); $request->setOrdType('consume'); $result = $this->client->postRequest($request); return $this->client->parseResponse($result); } protected function markFailed($orderId, $outOrderNo, $order, $items, $message) { $splitOrder = SplitOrder::where('order_id', $orderId)->where('channel', 'huifu')->find(); $splitConfig = new SplitConfig(); if (!$splitOrder) { SplitOrder::create([ 'order_id' => $orderId, 'order_sn' => $order['order_sn'], 'channel' => 'huifu', 'transaction_id' => $order['transaction_id'] ?? '', 'out_order_no' => $outOrderNo, 'total_amount' => $order['pay_fee'], 'split_amount' => array_sum(array_column($items, 'amount')), 'status' => 4, 'wx_result' => $message, 'retry_count' => 1, 'next_retry_time'=> time() + $splitConfig->getRetryInterval(), ]); } else { $splitOrder->status = 4; $splitOrder->wx_result = $message; $splitOrder->retry_count = $splitOrder['retry_count'] + 1; $splitOrder->next_retry_time = time() + $splitConfig->getRetryInterval(); $splitOrder->save(); } } }