StatusEnum::ENABLED, 'mall_id' => $this->mall_id] ]; $this->getAllPayTypeRefundListWhere($get, $where); // 默认本月 $get['daterange'] = !empty($get['daterange']) ? $get['daterange'] : array_values(DateHelper::thisMonth()); array_walk($get['daterange'], function (&$item) { $item = date('Ymd', !is_numeric($item) ? strtotime($item) : $item); }); if ($isGetAll) { $list = PaymentTypeRefundStatistics::lists([ 'where' => $where ]); $this->formatAllPayTypeRefundList($list, $get['daterange']); } else { $list = PaymentTypeRefundStatistics::listPage([ 'where' => $where ]); $this->formatAllPayTypeRefundList($list['list'], $get['daterange']); // 返回总数 $list['total_num'] = $this->getTotalNum($get); } return $list; } /** * 返回搜索下拉 可选择的具体支付方式(如 汇聚-微信) * * @return array */ public function getPayIdentifierOption(): array { $statisticsList = PaymentTypeRefundStatistics::find() ->where(['mall_id' => $this->mall_id, 'status' => StatusEnum::ENABLED]) ->select('payment_identifier,is_pay_group') ->asArray() ->all(); $payTypeOption = []; // 拆分,并组装搜索的select(格式:7|WEIXIN_XCX,16|W06-CASHIER) foreach ($statisticsList as $item) { $payTypeOption[] = [ 'value' => $item['payment_identifier'], 'label' => implode('+', $this->getPaymentTypeByPaymentIdentifier($item['payment_identifier'])) ]; } return $payTypeOption; } /** * 返回搜索下拉 可选择的支付方式 * * @return array */ public function getPayTypeOption(): array { $payTypeMap = PayTypeEnum::getMap(); $payType = []; foreach ($payTypeMap as $key => $value) { $showPayType = [ PayTypeEnum::WECHAT, PayTypeEnum::ALI, PayTypeEnum::JOINPAY, PayTypeEnum::MIXED, PayTypeEnum::HUIFUPAY, PayTypeEnum::BALANCE, PayTypeEnum::SCORE, PayTypeEnum::ON_LINE, PayTypeEnum::OFFLINE, PayTypeEnum::JOINPAY_FAST_PAY ]; if (!in_array($key, $showPayType)) { continue; } $payType[] = [ 'label' => $value, 'value' => $key ]; } return $payType; } /** * 返回支付方式 具体的支付方式文字(如 汇聚-微信) * * @param int $payType * @param string $tradeType * * @return string */ private function getTradeType(int $payType, string $tradeType): string { $map = []; switch ($payType) { case PayTypeEnum::WECHAT: $map = WechatPayTypeEnum::getMap(); break; case PayTypeEnum::ALI: $map = AliPayPayTypeEnum::getMap(); break; case PayTypeEnum::JOINPAY: if (MallAddons::isInstall($this->mall_id, AddonsEnum::ADDONS_NAME_JOIN_PAY)) { $map = JoinPayTypeEnum::getMap(); } break; case PayTypeEnum::YUNFAST: if (MallAddons::isInstall($this->mall_id, 'YunfastPay')) { $map = YunfastPayTypeEnum::getMap(); } break; case PayTypeEnum::HUIFUPAY: if (MallAddons::isInstall($this->mall_id, AddonsEnum::ADDONS_NAME_HUIFU_PAY)) { $map = [ 'h5_wechat' => '微信h5', 'h5_alipay' => '支付宝h5', 'app_wechat' => '微信app', 'app_alipay' => '支付宝app', 'wechat' => '微信公众号', 'T_MINIAPP' => '微信小程序', 'mp_wechat' => '微信小程序', 'fast_pay' => '快捷支付', 'big_pay' => '大额支付', 'balance_pay' => '余额支付', ]; } break; } return $map[$tradeType] ?? ''; } private function getAllPayTypeRefundListWhere(array $params, array &$where) { // 主表筛选 if (isset($params['payment_type']) && is_numeric($params['payment_type'])) { $where[] = ['=', 'payment_type', $params['payment_type']]; } if (!empty($params['payment_identifier'])) { $where[] = ['=', 'payment_identifier', $params['payment_identifier']]; } if (!empty($params['id'])) { $where[] = ['=', 'id', $params['id']]; } } /** * @param array $list * @param array $date * * @return void */ private function formatAllPayTypeRefundList(array &$list, array $date) { $baseWhere = [ [ 'status' => StatusEnum::ENABLED, 'mall_id' => $this->mall_id, 'statistics_id' => array_column($list, 'id') ] ]; foreach ($list as &$item) { $paymentIdentifierArr = explode(',', $item['payment_identifier']); foreach ($paymentIdentifierArr as $paymentIdentifierItem) { ['payType' => $payType, 'tradeType' => $tradeType] = $this->returnPaymentTypeAndTradeType($paymentIdentifierItem); $label = $this->getPaymentTypeAndTradeTypeText($payType, $tradeType); $item['total_refund_arr'][$paymentIdentifierItem] = [ 'label' => $label, 'money' => 0 ]; $item['total_pay_arr'][$paymentIdentifierItem] = [ 'label' => $label, 'money' => 0 ]; $item['total_refund_cumulative_arr'][$paymentIdentifierItem] = [ 'label' => $label, 'money' => 0 ]; $item['total_pay_cumulative_arr'][$paymentIdentifierItem] = [ 'label' => $label, 'money' => 0 ]; } $item['payment_identifier_text'] = implode('+', $this->getPaymentTypeByPaymentIdentifier($item['payment_identifier'])); if ($item['is_pay_group']) { $item['payment_identifier_text'] .= '(组合)'; } } $group = 'statistics_id, payment_type, trade_type'; $select = 'statistics_id, payment_type, trade_type, SUM(total_refund_money) total_refund_money, SUM(total_pay_money) total_pay_money'; $list = array_column($list, null, 'id'); // 累计退款总额 $cumulativeDetails = PaymentTypeRefundStatisticsDetails::lists([ 'group' => $group, 'select' => $select, 'where' => $baseWhere ]); $this->setMoneyTextArr($list, $cumulativeDetails, true); // 售后退货总额 $details = PaymentTypeRefundStatisticsDetails::lists([ 'group' => $group, 'select' => $select, 'where' => array_merge($baseWhere, $date ? [['between', 'date', $date[0], $date[1]]] : []) ]); $this->setMoneyTextArr($list, $details); $list = array_values($list); array_walk($list, function (&$item) { $item['total_refund_arr'] = array_values($item['total_refund_arr']); $item['total_pay_arr'] = array_values($item['total_pay_arr']); $item['total_refund_cumulative_arr'] = array_values($item['total_refund_cumulative_arr']); $item['total_pay_cumulative_arr'] = array_values($item['total_pay_cumulative_arr']); }); } /** * 将唯一识别符拆分为支付方式和具体的支付方式文字返回(示例:5,18|T_MINIAPP) * * @param string $paymentIdentifier * * @return array */ private function getPaymentTypeByPaymentIdentifier(string $paymentIdentifier): array { $paymentIdentifierArr = explode(',', $paymentIdentifier); $strArr = []; foreach ($paymentIdentifierArr as $paymentIdentifierItem) { ['payType' => $payType, 'tradeType' => $tradeType] = $this->returnPaymentTypeAndTradeType($paymentIdentifierItem); $strArr[] = $this->getPaymentTypeAndTradeTypeText(intval($payType), $tradeType); } return $strArr; } /** * 传入支付方式和具体的实付类型 返回如:汇付天下-微信小程序 * * @param int $paymentType * @param string $tradeType * * @return string */ private function getPaymentTypeAndTradeTypeText(int $paymentType, string $tradeType): string { $payTypeMap = PayTypeEnum::getMap(); $payTypeText = $payTypeMap[$paymentType] ?? ''; $tradeTypeText = $this->getTradeType($paymentType, $tradeType); return $payTypeText . ($tradeTypeText ? '-' . $tradeTypeText : ''); } /** * 示例:传入5,18|T_MINIAPP 返回支付方式和具体的支付类型 * * @param string $paymentIdentifierItem * * @return array */ private function returnPaymentTypeAndTradeType(string $paymentIdentifierItem): array { $paymentIdentifierItemArr = explode('|', $paymentIdentifierItem); $payType = $paymentIdentifierItemArr[0] ?? 0; $tradeType = $paymentIdentifierItemArr[1] ?? ''; return compact('payType', 'tradeType'); } /** * 设置列表支付方式和金额显示的文字 * * @param array $list * @param array $details * @param bool $isCumulative */ public function setMoneyTextArr(array &$list, array $details, bool $isCumulative = false) { if (!$details) { return; } $cumulativeField = $isCumulative ? 'cumulative_' : ''; $refundMoneyField = sprintf('total_refund_%sarr', $cumulativeField); $payMoneyField = sprintf('total_pay_%sarr', $cumulativeField); foreach ($details as $detail) { if (!isset($list[$detail['statistics_id']])) { continue; } $identifier = $this->makePaymentIdentifier($detail['payment_type'], $detail['trade_type']); $list[$detail['statistics_id']][$refundMoneyField][$identifier]['money'] = bcadd( $list[$detail['statistics_id']][$refundMoneyField][$identifier]['money'], $detail['total_refund_money'], 2 ); $list[$detail['statistics_id']][$payMoneyField][$identifier]['money'] = bcadd( $list[$detail['statistics_id']][$payMoneyField][$identifier]['money'], $detail['total_pay_money'], 2 ); } } /** * 生成数据唯一性标识符 * * @param int $payType * @param string $tradeType * * @return string */ public function makePaymentIdentifier(int $payType, string $tradeType): string { return $payType . ($tradeType ? '|' . $tradeType : ''); } /** * 统计总数 * * @param array $params * * @return array */ private function getTotalNum(array $params): array { $baseWhere = [ [ 'status' => StatusEnum::ENABLED, 'mall_id' => $this->mall_id, ] ]; $select = 'SUM(total_refund_money) total_refund_money, SUM(total_pay_money) total_pay_money'; // 有条件则过滤某个支付方式,没条件则不过滤 $findStatisticsIdWhere = []; $this->getAllPayTypeRefundListWhere($params, $findStatisticsIdWhere); // 得到statistics_id if ($findStatisticsIdWhere) { $findStatisticsIdWhere = array_merge($findStatisticsIdWhere, $baseWhere); $paymentTypeRefundStatisticsFind = PaymentTypeRefundStatistics::find(); PaymentTypeRefundStatistics::paramPack([ 'where' => $findStatisticsIdWhere, 'select' => 'id' ], $paymentTypeRefundStatisticsFind); $baseWhere = array_merge($baseWhere, [['in', 'statistics_id', $paymentTypeRefundStatisticsFind->column()]]); } // 累计总额 $cumulativeDetails = PaymentTypeRefundStatisticsDetails::getOne( $baseWhere, true, [], null, $select ); // 按时间统计总额 $dateDetails = PaymentTypeRefundStatisticsDetails::getOne( array_merge($baseWhere, !empty($params['daterange']) ? [['between', 'date', $params['daterange'][0], $params['daterange'][1]]] : []), true, [], null, $select ); return compact('cumulativeDetails', 'dateDetails'); } /** * 导出支付通道统计 * * @return void */ public function allPayTypeRefundExport(array $data) { ini_set('memory_limit', '1024M'); $download_id = $data['download_id'] ?? 0; if (!$download_id) { return; } $download = Download::findOne(['id' => $download_id]); if (!$download) { return; } $params = json_decode($download->params, true); $this->mall_id = $download->mall_id; $list = $this->allPayTypeRefund($params, true); $exportData = []; // 组装导出格式 foreach ($list as $item) { $exportData[] = [ 'payment_identifier' => $item['payment_identifier_text'], 'total_refund_cumulative' => implode('、', $this->mergeLabelAndMoney($item['total_refund_cumulative_arr'])), 'total_pay_cumulative' => implode('、', $this->mergeLabelAndMoney($item['total_pay_cumulative_arr'])), 'total_refund' => implode('、', $this->mergeLabelAndMoney($item['total_refund_arr'])), 'total_pay' => implode('、', $this->mergeLabelAndMoney($item['total_pay_arr'])), ]; } unset($goodsList, $statisticsTeam); $header = []; $this->getExportInfoByExportField($exportData, $header, $params['export_field'], $this->getAllPayTypeRefundExportField()); try { $csv = new CsvTool(); $csv->filename = $download->name; $csv->file_dirname = DownloadEnum::getTypeStorageDirMap($download->type); $csv->export_mode = CsvTool::EXPORT_MODE_ASYNC; $csv->header = $header; $csv->data = $exportData; $csv->export(); $csv->updateDown($download, $this->mall_id); } catch (\Exception $e) { $download->status = 3; $download->save(); \Yii::$app->custom->logs(FormatHelper::exception($e, '导出售后统计数据失败'), compact('data')); return; } } /** * [['label' => '标题', 'money': 1], ['label' => '标题', 'money': 1]] 转为 ['标题: 1', '标题: 1'] * * @param array $labelMoneyArr * * @return array */ private function mergeLabelAndMoney(array $labelMoneyArr): array { $arr = []; foreach ($labelMoneyArr as $item) { $arr[] = $item['label'] . ':' . $item['money']; } return $arr; } /** * @param array $params * @param bool $isGetAll * * @return array */ public function allPayTypeRefundDetails(array $params, bool $isGetAll = false): array { $baseWhere = [ ['status' => StatusEnum::ENABLED, 'mall_id' => $this->mall_id] ]; // 默认本月 $params['daterange'] = !empty($params['daterange']) ? $params['daterange'] : array_values(DateHelper::thisMonth()); // 得到当前支付方式退款的记录 $PaymentTypeRefundStatisticsLogsFind = PaymentTypeRefundStatisticsLogs::find(); PaymentTypeRefundStatisticsLogs::paramPack([ 'where' => array_merge($baseWhere, [['statistics_id' => $params['id']]]), 'select' => 'refund_id', ], $PaymentTypeRefundStatisticsLogsFind); $params['refund_ids'] = $PaymentTypeRefundStatisticsLogsFind->column(); $where = []; $this->getAllPayTypeRefundListDetailsWhere($baseWhere, $params, $where); $where = array_merge($where, $baseWhere); $with = [ 'order' => function ($query) { $query ->select('id, order_no, shipping_money, out_trade_no') ->with([ 'trade' => function ($query) { $query ->select('out_trade_no, id, is_pay_group, pay_type, trade_type') ->with([ 'tradeItem' => function ($query) { $query ->orderBy('pay_type') ->select('id, trade_id, pay_type, pay_fee, trade_type') ->with([ 'paymentTradeItemRefund' => function ($query) { $query->select('refund_fee, item_id'); } ]); } ]); } ]); }, 'base_user' ]; $listParams = [ 'where' => $where, 'select' => 'id, user_id, order_id, reality_refund_price, order_no, refund_at, created_at, refund_status', 'with' => $with ]; if ($isGetAll) { $list = OrderRefund::lists($listParams); $this->formatAllPayTypeRefundDetailsList($list); } else { $list = OrderRefund::listPage($listParams); $this->formatAllPayTypeRefundDetailsList($list['list']); // 返回总数 $list['total_num'] = $this->getOrderRefundTotalNum($where); } return $list; } /** * 组装支付方式退款统计明细 * * @param array $baseWhere * @param array $params * @param array $where * * @return void */ private function getAllPayTypeRefundListDetailsWhere(array $baseWhere, array $params, array &$where) { $where[] = ['in', 'id', $params['refund_ids']]; // 用户id 手机号 搜索 if (!empty($params['user_keyword'])) { $userFind = User::find(); User::paramPack([ 'select' => 'id', 'where' => array_merge( $baseWhere, [ [ 'OR', ['=', 'id', $params['user_keyword']], ['like', 'mobile', $params['user_keyword']], ] ] ) ], $userFind); $userIds = $userFind->column(); $where[] = ['in', 'user_id', $userIds]; } if ($params['daterange']) { array_walk($params['daterange'], function (&$item) { $item = !is_numeric($item) ? strtotime($item) : $item; }); $where[] = ['between', 'refund_at', $params['daterange'][0], $params['daterange'][1]]; } } /** * @param array $list * * @return void */ private function formatAllPayTypeRefundDetailsList(array &$list) { array_walk($list, function (&$item) { $item['created_at_text'] = date('Y-m-d H:i:s', $item['created_at']); $item['refund_at_text'] = date('Y-m-d H:i:s', $item['refund_at']); $item['refund_status_text'] = RefundStatusEnum::getMap()[$item['refund_status']] ?? '-'; // 存储显示的支付通道 $paymentIdentifier= []; if (!empty($item['order']['trade']['is_pay_group'])) { if ($item['payment_identifier_text'] !== '') { $item['payment_identifier_text'] .= '(组合)'; } // 显示组合支付每种支付类型的支付的价格 if (!empty($item['order']['trade']['tradeItem'])) { foreach ($item['order']['trade']['tradeItem'] as &$tradeItem) { $tradeItem['payment_text'] = $this->getPaymentTypeAndTradeTypeText((int)$tradeItem['pay_type'], $tradeItem['trade_type']); $paymentIdentifier[] = $tradeItem['payment_text']; } } } else { $paymentIdentifier[] = $this->getPaymentTypeAndTradeTypeText((int)$item['order']['trade']['pay_type'], $item['order']['trade']['trade_type']); } $item['payment_identifier_text'] = implode(' + ', $paymentIdentifier); }); } /** * 导出支付通道统计明细 * * @return void */ public function allPayTypeRefundDetailsExport(array $data) { ini_set('memory_limit', '1024M'); $download_id = $data['download_id'] ?? 0; if (!$download_id) { return; } $download = Download::findOne(['id' => $download_id]); if (!$download) { return; } $params = json_decode($download->params, true); $this->mall_id = $download->mall_id; $list = $this->allPayTypeRefundDetails($params, true); $exportData = []; // 组装导出格式 foreach ($list as $item) { $exportData[] = [ 'user' => sprintf('昵称:%s 手机号:%s', $item['base_user']['nickname'] ?? '', $item['base_user']['mobile'] ?? ''), 'payment_identifier' => $item['payment_identifier_text'] ?? '', 'pay_money' => $item['order']['pay_money'] ?? 0, 'shipping_money' => $item['order']['shipping_money'] ?? 0, 'reality_refund_price' => $item['reality_refund_price'] ?? 0, 'order_no' => $item['order']['order_no'] ?? '', 'refund_no' => $item['order_no'] ?? '', 'refund_at' => $item['refund_at_text'] ?? '', 'refund_status' => $item['refund_status_text'] ?? '', ]; } unset($goodsList, $statisticsTeam); if (empty($exportData)) { return; } // 得到导出标题 $header = []; $this->getExportInfoByExportField($exportData, $header, $params['export_field'], $this->getAllPayTypeRefundDetailsExportField()); try { $csv = new CsvTool(); $csv->filename = $download->name; $csv->file_dirname = DownloadEnum::getTypeStorageDirMap($download->type); $csv->export_mode = CsvTool::EXPORT_MODE_ASYNC; $csv->header = $header; $csv->data = $exportData; $csv->export(); $csv->updateDown($download, $this->mall_id); } catch (\Exception $e) { $download->status = 3; $download->save(); \Yii::$app->custom->logs(FormatHelper::exception($e, '导出售后统计数据失败'), compact('data')); return; } } /** * 导出支付通道统计数据可选择的字段 * * @return array[] */ public function getAllPayTypeRefundExportField() { return [ [ 'label' => '支付通道', 'value' => 'payment_identifier' ], [ 'label' => '累计退款总额', 'value' => 'total_refund_cumulative' ], [ 'label' => '累计实付总额', 'value' => 'total_pay_cumulative' ], [ 'label' => '售后退款总额', 'value' => 'total_refund' ], [ 'label' => '原实付总额', 'value' => 'total_pay' ], ]; } /** * 导出支付通道统计明细数据时可选择的字段 * * @return array[] */ public function getAllPayTypeRefundDetailsExportField(): array { return [ [ 'label' => '用户', 'value' => 'user' ], [ 'label' => '支付通道', 'value' => 'payment_identifier' ], [ 'label' => '原实付金额', 'value' => 'pay_money' ], [ 'label' => '运费', 'value' => 'shipping_money' ], [ 'label' => '退款金额', 'value' => 'reality_refund_price' ], [ 'label' => '订单编号', 'value' => 'order_no' ], [ 'label' => '售后单号', 'value' => 'refund_no' ], [ 'label' => '售后申请时间', 'value' => 'refund_at' ], [ 'label' => '售后状态', 'value' => 'refund_status' ] ]; } /** * 返回需要导出的指定字段 * * @param array $exportData * @param array $header * @param array $exportField * @param array $defaultExportField * * @return void */ private function getExportInfoByExportField(array &$exportData, array &$header, array $exportField, array $defaultExportField) { $keyMap = array_column($defaultExportField, 'label', 'value'); // 得到具体要导出的字段 $defaultExportField = array_column($defaultExportField, 'value'); $exportField = !empty($exportField) && array_intersect($exportField, $defaultExportField) ? $exportField : $defaultExportField; // 按照要导出的字段重新过滤 if (count($exportData[0]) !== count($exportField)) { array_walk($exportData, function (&$item) use ($exportField) { $res = []; foreach ($exportField as $field) { if (isset($item[$field])) { $res[$field] = $item[$field]; } } $item = $res; }); } // 得到导出标题 $exportDataKey = array_keys($exportData[0]); foreach ($exportDataKey as $key) { $header[] = $keyMap[$key] ?? ''; } } /** * 在订单退款表查询统计数据 * * @param array $where * * @return array */ private function getOrderRefundTotalNum(array $where) { $dateDetails = $this->getTotalRefundMoneyAndTotalPayMoney($where); // 累积的,忽略时间筛选 foreach ($where as $key => $item) { /* * 这个查询是这个格式 * [ * 0 => 'between' * 1 => 'refund_at' * 2 => 1730390400 * 3 => 1732982399 * ] */ if (isset($item[1]) && $item[1] === 'refund_at') { unset($where[$key]); break; } } $cumulativeDetails = $this->getTotalRefundMoneyAndTotalPayMoney($where); return compact('cumulativeDetails', 'dateDetails'); } /** * @param array $where * * @return array */ private function getTotalRefundMoneyAndTotalPayMoney(array $where): array { $defaultRes = [ 'total_refund_money' => 0, 'total_pay_money' => 0, ]; $orderRefundFind = OrderRefund::find(); OrderRefund::paramPack([ 'select' => 'SUM(reality_refund_price) total_refund_money, GROUP_CONCAT(order_id) order_ids', 'where' => $where ], $orderRefundFind); $refundOne = $orderRefundFind->asArray()->one(); if (empty($refundOne)) { return $defaultRes; } $defaultRes['total_refund_money'] = $refundOne['total_refund_money']; $orderIds = explode(',', $refundOne['order_ids']); if (empty($orderIds)) { return $defaultRes; } $defaultRes['total_pay_money'] = Order::find() ->where([ 'status' => StatusEnum::ENABLED, 'mall_id' => $this->mall_id, 'id' => $orderIds ]) ->sum('pay_money'); return $defaultRes; } }