table('QPAccountsDB.dbo.OrderWithDraw')->where('RecordID', $RecordID)->first(); if (!$query) { return 'fail'; } $service = new AiPay('AiPayOut'); $config = $service->getConfig(); if(!$accountName){ $dao = new AccountPayInfo(); $$accountName = $dao->randUserName($accountName); } $paymentMethod = $this->resolvePaymentMethod($PixType); $accountData = $this->buildAccountData($paymentMethod, $accountName, $PixNum, $email, $phone, $IFSCNumber, $BranchBank, $BankNO); if ($paymentMethod === null || empty($accountData)) { Util::WriteLog('AiPay', 'unsupported PixType for withdraw: ' . $PixType.'_'.$accountName); return 'fail'; } $amountDecimal = number_format($amount / NumConfig::NUM_VALUE, 2, '.', ''); $params = [ 'mchNo' => $config['mchNo'] ?? '', 'mchOrderNo' => $OrderId, 'paymentMethod' => $paymentMethod, 'amount' => $amountDecimal, 'notifyUrl' => $config['cashNotify'] ?? '', 'timestamp' => (string)round(microtime(true) * 1000), 'accountData' => $accountData, ]; $signedParams = $service->sign($params); $result = $service->post('/api/transfer', $signedParams); Log::info('AiPay withdraw request', $signedParams); Log::info('AiPay withdraw response', [$result]); try { $data = \GuzzleHttp\json_decode($result, true); } catch (\Throwable $e) { Util::WriteLog('AiPay_error', $e->getMessage()); return 'fail'; } if (isset($data['code']) && (int)$data['code'] === 0) { return $data; } return 'fail'; } public function notify($post) { if (!is_array($post)) { $post = \GuzzleHttp\json_decode($post, true); } Util::WriteLog('AiPay', 'cash callback: ' . json_encode($post, JSON_UNESCAPED_UNICODE)); $OrderId = $post['mchOrderNo'] ?? ''; $query = DB::connection('write')->table('QPAccountsDB.dbo.OrderWithDraw')->where('OrderId', $OrderId)->first(); if (!$query) { Util::WriteLog('AiPay', 'withdraw order not found: ' . $OrderId); return '{"success":true,"message":"order not found"}'; } if (!in_array($query->State, [5, 7])) { Util::WriteLog('AiPay', $OrderId . ' already handled'); return 'success'; } $agentID = DB::connection('write')->table('agent.dbo.admin_configs') ->where('config_value', self::AGENT) ->where('type', 'cash') ->value('id') ?? ''; $status = (int)($post['status'] ?? 0); $now = now(); $withdraw_data = []; $notifyState = 0; $msg = $post['msg'] ?? ''; $TakeMoney = $query->WithDraw + $query->ServiceFee; $UserID = $query->UserID; switch ($status) { case 3: // success $withdraw_data = [ 'State' => 2, 'agent' => $agentID, 'finishDate' => $now, ]; $this->handleSuccess($UserID, $TakeMoney, $OrderId, $query->ServiceFee); $notifyState = 1; break; case 4: // fail case 5: // reversal $msg = $msg ?: 'Withdraw rejected'; $bonus = '30000,' . $TakeMoney; PrivateMail::failMail($UserID, $OrderId, $TakeMoney, $msg, $bonus); Util::WriteLog('AiPayEmail', [$UserID, $OrderId, $TakeMoney, $msg, $bonus]); $withdraw_data = ['State' => 6, 'agent' => $agentID, 'remark' => $msg]; $notifyState = 2; break; default: Util::WriteLog('AiPay', 'cash notify pending: ' . $OrderId); return 'success'; } $recordData = [ 'before_state' => $query->State, 'after_state' => $withdraw_data['State'] ?? $query->State, 'RecordID' => $query->RecordID, 'update_at' => date('Y-m-d H:i:s'), ]; DB::connection('write')->table('QPAccountsDB.dbo.AccountsRecord') ->updateOrInsert(['RecordID' => $query->RecordID, 'type' => 1], $recordData); DB::connection('write')->table('QPAccountsDB.dbo.OrderWithDraw') ->where('OrderId', $OrderId) ->update($withdraw_data); return 'success'; } protected function resolvePaymentMethod($PixType): ?string { $map = [ 1 => '1201', // CashApp 2 => '1202', // PayPal 3 => '1203', // Venmo 4 => '1204', // CARD ]; return $map[$PixType] ?? null; } protected function buildAccountData(?string $paymentMethod, $accountName, $PixNum, $email, $phone, $IFSCNumber, $BranchBank, $BankNO): array { $splitName = $this->splitName($accountName); switch ($paymentMethod) { case '1201': $account = $PixNum ?: $phone; if ($account && strpos($account, '$') !== 0) { $account = '$' . $account; } return ['account' => $account]; case '1202': case '1203': return [ 'account' => $email ?: $PixNum, 'firstName' => $splitName['first'], 'lastName' => $splitName['last'], ]; case '1204': return [ 'cardNo' => $PixNum ?: $BankNO, 'cardExpireMonth' => $this->extractMonth($BranchBank), 'cardExpireYear' => $this->extractYear($BranchBank), 'cardSecurityCode' => substr($IFSCNumber ?? '', 0, 4), 'cardBrand' => 'visa', 'firstName' => $splitName['first'], 'lastName' => $splitName['last'], ]; } return []; } // protected function splitName($name): array // { // $name = trim($name ?: 'User'); // $parts = preg_split('/\s+/', $name, 2); // return [ // 'first' => $parts[0] ?? 'User', // 'last' => $parts[1] ?? 'Edward', // ]; // } protected function splitName(string $name = null): array { $name = trim($name ?? ''); if ($name === '') $name = 'Willam Edson'; // // 处理 "Last, First" // if (str_contains($name, ',')) { // $tmp = array_map('trim', explode(',', $name, 2)); // $name = ($tmp[1] ?? '') . ' ' . ($tmp[0] ?? ''); // $name = trim($name); // } // 清洗:保留字母、空格、-、' $name = preg_replace("/[^A-Za-z\\s\\-']/u", ' ', $name); $name = trim(preg_replace('/\s+/', ' ', $name)); $parts = explode(' ', $name, 2); return [ 'first' => $parts[0] ?: 'Willam', 'last' => $parts[1] ?? 'Edward', // 或 'Edward' / 'User' ]; } protected function extractMonth($value): string { if (preg_match('/(\d{2})/', (string)$value, $match)) { return $match[1]; } return '01'; } protected function extractYear($value): string { if (preg_match('/(\d{2,4})/', (string)$value, $match)) { return substr($match[1], -2); } return '25'; } protected function handleSuccess($UserID, $TakeMoney, $OrderId, $serviceFee): void { $first = DB::connection('write')->table('QPAccountsDB.dbo.UserTabData')->where('UserID', $UserID)->first(); if ($first) { DB::connection('write')->table('QPAccountsDB.dbo.UserTabData')->where('UserID', $UserID)->increment('TakeMoney', $TakeMoney); } else { DB::connection('write')->table('QPAccountsDB.dbo.UserTabData')->insert(['TakeMoney' => $TakeMoney, 'UserID' => $UserID]); try { PrivateMail::praiseSendMail($UserID); } catch (\Throwable $e) { // ignore mail failure } } try { StoredProcedure::addPlatformData($UserID, 4, $TakeMoney); } catch (\Throwable $exception) { Util::WriteLog('StoredProcedure', $exception->getMessage()); } $withdrawal_position_log = DB::connection('write')->table('agent.dbo.withdrawal_position_log')->where('order_sn', $OrderId)->first(); if ($withdrawal_position_log) { DB::connection('write')->table('agent.dbo.withdrawal_position_log') ->where('order_sn', $OrderId) ->update(['take_effect' => 2, 'update_at' => date('Y-m-d H:i:s')]); } RecordUserDataStatistics::updateOrAdd($UserID, $TakeMoney, 0, $serviceFee); (new RechargeWithDraw())->withDraw($UserID, $TakeMoney); $redis = Redis::connection(); $redis->incr('draw_' . date('Ymd') . $UserID); StoredProcedure::user_label($UserID, 2, $TakeMoney); } }