feat: 도메인 인라인 버튼 등록 + cheapest TLD + Cron 자동취소
- 도메인 등록 인라인 버튼 확인 플로우 (domain-register.ts) - manage_domain에 cheapest action 추가 (가장 저렴한 TLD TOP 15) - 24시간 경과 입금 대기 자동 취소 Cron (UTC 15:00) - 거래 내역 한글 라벨 + description 표시 - CLAUDE.md 문서 업데이트 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -128,8 +128,8 @@ const tools = [
|
||||
properties: {
|
||||
action: {
|
||||
type: 'string',
|
||||
enum: ['register', 'check', 'whois', 'list', 'info', 'get_ns', 'set_ns', 'price'],
|
||||
description: 'price: TLD 가격 조회 (.com 가격, .io 가격), register: 도메인 등록, check: 가용성 확인, whois: WHOIS 조회, list: 내 도메인 목록, info: 도메인 상세정보, get_ns/set_ns: 네임서버 조회/변경',
|
||||
enum: ['register', 'check', 'whois', 'list', 'info', 'get_ns', 'set_ns', 'price', 'cheapest'],
|
||||
description: 'price: TLD 가격 조회 (.com 가격, .io 가격), cheapest: 가장 저렴한 TLD 목록 조회, register: 도메인 등록, check: 가용성 확인, whois: WHOIS 조회, list: 내 도메인 목록, info: 도메인 상세정보, get_ns/set_ns: 네임서버 조회/변경',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
@@ -523,6 +523,11 @@ async function callNamecheapApi(
|
||||
headers: { 'X-API-Key': apiKey },
|
||||
}).then(r => r.json());
|
||||
}
|
||||
case 'get_all_prices': {
|
||||
return fetch(`${apiUrl}/prices`, {
|
||||
headers: { 'X-API-Key': apiKey },
|
||||
}).then(r => r.json());
|
||||
}
|
||||
case 'check_domains': {
|
||||
return fetch(`${apiUrl}/domains/check`, {
|
||||
method: 'POST',
|
||||
@@ -761,6 +766,27 @@ async function executeDomainAction(
|
||||
return `💰 .${targetTld} 도메인 가격\n\n• 등록/갱신: ${price?.toLocaleString()}원/년`;
|
||||
}
|
||||
|
||||
case 'cheapest': {
|
||||
const result = await callNamecheapApi('get_all_prices', {}, allowedDomains, telegramUserId, db, userId);
|
||||
if (result.error) return `🚫 ${result.error}`;
|
||||
|
||||
// 가격 > 0인 TLD만 필터링, krw 기준 정렬
|
||||
const sorted = (result as any[])
|
||||
.filter((p: any) => p.krw > 0)
|
||||
.sort((a: any, b: any) => a.krw - b.krw)
|
||||
.slice(0, 15);
|
||||
|
||||
if (sorted.length === 0) {
|
||||
return '🚫 TLD 가격 정보를 가져올 수 없습니다.';
|
||||
}
|
||||
|
||||
const list = sorted.map((p: any, i: number) =>
|
||||
`${i + 1}. .${p.tld} - ${p.krw.toLocaleString()}원/년`
|
||||
).join('\n');
|
||||
|
||||
return `💰 가장 저렴한 TLD TOP 15\n\n${list}\n\n💡 특정 TLD 가격은 ".com 가격" 형식으로 조회`;
|
||||
}
|
||||
|
||||
case 'register': {
|
||||
if (!domain) return '🚫 등록할 도메인을 지정해주세요.';
|
||||
if (!telegramUserId) return '🚫 도메인 등록에는 로그인이 필요합니다.';
|
||||
@@ -783,33 +809,38 @@ async function executeDomainAction(
|
||||
balance = balanceRow?.balance || 0;
|
||||
}
|
||||
|
||||
// 4. 확인 페이지 생성 (코드에서 고정 형식)
|
||||
// 4. 확인 페이지 생성 (인라인 버튼 포함)
|
||||
if (balance >= price) {
|
||||
return `📋 도메인 등록 확인
|
||||
// 버튼 데이터를 특수 마커로 포함
|
||||
const keyboardData = JSON.stringify({
|
||||
type: 'domain_register',
|
||||
domain: domain,
|
||||
price: price
|
||||
});
|
||||
return `__KEYBOARD__${keyboardData}__END__
|
||||
📋 <b>도메인 등록 확인</b>
|
||||
|
||||
• 도메인: ${domain}
|
||||
• 도메인: <code>${domain}</code>
|
||||
• 가격: ${price.toLocaleString()}원 (예치금에서 차감)
|
||||
• 현재 잔액: ${balance.toLocaleString()}원 ✓
|
||||
• 현재 잔액: ${balance.toLocaleString()}원 ✅
|
||||
• 등록 기간: 1년
|
||||
|
||||
📌 등록자 정보
|
||||
📌 <b>등록자 정보</b>
|
||||
서비스 기본 정보로 등록됩니다.
|
||||
(WHOIS Guard가 적용되어 개인정보는 비공개)
|
||||
|
||||
⚠️ 주의사항
|
||||
도메인 등록 후에는 취소 및 환불이 불가능합니다.
|
||||
|
||||
등록을 진행하시려면 '확인'이라고 입력해주세요.`;
|
||||
⚠️ <b>주의사항</b>
|
||||
도메인 등록 후에는 취소 및 환불이 불가능합니다.`;
|
||||
} else {
|
||||
const shortage = price - balance;
|
||||
return `📋 도메인 등록 확인
|
||||
return `📋 <b>도메인 등록 확인</b>
|
||||
|
||||
• 도메인: ${domain}
|
||||
• 도메인: <code>${domain}</code>
|
||||
• 가격: ${price.toLocaleString()}원
|
||||
• 현재 잔액: ${balance.toLocaleString()}원 ⚠️ 부족
|
||||
• 부족 금액: ${shortage.toLocaleString()}원
|
||||
|
||||
💳 입금 계좌
|
||||
💳 <b>입금 계좌</b>
|
||||
하나은행 427-910018-27104 (주식회사 아이언클래드)
|
||||
입금 후 '홍길동 ${shortage}원 입금' 형식으로 알려주세요.`;
|
||||
}
|
||||
@@ -866,10 +897,12 @@ ${result.account_info.bank} ${result.account_info.account}
|
||||
return `📋 ${result.message}`;
|
||||
}
|
||||
const statusIcon = (s: string) => s === 'confirmed' ? '✓' : s === 'pending' ? '⏳' : '✗';
|
||||
const typeLabel = (t: string) => t === 'deposit' ? '입금' : t === 'withdrawal' ? '출금' : t === 'refund' ? '환불' : t;
|
||||
const txList = result.transactions.map((tx: any) => {
|
||||
const date = tx.confirmed_at || tx.created_at;
|
||||
const dateStr = date ? new Date(date).toLocaleDateString('ko-KR', { month: '2-digit', day: '2-digit' }) : '';
|
||||
return `#${tx.id}: ${tx.type === 'deposit' ? '입금' : tx.type} ${tx.amount.toLocaleString()}원 ${statusIcon(tx.status)} (${dateStr})`;
|
||||
const desc = tx.description ? ` - ${tx.description}` : '';
|
||||
return `#${tx.id}: ${typeLabel(tx.type)} ${tx.amount.toLocaleString()}원 ${statusIcon(tx.status)} (${dateStr})${desc}`;
|
||||
}).join('\n');
|
||||
return `📋 거래 내역\n\n${txList}`;
|
||||
}
|
||||
@@ -1266,6 +1299,12 @@ export async function generateOpenAIResponse(
|
||||
for (const toolCall of assistantMessage.tool_calls) {
|
||||
const args = JSON.parse(toolCall.function.arguments);
|
||||
const result = await executeTool(toolCall.function.name, args, env, telegramUserId, db);
|
||||
|
||||
// __KEYBOARD__ 마커가 있으면 AI 재해석 없이 바로 반환 (버튼 보존)
|
||||
if (result.includes('__KEYBOARD__')) {
|
||||
return result;
|
||||
}
|
||||
|
||||
toolResults.push({
|
||||
role: 'tool',
|
||||
tool_call_id: toolCall.id,
|
||||
|
||||
Reference in New Issue
Block a user