config_service)) { return $this->config_service = new TaxConfigService(['mall_id' => $this->mall_id]); } return $this->config_service; } /** * @return LuYongService */ public function getLyService() { if (empty($this->ly_service)) { return $this->ly_service = new LuYongService(['mall_id' => $this->mall_id]); } return $this->ly_service; } /** * @return AlipayService */ public function getAlipayService() { if (empty($this->alipay_service)) { return $this->alipay_service = new AlipayService($this->mall_id); } return $this->alipay_service; } /** * @return XinLongService */ public function getXlService() { if (empty($this->xl_service)) { return $this->xl_service = new XinLongService(['mall_id' => $this->mall_id]); } return $this->xl_service; } /** * 获取订单列表 * * @param array $params * @return array */ public function getOrderList($params) { $params = array_filter($params, function ($val) { return ($val === '' || $val === null) ? false : true; }); $limit = $this->getLimit($params); $page = $this->getPage($params); $data = TaxOrder::getPageList($page, $limit, function($query) use ($params) { $query->with(['account', 'withdraw' => function ($q) { return $q->select('id, order_no'); }]) ->where(['mall_id' => $this->mall_id, 'sub_type' => TaxOrder::SUB_TYPE_USER]); // 标题查询 !empty($params['title']) && $query->andWhere(['like', 'title', $params['title']]); // 批次号查询 !empty($params['batch_num']) && $query->andWhere(['like', 'batch_num', $params['batch_num']]); !empty($params['order_sn']) && $query->andWhere(['like', 'order_sn', $params['order_sn']]); !empty($params['status']) && $query->andWhere(['=', 'status', $params['status']]); !empty($params['mobile']) && $query->andWhere(['=', 'mobile', $params['mobile']]); !empty($params['name']) && $query->andWhere(['=', 'name', $params['name']]); if (!empty($params['date'])) { $query->andWhere(['>=', 'created_at', strtotime($params['date'][0])]); $query->andWhere(['<=', 'created_at', strtotime($params['date'][1])]); } }, 'id desc'); foreach ($data['list'] as $k => $v) { $data['list'][$k]['status_text'] = StatusEnum::getMap()[$v['status']]; $data['list'][$k]['type_text'] = OrderTypeEnum::getMap()[$v['type']]; } return $data; } /** * 批量提现 * * @param array $params * [ * mall_id => int, * ids => array, * ] * @return void */ public static function batchWithdraw(array $params) { $ids = $params['ids']; $mall_id = $params['mall_id']; foreach ($ids as $id) { $withdraw_log = UserWithdrawLog::getTaxLogByAgree($mall_id, $id); if (empty($withdraw_log)) { return [false, 'ID:' . $id . ' 提现记录不存在']; } // 账户信息 $extra = Json::decode($withdraw_log->extra); if (empty($extra)) { return [false, '会员ID:' . $withdraw_log['user_id'] . ' 支付宝信息有误']; } } // 添加异步队列 foreach ($ids as $id) { $data = compact('mall_id', 'id'); Yii::$app->services->rabbitMq->push( RabbitMqEnum::EXCHANGE_TASK, RabbitMqEnum::TASK_QUEUE, $data, self::class, 'handleWithdraw' ); } return [true, '']; } /** * 处理提现 * * @param array $data * [ * id => int, 提现记录ID * mall_id => int, 商城ID * ] * @return void */ public function handleWithdraw(array $data) { $id = $data['id']; $this->mall_id = $data['mall_id']; $config_service = $this->getConfigService(); $alipay_service = $this->getAlipayService(); Yii::$app->db->close(); try { $withdraw_log = UserWithdrawLog::getTaxLogByAgree($this->mall_id, $id); // 查询类型(插件使用的类型) $type = $config_service->getType(); // 低于2元不能提现(手续费低于0.1元不能转账) if ($withdraw_log->num < 2) { // 提现记录失败 // UserWithdrawLog::remit_fail($withdraw_log, '合规提现不能低于2元'); // return true; } // 创建提现订单、生成税务方手续费订单 $order = $this->createOrder($withdraw_log); if ($order === false) { // 提现记录失败 UserWithdrawLog::remit_fail($withdraw_log, '创建订单失败:' . $this->getError()); return true; } // 如果是自用版本 检测支付宝余额 if ($type == TaxConfig::PERSONAL) { if ($this->checkAccountAmount($order) === false) { UserWithdrawLog::remit_fail($withdraw_log, $this->getError()); return true; } } // 如果是服务商版本 检测用工方余额 if ($type == TaxConfig::SERVICE) { if ($this->checkBookAmount($order) === false) { UserWithdrawLog::remit_fail($withdraw_log, $this->getError()); return true; } } // 提交订单 if ($this->subOrder($order) === false) { TaxOrderLog::addLog($order->batch_num, "向税务方提交风控交易失败:" . $this->getError()); TaxOrder::transError($order->batch_num, null, $this->getError()); // 提现记录失败 UserWithdrawLog::remit_fail($withdraw_log, '提交订单失败:' . $this->getError()); return true; } // 充值(企业支付宝 到 记账本) $response = $alipay_service->bookQuery($order->book->account_book_id, $order->book->agreement_no); // 查询的是税务方的记账本余额 $balance = $response->available_amount ?? 0; // 余额 大于 打款金额 ,不用充值。否则需要充值 if ($balance < ($order->amount)) { // 自用版 if ($type == TaxConfig::PERSONAL) { // 企业支付宝 打款到 记账本 $res = $this->transBook($order); if ($res === false) { TaxOrderLog::addLog($order->batch_num, "企业支付宝转账到记账本失败:" . $this->getError()); TaxOrder::transError($order->batch_num, null, $this->getError()); // 提现记录失败 UserWithdrawLog::remit_fail($withdraw_log, $this->getError()); return true; } } // 服务商版 if ($type == TaxConfig::SERVICE) { // 从用工方记账本转账到税务方记账本 $res = $this->transBookFromBook($order); if ($res === false) { TaxOrderLog::addLog($order->batch_num, "记账本转账到税务记账本失败:" . $this->getError()); TaxOrder::transError($order->batch_num, null, $this->getError()); // 提现记录失败 UserWithdrawLog::remit_fail($withdraw_log, $this->getError()); return true; } } } // 记账本出款到用户账户 $res = $this->byAlipayBookToTransUser($order); if (!$res) { // 可能支付宝账户有误 TaxOrderLog::addLog($order->batch_num, "记账本出款到用户账户失败:" . $this->getError()); TaxOrder::transError($order->batch_num, null, $this->getError()); UserWithdrawLog::remit_fail($withdraw_log, $this->getError()); return true; } // 打款成功 UserWithdrawLog::remit_success($withdraw_log); $res = TaxOrder::transSuccess(null, $order->order_sn); // 自用版手续费打款 if ($type == TaxConfig::PERSONAL) { $this->transToTax($order->batch_num); } // 服务商版手续费打款 if ($type == TaxConfig::SERVICE) { $this->bookTransToTax($order->batch_num); } return true; } catch (Exception $e) { dd($e); $this->error($e->getMessage()); $exception = FormatHelper::exception($e, '支付宝合规队列提现'); Yii::error($exception); return true; } return true; } /** * 创建订单 * * @param UserWithdrawLog $withdraw_log * @return TaxOrder|false */ protected function createOrder($withdraw_log) { // 批次号 $batch_num = TaxOrder::getBatchNum(); // 默认记帐本 $book = TaxAccount::getDefaultAccount($this->mall_id); if (empty($book)) { return $this->error('未设置默认记帐本'); } // 签约查询需要重新写 $sign = $this->getSignUser($withdraw_log->user_id); if (empty($sign)) { return $this->error(" {$withdraw_log->user_id} 用户未签约 "); } $fee = self::makeFee($withdraw_log->actual_num, $this->getConfigService()->getTaxFee()); if ($fee <= 0) $fee = 0.1; if (($model = TaxOrder::addUserOrder($batch_num, $book->id, $fee, $withdraw_log)) == false) { return $this->error('创建用户支付宝订单失败'); } if (TaxOrder::addTaxOrder($batch_num, $book->id, $fee, $withdraw_log, $this->config_service) == false) { return $this->error('创建手续费订单失败'); } // 风险检测 if ($this->riskCheck($model) === false) { return $this->error('风险检测未通过' . $this->getError()); } return $model; } /** * 风险检测 * * @param TaxOrder $order 必须是用户打款的订单 * @return boolean */ protected function riskCheck(TaxOrder $order) { $type = $this->getConfigService()->getApiType(); if ($type == TaxConfig::XIONLONG) { // xinlong不需要检测? return true; } if ($type == TaxConfig::LUYONG) { $sign = $this->getSignUser($order->user_id); if (empty($sign)) { return $this->error('用户未签约'); } if ($this->getLyService()->riskCheck($order, $sign) === false) { return $this->error('打款批次风控接口检测失败' . $this->getError()); } } return true; } /** * 获取用户签约信息 * * @param integer $user_id * @return TaxUserAuth|LySilentSign|null */ protected function getSignUser(int $user_id) { $type = $this->getConfigService()->getApiType(); if (!empty($this->sign_users[$type][$user_id])) { return $this->sign_users[$type][$user_id]; } if ($type == TaxConfig::XIONLONG) { return $this->sign_users[TaxConfig::XIONLONG][$user_id] = TaxUserAuth::getAccountUser($this->mall_id, $user_id, TaxUserAuth::STATUS_OK); } if ($type == TaxConfig::LUYONG) { return $this->sign_users[TaxConfig::LUYONG][$user_id] = LySilentSign::getSignUser($user_id); } return null; } /** * 提交订单 * * @param TaxOrder $order * @return boolean */ protected function subOrder(TaxOrder $order) { $type = $this->getConfigService()->getApiType(); if ($type == TaxConfig::XIONLONG) { if ($this->getXlService()->submitOrder($order->batch_num) === false) { return $this->error($this->getXlService()->getError()); } } if ($type == TaxConfig::LUYONG) { if ($this->getLyService()->asyncTransferNotify($order->batch_num) == false) { return $this->error('支付时异步请求接口请求失败'); } } return true; } /** * 检测支付宝余额是否充足 * * @param TaxOrder $order * @return boolean */ protected function checkAccountAmount(TaxOrder $order) { $alipay_user_id = $this->getConfigService()->getAlipayUserId(); if (empty($alipay_user_id)) { return $this->error('未设置支付宝账号ID'); } $account = $this->getAlipayService()->accountQuery($alipay_user_id); if ($account === false) { return $this->error('支付宝账号ID设置错误'); } // 余额不足 if ($account->available_amount < bcadd($order->amount, $order->pay_fee, 2)) { return $this->error('支付宝账户余额不足, 请充值'); } return true; } /** * 检测记账本余额是否充足 * * @param TaxOrder $order * @return boolean */ protected function checkBookAmount(TaxOrder $order) { $employ = TaxAccountBook::getEmploy($this->mall_id); $response = $this->getAlipayService()->bookQuery($employ->account_book_id, $employ->agreement_no); // 查询的是税务方的记账本余额 $balance = $response->available_amount ?? 0; // 余额不足 if ($balance < bcadd($order->amount, $order->pay_fee, 2)) { return $this->error('记账本余额不足, 请充值'); } return true; } public static function makeFee($money, $rate = TaxOrder::TAX_ALI_ACCOUNT_RATE) { $rate = bcdiv((string)$rate, 100, 4); $fee = bcmul((string)$money, $rate, 2); return $fee <= 0 ? '0.1' : $fee; } /** * 将企业支付的资金转入记账本 * * @param TaxOrder $order * @return boolean */ protected function transBook(TaxOrder $order) { // 仅出款不转账到记帐本 if ($order->account_type == TaxOrder::ORDER_TYPE_MANUAL) { return true; } $service = $this->getAlipayService(); $book_batch_num = $order->batch_num; // 获取转账的总金额,该金额包含 给用户的和给税务方手续费的 $amount = TaxOrder::find()->where(['order_sn' => $order->order_sn])->sum('amount'); // 记账本资金专款拨入(商户给人力资源记账本转账) $response = $service->transUniTransferBook( $book_batch_num, $order->book->account_book_id, $order->title, $order->book->agreement_no, $amount, $order->remarks ? $order->remarks : $order->title ); if ($response === false) { return $this->error($service->getError()); } // 记录转账数据 $res = TaxOrderBook::addOrder([ 'mall_id' => $this->mall_id, 'out_biz_no' => $book_batch_num, 'order_id' => $order->id, 'identity' => $order->book->account_book_id, 'agreement_no' => $order->book->agreement_no, 'amount' => $order->amount, ]); if ($res === false) { Yii::error('支付宝合规 - 添加转账记录失败' . __LINE__); // throw new Exception('添加转账记录失败'); } return true; } /** * 从用工方记账本转账到税务方记账本 * * @param TaxOrder $order * @return boolean */ protected function transBookFromBook(TaxOrder $order) { // 仅出款不转账到记帐本 if ($order->account_type == TaxOrder::ORDER_TYPE_MANUAL) { return true; } $service = $this->getAlipayService(); $book_batch_num = $order->batch_num; // 打款金额 $amount = $order->amount; // 获取双方记账本 $payer = TaxAccountBook::getEmploy($this->mall_id); $payee = TaxAccountBook::getTaxation($this->mall_id); // 记账本之间相互调拨 $response = $service->transBookFromBook( $book_batch_num, $amount, '转账到记账本', $payee->account_book_id, $payee->agreement_no, $payer->account_book_id, $payer->agreement_no, ); if ($response === false) { return $this->error($service->error); } // 记录转账数据 $res = TaxOrderBook::addOrder([ 'mall_id' => $this->mall_id, 'out_biz_no' => $book_batch_num, 'order_id' => $order->id, 'identity' => $payer->account_book_id, 'agreement_no' => $payer->agreement_no, 'amount' => $order->amount, ]); if ($res === false) { Yii::error('支付宝合规 - 添加转账记录失败' . __LINE__); } return true; } /** * 从记账本将资金转到用户 * * @param TaxOrder $order * @return boolean */ protected function byAlipayBookToTransUser(TaxOrder $order) { $service = new AlipayService($this->mall_id); // 代发到户 $response = $service->bookTransUniTransferUser( $order->order_sn, $order->amount, '服务费', [ // 用户数据 'name' => $order->name, 'identity' => $order->mobile, ], [ // 记账本数据 'account_book_id' => $order->book->account_book_id, 'agreement_no' => $order->book->agreement_no ], '服务费' ); if ($response === false) { TaxOrder::transError(null, $order->order_sn, $service->error); return $this->error($service->error); } $order->addAlipayResponse($response); return true; } /** * 异步处理 * 用户打款成功之后,需要给税务方打手续费 * * @param string $batch_num * @return boolean */ protected function transToTax(string $batch_num) { $tax_order = TaxOrder::findOne(['batch_num' => $batch_num, 'sub_type' => TaxOrder::SUB_TYPE_TAX]); if (empty($tax_order)) return $this->error('手续费订单不存在'); // 创建订单的时候已经从支付宝转入记账本了,就不用重新转,直接出款就行了 $res = $this->byAlipayToTransTax($tax_order); if (!$res) { // Yii::error("给税务方转手续费失败2:" . $tax_order->batch_num); TaxOrderLog::addLog($tax_order->order_sn, "给税务方转手续费失败:" . $this->error); return false; } TaxOrder::transSuccess(null, $tax_order->order_sn); // 给税务方发送支付成功通知 if ($this->orderSuccess($tax_order) === false) { TaxOrderLog::addLog($tax_order->order_sn, "向税务方发送订单完成失败:" . $this->getError()); return false; } return true; } /** * 手续费打款 * * @param string $batch_num * @return boolean */ protected function bookTransToTax(string $batch_num) { $tax_order = TaxOrder::findOne(['batch_num' => $batch_num, 'sub_type' => TaxOrder::SUB_TYPE_TAX]); if (empty($tax_order)) return; // 创建订单的时候已经从支付宝转入记账本了,就不用重新转,直接出款就行了 $res = $this->byBookToTransTax($tax_order); if (!$res) { // Yii::error("给税务方转手续费失败2:" . $tax_order->batch_num); TaxOrderLog::addLog($tax_order->order_sn, "给税务方转手续费失败:" . $this->getError()); return false; } TaxOrder::transSuccess(null, $tax_order->order_sn); // 给税务方发送支付成功通知 if ($this->orderSuccess($tax_order) === false) { TaxOrderLog::addLog($tax_order->order_sn, "向税务方发送订单完成失败:" . $this->getError()); return false; } return true; } /** * 订单完成 通知税务方 * * @param TaxOrder $order * @return boolean */ protected function orderSuccess(TaxOrder $order) { $type = $this->getConfigService()->getApiType(); if ($type == TaxConfig::XIONLONG) { if ($this->getXlService()->pushPayFinish($order->batch_num) === false) { return $this->error($this->getXlService()->getError()); } } if ($type == TaxConfig::LUYONG) { if ($this->getLyService()->asyncTransferResultNotify($order) === false) { return $this->error('支付时异步请求接口请求失败'); } } return true; } /** * 手续费转账给税务公司 * * @param TaxOrder $order * @return boolean */ protected function byAlipayToTransTax(TaxOrder $order) { $service = $this->getAlipayService(); $config = $this->getConfigService()->getConfig(); if ($config['settle'] == 1) { $product_code = 'TRANS_ACCOUNT_NO_PWD'; // 支付宝 $payee = [ 'identity_type' => 'ALIPAY_LOGON_ID', // 用户id 'identity' => $config['tax_config']['taxAccount'], 'name' => $config['tax_config']['taxAccountName'], ]; } if ($config['settle'] == 2) { $product_code = 'TRANS_BANKCARD_NO_PWD'; // 银行卡 $payee = [ 'identity_type' => 'BANKCARD_ACCOUNT', // 用户id 'identity' => $config['tax_config']['bankCard'], 'name' => $config['tax_config']['bankName'], 'bankcard_ext_info' => $config['tax_config']['bank'], 'account_type' => '2', ]; } // 代发到户 $response = $service->transUniTransferUser( $order->order_sn, $order->amount, '服务费', $payee, $product_code ); if ($response === false) { // TaxOrder::transError(null, $order->order_sn, $service->error); $order->transTaxFeeError($service->getError()); return $this->error($service->getError()); } $order->addAlipayResponse($response); return true; } /** * 手续费转账给税务公司(记账本打款) * * @param TaxOrder $order * @return boolean */ protected function byBookToTransTax(TaxOrder $order) { $service = $this->getAlipayService(); $config = $this->getConfigService()->getConfig(); if ($config['settle'] == 1) { $product_code = 'TRANS_ACCOUNT_NO_PWD'; // 支付宝 $payee = [ 'identity_type' => 'ALIPAY_LOGON_ID', // 用户id 'identity' => $config['tax_config']['taxAccount'], 'name' => $config['tax_config']['taxAccountName'], ]; } if ($config['settle'] == 2) { // 目前没有2 $product_code = 'TRANS_BANKCARD_NO_PWD'; // 银行卡 $payee = [ 'identity_type' => 'BANKCARD_ACCOUNT', // 用户id 'identity' => $config['tax_config']['bankCard'], 'name' => $config['tax_config']['bankName'], 'bankcard_ext_info' => $config['tax_config']['bank'], 'account_type' => '2', ]; } // 用工方记账本打款 $payer = TaxAccountBook::getEmploy($this->mall_id); // 代发到户 $response = $service->bookTransUniTransferUser( $order->order_sn, $order->amount, '服务费', $payee, $payer ); if ($response === false) { // TaxOrder::transError(null, $order->order_sn, $service->error); $order->transTaxFeeError($service->error); return $this->error($service->error); } $order->addAlipayResponse($response); return true; } /** * 返回提现内容 * * @param array $params * @return array */ public static function withdrawChannel($params) { $mall_id = $params['mall_id']; $config = TaxConfig::read($mall_id); $is_install = 0; !empty($config) && $is_install = 1; return [ 'avoidtax' => ['is_install' => $is_install] ]; } }