refactor: improve OpenAI service and tools
- Enhance OpenAI message types with tool_calls support - Improve security validation and rate limiting - Update utility tools and weather tool - Minor fixes in deposit-agent and domain-register Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,11 @@ const NameserverResponseSchema = z.object({
|
||||
nameservers: z.array(z.string()).optional(),
|
||||
});
|
||||
|
||||
const PriceResponseSchema = z.object({
|
||||
krw: z.number().optional(),
|
||||
register_krw: z.number().optional(),
|
||||
});
|
||||
|
||||
interface RegisterResult {
|
||||
success: boolean;
|
||||
domain?: string;
|
||||
@@ -47,21 +52,68 @@ export async function executeDomainRegister(
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. 현재 잔액 확인
|
||||
// 1. Verify price from Namecheap API (security: prevent price manipulation)
|
||||
const domainTld = domain.split('.').pop() || '';
|
||||
const priceCheckResponse = await fetch(`${apiUrl}/prices/${domainTld}`, {
|
||||
headers: { 'X-API-Key': apiKey }
|
||||
});
|
||||
|
||||
if (!priceCheckResponse.ok) {
|
||||
logger.error('Failed to fetch price from Namecheap API', new Error(`HTTP ${priceCheckResponse.status}`));
|
||||
return { success: false, error: '가격 정보를 가져올 수 없습니다.' };
|
||||
}
|
||||
|
||||
const priceJsonData = await priceCheckResponse.json();
|
||||
const priceParseResult = PriceResponseSchema.safeParse(priceJsonData);
|
||||
|
||||
if (!priceParseResult.success) {
|
||||
logger.error('Price response schema validation failed', priceParseResult.error);
|
||||
return { success: false, error: '가격 정보 형식이 올바르지 않습니다.' };
|
||||
}
|
||||
|
||||
const priceData = priceParseResult.data;
|
||||
const actualPrice = priceData.krw || priceData.register_krw;
|
||||
|
||||
if (!actualPrice || typeof actualPrice !== 'number') {
|
||||
logger.error('Invalid price data from API', new Error('Missing or invalid krw/register_krw'), { priceData });
|
||||
return { success: false, error: '가격 정보가 올바르지 않습니다.' };
|
||||
}
|
||||
|
||||
// SECURITY: Verify callback price matches actual API price (allow 5% tolerance for exchange rate fluctuation)
|
||||
const priceDiff = Math.abs(actualPrice - price);
|
||||
const tolerance = actualPrice * 0.05; // 5%
|
||||
|
||||
if (priceDiff > tolerance) {
|
||||
logger.warn('Price mismatch detected - potential price manipulation', {
|
||||
callbackPrice: price,
|
||||
actualPrice,
|
||||
difference: priceDiff,
|
||||
domain
|
||||
});
|
||||
return {
|
||||
success: false,
|
||||
error: `가격이 변경되었습니다. 현재 가격: ${actualPrice.toLocaleString()}원\n다시 등록을 시도해주세요.`
|
||||
};
|
||||
}
|
||||
|
||||
logger.info('Price verification passed', { domain, callbackPrice: price, actualPrice });
|
||||
|
||||
// 2. 현재 잔액 확인
|
||||
const balanceRow = await env.DB.prepare(
|
||||
'SELECT balance FROM user_deposits WHERE user_id = ?'
|
||||
).bind(userId).first<{ balance: number }>();
|
||||
|
||||
const currentBalance = balanceRow?.balance || 0;
|
||||
if (currentBalance < price) {
|
||||
// Use actual price from API instead of callback price
|
||||
if (currentBalance < actualPrice) {
|
||||
return {
|
||||
success: false,
|
||||
error: `잔액이 부족합니다. (현재: ${currentBalance.toLocaleString()}원, 필요: ${price.toLocaleString()}원)`
|
||||
error: `잔액이 부족합니다. (현재: ${currentBalance.toLocaleString()}원, 필요: ${actualPrice.toLocaleString()}원)`
|
||||
};
|
||||
}
|
||||
|
||||
// 2. Namecheap API로 도메인 등록
|
||||
console.log(`[DomainRegister] 도메인 등록 요청: ${domain}, 가격: ${price}원`);
|
||||
// 3. Namecheap API로 도메인 등록
|
||||
logger.info('도메인 등록 요청', { domain, actualPrice, callbackPrice: price });
|
||||
|
||||
const registerResponse = await fetch(`${apiUrl}/domains/register`, {
|
||||
method: 'POST',
|
||||
@@ -88,13 +140,13 @@ export async function executeDomainRegister(
|
||||
|
||||
if (!registerResponse.ok || !registerResult.registered) {
|
||||
const errorMsg = registerResult.error || registerResult.detail || '도메인 등록에 실패했습니다.';
|
||||
console.error(`[DomainRegister] 등록 실패:`, registerResult);
|
||||
logger.error('등록 실패', new Error(errorMsg), { registerResult });
|
||||
return { success: false, error: errorMsg };
|
||||
}
|
||||
|
||||
console.log(`[DomainRegister] 등록 성공:`, registerResult);
|
||||
logger.info('등록 성공', { registerResult });
|
||||
|
||||
// 3. 잔액 차감 + 거래 기록 (Optimistic Locking)
|
||||
// 4. 잔액 차감 + 거래 기록 (Optimistic Locking) - USE ACTUAL PRICE
|
||||
try {
|
||||
await executeWithOptimisticLock(env.DB, async () => {
|
||||
// Read current balance and version
|
||||
@@ -102,24 +154,24 @@ export async function executeDomainRegister(
|
||||
'SELECT balance, version FROM user_deposits WHERE user_id = ?'
|
||||
).bind(userId).first<{ balance: number; version: number }>();
|
||||
|
||||
if (!current || current.balance < price) {
|
||||
if (!current || current.balance < actualPrice) {
|
||||
throw new Error('잔액이 부족합니다.');
|
||||
}
|
||||
|
||||
// Update balance with version check
|
||||
// Update balance with version check - USE ACTUAL PRICE
|
||||
const updateResult = await env.DB.prepare(
|
||||
'UPDATE user_deposits SET balance = balance - ?, version = version + 1, updated_at = CURRENT_TIMESTAMP WHERE user_id = ? AND version = ?'
|
||||
).bind(price, userId, current.version).run();
|
||||
).bind(actualPrice, userId, current.version).run();
|
||||
|
||||
if (!updateResult.success || updateResult.meta.changes === 0) {
|
||||
throw new OptimisticLockError('Version mismatch on balance update');
|
||||
}
|
||||
|
||||
// Insert transaction record
|
||||
// Insert transaction record - USE ACTUAL PRICE
|
||||
const txResult = await env.DB.prepare(
|
||||
`INSERT INTO deposit_transactions (user_id, type, amount, status, description, confirmed_at)
|
||||
VALUES (?, 'withdrawal', ?, 'confirmed', ?, CURRENT_TIMESTAMP)`
|
||||
).bind(userId, price, `도메인 등록: ${domain}`).run();
|
||||
).bind(userId, actualPrice, `도메인 등록: ${domain}`).run();
|
||||
|
||||
if (!txResult.success) {
|
||||
throw new Error('거래 기록 생성 실패');
|
||||
@@ -129,8 +181,9 @@ export async function executeDomainRegister(
|
||||
userId,
|
||||
telegramUserId,
|
||||
domain,
|
||||
price,
|
||||
newBalance: current.balance - price,
|
||||
actualPrice,
|
||||
callbackPrice: price,
|
||||
newBalance: current.balance - actualPrice,
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -139,7 +192,7 @@ export async function executeDomainRegister(
|
||||
userId,
|
||||
telegramUserId,
|
||||
domain,
|
||||
price,
|
||||
actualPrice,
|
||||
});
|
||||
return {
|
||||
success: false,
|
||||
@@ -198,23 +251,30 @@ export async function executeDomainRegister(
|
||||
}
|
||||
}
|
||||
} catch (infoError) {
|
||||
console.log(`[DomainRegister] 도메인 정보 조회 실패 (무시):`, infoError);
|
||||
logger.info('도메인 정보 조회 실패 (무시)', { error: infoError });
|
||||
}
|
||||
|
||||
const newBalance = currentBalance - price;
|
||||
console.log(`[DomainRegister] 완료: ${domain}, 잔액: ${currentBalance} -> ${newBalance}, 만료: ${expiresAt}, NS: ${nameservers.join(', ')}`);
|
||||
const newBalance = currentBalance - actualPrice;
|
||||
logger.info('도메인 등록 완료', {
|
||||
domain,
|
||||
oldBalance: currentBalance,
|
||||
newBalance,
|
||||
actualPrice,
|
||||
expiresAt,
|
||||
nameservers: nameservers.join(', ')
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
domain: domain,
|
||||
price: price,
|
||||
price: actualPrice, // Return actual price charged
|
||||
newBalance: newBalance,
|
||||
nameservers: nameservers,
|
||||
expiresAt: expiresAt,
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
logger.error('도메인 등록 중 오류', error as Error, { domain, price });
|
||||
logger.error('도메인 등록 중 오류', error as Error, { domain, callbackPrice: price });
|
||||
return {
|
||||
success: false,
|
||||
error: '도메인 등록 중 오류가 발생했습니다. 잠시 후 다시 시도해주세요.'
|
||||
|
||||
Reference in New Issue
Block a user