255], [['type'], 'string', 'max' => 45], ]; } /** * {@inheritdoc} */ public function attributeLabels() { return [ 'id' => 'ID', 'mall_id' => 'Mall ID', 'store_id' => 'Store ID', 'user_id' => 'User ID', 'order_no' => 'Order No', 'num' => 'Num', 'actual_num' => 'Actual Num', 'poundage_num' => 'Poundage Num', 'poundage_ratio' => 'Poundage Ratio', 'type' => 'Type', 'extra' => 'Extra', 'withdraw_status' => 'Withdraw Status', 'status' => 'Status', 'remark' => 'Remark', 'created_at' => 'Created At', 'updated_at' => 'Updated At', 'content' => 'Content', ]; } public function saveRecord($params) { if(!empty($params['type'])){ switch ($params['type']){ case "joinpay": case "bank": if(empty($params['bank_account'])||empty($params['bank_name'])||empty($params['name'])){ return [ 'state' => false, 'msg' => '请选择银行卡!' ]; } break; case "wechat": if(empty($params['wechat_qrcode'])){ return [ 'state' => false, 'msg' => '请上传收款码!' ]; } break; case "alipay_tax": if(empty($params['id_card'])){ return [ 'state' => false, 'msg' => '请填写支付宝身份证号码!' ]; } if(empty($params['mobile'])){ return [ 'state' => false, 'msg' => '请填写支付宝账号!' ]; } if(empty($params['name'])){ return [ 'state' => false, 'msg' => '请填写支付宝真实姓名!' ]; } break; case "alipay_offline": case "alitopay": if(empty($params['name'])){ return [ 'state' => false, 'msg' => '请填写支付宝真实姓名!' ]; } if(empty($params['mobile'])){ return [ 'state' => false, 'msg' => '请填写支付宝账号!' ]; } break; case "huifu_balance": if(empty($params['huifu_no'])){ return [ 'state' => false, 'msg' => '请填写汇付号' ]; } break; } } if ($params['price'] <= 0) { return [ 'state' => false, 'msg' => '提现金额必须大于0!' ]; } $exists = self::find()->where([ 'mall_id' => $params['mall_id'], 'withdraw_status' => 0, 'user_id' => $params['user_id'] ])->asArray()->one();// mycat不支持exists()用法 if ($exists) { return [ 'state' => false, 'msg' => '尚有未审核的提现申请!' ]; } $store = HappinessStore::getOne([['id' => $params['store_id']]], false, [], false, 'id,mall_id,user_id,balance,total_expenses'); //手续费设置 $poundage_ratio = 0; //需要提现手续费金额 $cash_amount = $params['price']; if ($params['type'] != 'balance') { $finance = \Yii::$app->services->setting->addons->get($params['mall_id'], HappinessEnum::ADDONS_NAME, 'basic'); $poundage_ratio = $finance['balance_cash_service_fee'] ?? 0; } if ($store['balance'] < $params['price']) { return [ 'state' => false, 'msg' => '提现金额超出可提现金额!' ]; } if (!$params['type']) { return [ 'state' => false, 'msg' => '请选择提现方式!' ]; } if ($params['type'] == 'wechat') { if (!isset($params['wechat_qrcode'])) { return [ 'state' => false, 'msg' => '请上传微信收款码!' ]; } } if ($params['type'] == 'alipay') { if (!$params['mobile']) { return [ 'state' => false, 'msg' => '请填写支付宝账号!' ]; } if (!$params['name']) { return [ 'state' => false, 'msg' => '请填写支付宝姓名!' ]; } } if ($params['type'] == 'paid') {//代付银行卡 if (!$params['name']) { return [ 'state' => false, 'msg' => '请填写持卡人!' ]; } if (!$params['bank_account']) { return [ 'state' => false, 'msg' => '请填写银行卡号!' ]; } } if ($params['type'] == 'alipay_tax') { if (!$params['mobile']) { return [ 'state' => false, 'msg' => '请填写支付宝账号(手机号)' ]; } if (!$params['name']) { return [ 'state' => false, 'msg' => '请填写支付宝姓名' ]; } if (!$params['id_card']) { return [ 'state' => false, 'msg' => '请填写身份证信息' ]; } $service = new \addons\AvoidTax\common\service\UserService(['mall_id' => $params['mall_id']]); if (!$service->idIsSignAndAddAuthOfUid( $params['id_card'], $params['name'], $params['user_id'], $params['mobile'] )) { $res = $service->checkSign($params['user_id']); if ($res === false) { $res = $service->goToSing($params['user_id'], $params['mobile'], $params['id_card'], $params['name']); if (!$res) { return [ 'state' => false, 'msg' => $service->error ]; } // 跳转url签约 return [ 'state' => true, 'data' => $res ]; } } } $cash = new HappinessWithdrawLog(); $cash->mall_id = $params['mall_id']; $cash->user_id = $store['user_id']; $cash->store_id = $store['id']; $cash->num = $params['price']; $cash->actual_num = $params['price']; $cash->order_no = StringHelper::generateOrderNo('tx'); $cash->status = StatusEnum::ENABLED; $cash->remark = $params['remark'] ?? ''; $cash->content = Json::encode(['user_content' => $params['content']]); $cash->type = $params['type']; $cash->extra = Json::encode([ 'name' => $params['name'], 'mobile' => $params['mobile'], 'bank_name' => $params['bank_name'], 'bank_account' => (string)$params['bank_account'], 'bank_account_type' => $params['bank_account_type'], 'wechat_qrcode' => $params['wechat_qrcode'], 'id_card' => $params['id_card'] ?? '', 'receive_bank_chanel_no' => $params['receive_bank_chanel_no'] ?? '', 'huifu_no' => $params['huifu_no'] ?? '' ], JSON_UNESCAPED_UNICODE); if ($poundage_ratio < 0 || $poundage_ratio > 100) { return [ 'state' => false, 'msg' => '手续费设置不正确!' ]; } //手续费 $poundage = 0; if ($poundage_ratio > 0 && $poundage_ratio < 100) { //手续费:取小数点2位,截断 $poundage = bcmul($poundage_ratio / 100, $cash_amount, 4); // 四舍五入,保留2位 $poundage = number_format($poundage, 2, '.', ''); $cash->actual_num = bcsub($params['price'], $poundage, 2); $cash->poundage_num = $poundage; $cash->poundage_ratio = $poundage_ratio; } if (intval(bcmul($cash->actual_num, 100)) < 1) { return [ 'state' => false, 'msg' => '提现金额扣除费用后金额不足!' ]; } $t = \Yii::$app->db->beginTransaction(); try { switch ($params['type']) { case '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_STORE_BALANCE, ]; $wallet_service_status = UserWalletService::handle(0, $data); if (!$wallet_service_status) { throw new Exception('提现失败' . UserWalletService::getStaticError()); } $cash->withdraw_status = 1; break; default: $cash->withdraw_status = 0; } if (!$cash->save()) { throw new Exception($cash->getErrorMessage()); } $store->balance -= $params['price']; $store->total_expenses += $params['price']; $res = $store->save(); if ($res == false) throw new Exception($store->getErrorMessage()); $res_data = [ 'state' => true, 'data' => ['cash_id' => $cash->id], 'msg' => '' ]; $t->commit(); } catch (Exception $e) { $t->rollBack(); \Yii::error($e->getMessage()); return [ 'state' => false, 'msg' => '提现失败!' . $e->getMessage() ]; } return $res_data; } 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; } 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'], 'id' => $log['id'], ]; switch ($log['type']) { case "joinpay": //汇聚代付 $extraArr = json_decode($log['extra'], true); $extra = json_encode([ 'bank_account' => !empty($extraArr['bank_card_no'])?$extraArr['bank_card_no']:$extraArr['bank_account'], 'name' => !empty($extraArr['payer_name'])?$extraArr['payer_name']:$extraArr['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->cashInfo = json_decode(json_encode($cash)); $res = $payment->transfer($paymentTransfer); } catch (Exception $e) { throw new \Exception($e->getMessage()); } break; case 'alipay_tax': break; case 'huifu_balance': //调用汇付余额支付 $config = \Yii::$app->services->setting->addons->get($mall_id, AddonsEnum::ADDONS_NAME_HUIFU_PAY, 'basic'); $extraArr = json_decode($log['extra'], true); $data = [ 'goods_title' => '小店提现ID(' . $log['store_id'] . ')', 'amount' => $log['actual_num'], 'huifu_id' => $config['huifupay_huifu_id'], ]; $item = [ 'num' => $log['actual_num'], 'id' => $extraArr['huifu_no'] ]; $splitAccount[] = $item; $huifuService = new HuifuService($mall_id); $res = $huifuService->balancePay($data, $splitAccount); if ($res&&$res['trans_stat']!='S') { throw new \Exception($res['resp_desc']); } 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_STORE_BALANCE_WALLET, $log->store_id); $identification = uniqid(); if (LockHelper::lock($lock_key, $identification, 100, 100)) { // 退回的金额 $store = HappinessStore::findOne(['mall_id' => $mall_id, 'id' => $log->store_id, 'user_id' => $log->user_id]); $store->balance = bcadd($store->balance, $log->num, 2); $store->total_expenses = bcsub($store->total_expenses, $log->num, 2); $res_store = $store->save(); $log->withdraw_status = $params['withdraw_status']; $log->remark = $params['remark']; $log->updated_at = time(); $res = $log->save(); LockHelper::uLock($lock_key, $identification); if (!$res_store) { throw new \Exception($store->getErrorMessage()); } $res = true; } else { throw new \Exception('系统繁忙,请稍后再试'); } break; } return $res; }catch (\Exception $e){ self::$static_error = $e->getMessage(); 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; } public static function getWithdrawTypeMap($mall_id, $from = 'withdraw_balance', $discard = ['alipay_tax']) { $payment = \Yii::$app->services->setting->mall->get($mall_id, $from); if (empty($payment['balance_cash_type'])) return []; $cashType = $payment['balance_cash_type']; $cash_type = Json::decode($cashType); $default_withdraw_way = HappinessWithdrawWayEnum::getDefaultWithDrawWayMap(HappinessWithdrawWayEnum::WITHDRAW_TYPE_2); $extension_withdraw_way = GlobalInvoke::exec(GlobalInvoke::INVOKE_GET_WITHDRAW_EXTENSION_WAY, $mall_id, ['mall_id' => $mall_id]); $all_withdraw = array_merge($default_withdraw_way, $extension_withdraw_way); $type_map = []; if (!empty($cash_type)) { foreach ($all_withdraw as $key => $value) { if ($key == 'alipay_tax') { $type_map[$key] = $value['name']; } else { $type_map[$key] = $value; } } } return $type_map; } }