diff --git a/src/deposit-agent.ts b/src/deposit-agent.ts index efc14a5..475b423 100644 --- a/src/deposit-agent.ts +++ b/src/deposit-agent.ts @@ -18,6 +18,7 @@ import type { ManageDepositArgs, DepositFunctionResult } from './types'; const logger = createLogger('deposit-agent'); +const MIN_DEPOSIT_AMOUNT = 1000; // 1,000원 const MAX_DEPOSIT_AMOUNT = 100_000_000; // 1억원 export interface DepositContext { @@ -81,6 +82,9 @@ export async function executeDepositFunction( if (!amount || amount <= 0) { return { error: '충전 금액을 입력해주세요.' }; } + if (amount < MIN_DEPOSIT_AMOUNT) { + return { error: `최소 충전 금액은 ${MIN_DEPOSIT_AMOUNT.toLocaleString()}원입니다.` }; + } if (amount > MAX_DEPOSIT_AMOUNT) { return { error: `최대 충전 금액은 ${MAX_DEPOSIT_AMOUNT.toLocaleString()}원입니다.` }; } @@ -159,9 +163,14 @@ export async function executeDepositFunction( } catch (error) { if (error instanceof OptimisticLockError) { logger.warn('동시성 충돌 감지 (입금 자동 매칭)', { userId, amount, depositor_name }); - throw new Error('처리 중 오류가 발생했습니다. 잠시 후 다시 시도해주세요.'); + return { + error: '⚠️ 동시 요청으로 처리가 지연되었습니다. 잠시 후 다시 시도해주세요.', + }; } - throw error; + logger.error('입금 자동 매칭 실패', error as Error, { userId, amount, depositor_name }); + return { + error: '처리 중 오류가 발생했습니다. 잠시 후 다시 시도해주세요.', + }; } } @@ -368,9 +377,14 @@ export async function executeDepositFunction( user_id: tx.user_id, amount: tx.amount, }); - throw new Error('처리 중 오류가 발생했습니다. 잠시 후 다시 시도해주세요.'); + return { + error: '⚠️ 동시 요청으로 처리가 지연되었습니다. 잠시 후 다시 시도해주세요.', + }; } - throw error; + logger.error('관리자 입금 확인 실패', error as Error, { transaction_id, user_id: tx.user_id, amount: tx.amount }); + return { + error: '처리 중 오류가 발생했습니다. 잠시 후 다시 시도해주세요.', + }; } } diff --git a/src/security.ts b/src/security.ts index 65b5bed..606f4b2 100644 --- a/src/security.ts +++ b/src/security.ts @@ -1,6 +1,8 @@ import { Env, TelegramUpdate } from './types'; import { createLogger } from './utils/logger'; +const logger = createLogger('security'); + // Telegram 서버 IP 대역 (2024년 기준) // https://core.telegram.org/bots/webhooks#the-short-version const TELEGRAM_IP_RANGES = [ @@ -99,12 +101,12 @@ export async function validateWebhookRequest( // 3. Secret Token 검증 (필수) if (!env.WEBHOOK_SECRET) { - console.error('WEBHOOK_SECRET not configured - rejecting request'); + logger.error('WEBHOOK_SECRET not configured - rejecting request', new Error('Missing WEBHOOK_SECRET')); return { valid: false, error: 'Security configuration error' }; } if (!isValidSecretToken(request, env.WEBHOOK_SECRET)) { - console.error('Invalid webhook secret token'); + logger.warn('Invalid webhook secret token'); return { valid: false, error: 'Invalid secret token' }; } @@ -112,7 +114,7 @@ export async function validateWebhookRequest( const clientIP = request.headers.get('CF-Connecting-IP'); if (clientIP && !isValidTelegramIP(clientIP)) { // 경고만 로그 (Cloudflare 프록시 환경에서는 정확하지 않을 수 있음) - console.warn(`Request from non-Telegram IP: ${clientIP}`); + logger.warn('Request from non-Telegram IP', { clientIP }); } // 5. 요청 본문 파싱 및 검증 diff --git a/src/services/bank-sms-parser.ts b/src/services/bank-sms-parser.ts index a83a82e..e91ad78 100644 --- a/src/services/bank-sms-parser.ts +++ b/src/services/bank-sms-parser.ts @@ -1,5 +1,8 @@ import { Env, BankNotification, WorkersAITextGenerationOutput, WorkersAITextGenerationInput } from '../types'; import { parseQuotedPrintable } from '../utils/email-decoder'; +import { createLogger } from '../utils/logger'; + +const logger = createLogger('bank-sms-parser'); /** * 은행 SMS 파싱 함수 @@ -23,17 +26,17 @@ export async function parseBankSMS(content: string, env?: Env): Promise `• ${ns}`).join('\n')}`; diff --git a/src/tools/index.ts b/src/tools/index.ts index 3380803..a2f1d86 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -134,10 +134,12 @@ export function selectToolsForMessage(message: string): typeof tools { } } - // 패턴 매칭 없으면 전체 도구 사용 (폴백) - if (selectedCategories.size === 1) { // utility만 있으면 폴백 - logger.info('패턴 매칭 없음 → 전체 도구 사용'); - return tools; + // 패턴 매칭 없으면 유틸리티 도구만 사용 (토큰 절약) + if (selectedCategories.size === 1) { // utility만 있으면 + logger.info('패턴 매칭 없음 → 유틸리티 도구만 사용 (토큰 절약)'); + // 기본 도구만 반환 (시간, 계산) + const utilityNames = TOOL_CATEGORIES.utility || []; + return tools.filter(t => utilityNames.includes(t.function.name)); } const selectedNames = new Set(