255], [['type'], 'string', 'max' => 45], ]; } /** * {@inheritdoc} */ public function attributeLabels() { return [ 'id' => 'ID', 'mall_id' => '商城id', 'mch_id' => '多商户id', 'user_id' => '会员id', 'order_no' => '提现订单号', 'num' => '提现金额', 'actual_num' => '实际到账金额', 'poundage_num' => '手续费扣除金额', 'poundage_ratio' => '手续费比例', 'type' => '提现方式 bank--银行转账 balance--打款到余额', 'extra' => '额外信息', 'withdraw_status' => '提现状态 0--待打款 --已打款 2--驳回 ', 'status' => '状态[0禁用 1启用 -1删除]', 'remark' => '备注', 'created_at' => '添加时间戳', 'updated_at' => '更新时间戳', ]; } public static function withdrawApply($params) { // $t = Yii::$app->db->beginTransaction(); // $lock_key = RedisKeyEnum::suffix(RedisKeyEnum::LOCK_MCH_BALANCE_WALLET, $params['mch_id']); $identification = uniqid(); try { // if (LockHelper::lock($lock_key, $identification, 100, 100)) { $store = Mch::getOne([['id' => $params['mch_id']]], false, [], false, 'id,mall_id,user_id,balance'); $cash = new self(); $cash->mall_id = $params['mall_id']; $cash->user_id = $store['user_id']; $cash->mch_id = $store['id']; $cash->num = $params['withdrawal_money']; $cash->actual_num = $params['withdrawal_money']; $cash->order_no = StringHelper::generateOrderNo('tx'); $cash->extra = json_encode([]); $cash->status = 1; $cash->remark = $params['remark'] ?? ''; $extra = []; $return_data = []; $withdraw_status = 0; switch ($params['withdrawal_type']) { case 'balance': //提现到个人余额 $cash->type = 'balance'; $desc = '余额提现' . $cash->num; $data = [ 'message_id' => 0, 'mall_id' => $store['mall_id'], 'user_id' => $store['user_id'], 'wallet_type' => 'balance', 'is_frozen' => false, 'unfrozen' => false, 'money' => abs($cash->actual_num), 'desc' => $desc, 'source_table' => 'user_wallet', 'source_table_id' => $cash->id, 'from_type' => UserWalletEnum::WITHDRAWAL, 'capital_type' => UserWalletEnum::RECHARGE_MCH_BALANCE, ]; $wallet_service_status = UserWalletService::handle(0, $data); if (!$wallet_service_status) { throw new Exception('提现失败' . UserWalletService::getStaticError()); } $withdraw_status = 1; break; case 'bank': case 'joinpay': case 'yunfast_pay': if (empty($params['select_bank'])) throw new Exception('请选择银行卡'); $extra = $params['select_bank']; break; case 'alitopay': case 'alipay_offline': if (empty($params['name'])) throw new Exception('请输入支付宝真实姓名'); if (empty($params['mobile'])) throw new Exception('支付宝绑定的手机号'); // if (empty($params['id_card'])) throw new Exception('请输入支付宝身份证号码'); // $extra['id_card'] = $params['id_card']; $extra['name'] = $params['name']; $extra['mobile'] = $params['mobile']; break; case 'alipay_tax': if (empty($params['name'])) throw new Exception('请输入支付宝真实姓名'); if (empty($params['mobile'])) throw new Exception('支付宝绑定的手机号'); $extra['name'] = $params['name']; $extra['mobile'] = $params['mobile']; if (empty($params['id_card'])) throw new Exception('请输入支付宝身份证号码'); $extra['id_card'] = $params['id_card']; $obj = new UserService(['mall_id' => $params['mall_id']]); $res = $obj->checkSign($store['user_id']); $data['is_sign'] = 0; if ($res != false) { $data['is_sign'] = 1; $data = array_merge($res, $data); } $return_data['is_sign'] = $data['is_sign']; if (!$data['is_sign']) { $res = $obj->goToSing($store['user_id'], $params['mobile'], $params['id_card'], $params['name']); if (!$res) { throw new Exception($obj->error); } $res['is_sign'] = $data['is_sign']; return ['code' => 1, 'data' => $res]; } break; case 'wechat': if (empty($params['wechat_qrcode'])) throw new Exception('请上传凭证'); $extra['content'] = $params['content']; $extra['wechat_qrcode'] = $params['wechat_qrcode']; break; case 'auto': $userInfo = UserInfo::findOne(['user_id' => $store['user_id'], 'mall_id' => $params['mall_id'], 'status' => StatusEnum::ENABLED, 'platform' => ['wechat', 'mp-wx']]); if (empty($userInfo)) throw new Exception('该商户不存在微信相关信息,请先到前端用微信公众号或者小程序注册'); break; default: throw new Exception('提现方式异常'); // } } $cash->extra = json_encode($extra); $cash->type = $params['withdrawal_type']; if($cash->type!='balance'){ if ($params['balance_cash_service_fee'] > 0 && $params['balance_cash_service_fee'] < 100) { //手续费:取小数点2位,截断 $poundage = bcmul($params['balance_cash_service_fee'] / 100, $params['withdrawal_money'], 4); // 四舍五入,保留2位 $poundage = number_format($poundage, 2, '.', ''); $cash->actual_num = bcsub($params['withdrawal_money'], $poundage, 2); $cash->poundage_num = $poundage; $cash->poundage_ratio = $params['balance_cash_service_fee']; } } if (intval(bcmul($cash->actual_num, 100)) < 1) { throw new Exception('提现金额扣除费用后金额不足'); } $cash->withdraw_status = $withdraw_status; $res = $cash->save(); if ($res == false) throw new Exception($cash->getErrorMessage()); $store->balance -= $params['withdrawal_money']; $res = $store->save(); if ($res == false) throw new Exception($store->getErrorMessage()); // $t->commit(); // LockHelper::uLock($lock_key, $identification); return ['code' => 1, 'data' => $return_data]; } catch (\Exception $e) { // $t->rollBack(); // LockHelper::uLock($lock_key, $identification); self::$static_error = $e->getMessage(); return []; } } /** * @name: * @msg: 后台审核门店提现申请 * @param {int} $mall_id * @param {array} $params * @return {*} */ public static function cashAudit(int $mall_id, array $params) { $log = self::findOne(['mall_id' => $mall_id, 'status' => StatusEnum::ENABLED, 'id' => $params['id'], 'withdraw_status' => 0]); if (empty($log)) { self::$static_error = '提现记录不存在'; return false; } // $transaction = \Yii::$app->db->beginTransaction(); try { $res = false; switch ($params['withdraw_status']) { case 1: $cash = [ 'actual_num' => $log['actual_num'], 'mall_id' => $log['mall_id'], 'user_id' => $log['user_id'], 'order_no' => $log['order_no'], ]; switch ($log['type']) { case "joinpay": //汇聚代付 $extraArr = json_decode($log['extra'], true); $extra = json_encode([ 'bank_account' => $extraArr['bank_card_no'], 'name' => $extraArr['payer_name'], 'bank_account_type' => $extraArr['bank_account_type'], ]); $cash['extra'] = $extra; $data['cash'] = json_decode(json_encode($cash)); $data['title'] = '商户余额提现'; $data['source'] = '3'; $res = WithdrawWay::extensionWithdrawPay($data); break; case "alitopay": $extraArr = json_decode($log['extra'], true); $res = self::withdrawToPay($mall_id, $log['id'], $log['order_no'], $extraArr['name'], $extraArr['mobile'], $log['actual_num']); break; case "alipay_offline": case "bank": case "wechat": $res = true; break; case "auto": $userInfo = UserInfo::getOne([["user_id" => $log->user_id, "platform" => [User::PLATFORM_WECHAT, User::PLATFORM_MP_WX], "status" => StatusEnum::ENABLED]]); try { $cash['extra'] = []; $payment = new PayService(); $paymentTransfer = new PaymentTransfer(); $paymentTransfer->orderNo = PaymentTransfer::generate_order_no("TX"); $paymentTransfer->user = $userInfo; $paymentTransfer->title = "收益提现"; $paymentTransfer->amount = $log['actual_num']; $paymentTransfer->transferType = PayTypeEnum::WECHAT; $paymentTransfer->withdrawal_source = 'MchWithdrawLog'; $paymentTransfer->withdrawal_source_id = $log['id']; $paymentTransfer->cashInfo = $cash; $res = $payment->transfer($paymentTransfer); } catch (Exception $e) { throw new \Exception($e->getMessage()); } break; case 'alipay_tax': break; } if ($res) { $log->withdraw_status = $params['withdraw_status']; $log->remark = $params['remark']; $log->updated_at = time(); $res = $log->save(); if (!$res) { throw new \Exception($log->getErrorMessage()); } } break; case 2: // 驳回提现申请,退回已经扣除的余额 $lock_key = RedisKeyEnum::suffix(RedisKeyEnum::LOCK_MCH_BALANCE_WALLET, $log->mch_id); $identification = uniqid(); if (LockHelper::lock($lock_key, $identification, 100, 100)) { // 退回的金额 $store = Mch::findOne(['mall_id' => $mall_id, 'id' => $log->mch_id, 'user_id' => $log->user_id]); $store->balance = bcadd($store->balance, $log->num, 2); $res_store = $store->save(); $log->withdraw_status = $params['withdraw_status']; $log->remark = $params['remark']; $log->updated_at = time(); $res = $log->save(); if (!$res_store) { throw new \Exception($store->getErrorMessage()); } LockHelper::uLock($lock_key, $identification); $res = true; } else { throw new \Exception('系统繁忙,请稍后再试'); } break; } // $transaction->commit(); return $res; } catch (\Exception $e) { self::$static_error = $e->getMessage(); // $transaction->rollBack(); return false; } } public static function withdrawToPay($mall_id, $source_id, $order_no, $name, $mobile, $money) { // 获取设置 $setting = (new SettingService($mall_id))->getSetting(); if (empty($setting)) throw new \Exception('未进行代付配置'); // 加锁 $uniqid = uniqid(); $lock_key = 'alitopay_order_lock_' . $mall_id; if (!LockHelper::lock($lock_key, $uniqid)) throw new \Exception('操作频繁请稍后重试'); // 拼接数据入表 $insert_data = []; $time = time(); // 是否达到最低打款金额 if ($money < AlitoapyEnum::MIX_WITHDRAW) { throw new \Exception("订单{$name},打款金额必须大于1元"); } // 数据 $data['mall_id'] = $mall_id; $data['order_sn'] = $time . rand(1000, 9999); $data['type'] = Order::TYPE_WITHDRAWAL; $data['money'] = $money; $data['name'] = $name; $data['account'] = $mobile; $data['source_id'] = $source_id; $data['source_sn'] = $order_no; $data['created_at'] = $data['updated_at'] = $time; $insert_data[] = $data; // 开启事务 $db = Yii::$app->db->beginTransaction(); try { // 批量插入 Order::batchInsert($insert_data); // 批量修改打款中 $db->commit(); } catch (\Exception $e) { $db->rollBack(); LockHelper::uLock($lock_key, $uniqid); throw new \Exception($e->getMessage()); } // 批量进行打款 $count = self::aliPay($mall_id, $insert_data); LockHelper::uLock($lock_key, $uniqid); return $count; } private static function aliPay($mall_id, array $list) { $aliapy = new AlipayService($mall_id); // 创建一个批次号 $out_batch_no = 'TX' . date('Ymd') . $mall_id . rand(1000, 9999); $count = 0; $sub_msg = null; foreach ($list as $v) { // 打款 $res = $aliapy->transfer( $v['money'], $v['name'], $v['account'], $v['order_sn'] ); if ($res['code'] == '10000') { // 打款成功 Order::paySuccessBySourceIdAndOrderSn( $v['source_id'], $v['order_sn'],$res['order_id'] ); $count++; } else { // 打款失败 Order::payFialBySourceIdAndOrderSn( $v['source_id'], $v['order_sn'], $res['sub_msg'] ); $sub_msg = $res['sub_msg']; } } // 设置批次号 Order::setOutBatchNoByOrderSn($out_batch_no, array_column($list, 'order_sn')); if (isset($sub_msg)) { throw new \Exception($sub_msg); } return $count; } /** * @name: * @msg: 导出字段列表 * @param {array} $fields * @return {*} */ public static function exportFieldList(array $fields = null) { $field_list = [ 'nickname' => '会员昵称', 'mobile' => '手机号', 'type' => '提现方式', 'account_username' => '收款账户名', 'account_no' => '收款账户', 'account_organization' => '收款机构', 'num' => '提现金额', 'poundage_num' => '手续费', 'actual_num' => '实际打款金额', 'remark' => '备注', 'created_at' => '付款时间', ]; if (empty($fields)) { return $field_list; } else { $new_list = []; foreach ($fields as $val) { if (!empty($field_list[$val])) { $new_list[$val] = $fields[$val]; } } return $new_list; } } public static function exportHandle(array $data = null) { try { $download_id = $data['download_id'] ?? 0; $download = Download::findOne(['id' => $download_id]); $params = json_decode($download->params, true); // dd($params); $have_select_field_arr = []; $all_fields = self::exportFieldList(); $fields = $params['fields'] ?? []; if (!empty($fields)) { foreach ($fields as $field) { $have_select_field_arr[] = $all_fields[$field]; } } else { $have_select_field_arr = $all_fields; } // dd($have_select_field_arr); $csv = new CsvTool(); $csv->filename = $download->name; $csv->file_dirname = DownloadEnum::getTypeStorageDirMap($download->type); $csv->export_mode = CsvTool::EXPORT_MODE_ASYNC; $csv->header = $have_select_field_arr; $cash_status_map = ['waiting-payment' => 0, 'payment' => 1, 'refund' => 2]; $list_query = self::find()->where(['mall_id' => $params['mall_id'], 'status' => StatusEnum::ENABLED, 'withdraw_status' => $cash_status_map[$params['curr_tabname']]]); if (!empty($params['user_id'])) { $wheres[] = ['user_id' => $params['user_id']]; } if (!empty($params['nickname'])) { $users = User::find() ->select('id,nickname,mobile,avatar_url') ->where(['mall_id' => $params['mall_id'], 'status' => StatusEnum::ENABLED]) ->andWhere(['like', 'nickname', $params['nickname']]) ->asArray() ->indexBy('id') ->all(); $wheres[] = ['user_id' => array_column($users, 'id')]; } if (!empty($params['start_time'])) { $wheres[] = ['>=', 'created_at', strtotime($params['start_time'])]; } if (!empty($params['end_time'])) { $wheres[] = ['<=', 'created_at', strtotime($params['end_time'] . ' 23:59:59')]; } $rows = []; $type_map= PaymentLogic::getWithdrawTypeMap($params['mall_id']); foreach ($list_query->asArray()->batch(200) as $lists) { if (empty($users)) { $user_ids = array_column($lists, 'user_id'); $users = User::find() ->select('id,nickname,mobile,avatar_url') ->where(['mall_id' => $params['mall_id'], 'status' => StatusEnum::ENABLED, 'id' => $user_ids]) ->asArray() ->indexBy('id') ->all(); } foreach ($lists as &$list) { $user = $users[$list['user_id']] ?? ''; $list['nickname'] = $user['nickname'] ?? ''; $list['mobile'] = $user['mobile'] ?? ''; $list['account_no'] = $list['account_username'] = $list['account_organization'] = ''; switch ($list['type']) { case 'bank': case 'joinpay': $extra = json_decode($list['extra'], true); $list['account_no'] = $extra['bank_card_no'] ?? ''; $list['account_username'] = $extra['payer_name'] ?? ''; $list['account_organization'] = $extra['bank_name'] ?? ''; break; case 'alipay': $extra = json_decode($list['extra'], true); $list['account_no'] = $extra['mobile'] ?? ''; $list['account_username'] = $extra['name'] ?? ''; $list['account_organization'] = ''; break; case 'balance': $list['account_no'] = $user['mobile'] ?? ''; $list['account_username'] = $user['nickname'] ?? ''; $list['account_organization'] = '平台'; break; case 'auto': $list['account_no'] = $user['mobile'] ?? ''; $list['account_username'] = $user['nickname'] ?? ''; $list['account_organization'] = ''; break; } $arr = []; foreach ($fields as $field) { if (!empty($list[$field])) { if ('created_at' == $field) { $list[$field] = date('Y-m-d H:i:s', $list[$field]); } if ('mobile' == $field) { $list[$field] = (string)$list[$field]; } if($field=='type'){ $list[$field] = $type_map[$list[$field]]??''; } } else { $list[$field] = ''; } $arr[] = $list[$field]; } $rows[] = $arr; } } $csv->data = $rows; $csv->export(); $csv->updateDown($download, $params['mall_id']); return true; } catch (\Exception $e) { $download->status = 3; $res = $download->save(); self::setStaticError($e->getMessage()); return $e->getMessage(); } } /** * 打款失败,变成待打款状态,让客户自己去驳回 * @param $withdraw_log * @param $msg */ public static function remit_fail($withdraw_log, $msg) { $res = MchWithdrawLog::updateAll([ 'withdraw_status' => 0, 'remark' => '打款结果:' . $msg, 'updated_at' => time() ], ['id' => $withdraw_log->id]); } }