Add refund received toggle button and filter options
- Add refund_received column to claude_payment_status table
- API endpoint POST /api/tools/refund-received/{email} to toggle
- Clickable badge in table to mark refund as received/not received
- Filter options: refund received / refund not received
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
23
database.py
23
database.py
@@ -105,17 +105,19 @@ class DatabaseManager:
|
||||
proxy_expire_days INTEGER DEFAULT 30,
|
||||
proxy_share TEXT DEFAULT 'exclusive',
|
||||
proxy_purchase_date TEXT DEFAULT '',
|
||||
refund_received TEXT DEFAULT '0',
|
||||
checked_at TEXT
|
||||
)
|
||||
''')
|
||||
|
||||
# 兼容旧表:动态添加缺少的列
|
||||
for col in ['suspended_time', 'title', 'remark', 'card_number', 'proxy', 'proxy_expire_days', 'proxy_share', 'proxy_purchase_date']:
|
||||
for col in ['suspended_time', 'title', 'remark', 'card_number', 'proxy', 'proxy_expire_days', 'proxy_share', 'proxy_purchase_date', 'refund_received']:
|
||||
try:
|
||||
cursor.execute(f"SELECT {col} FROM claude_payment_status LIMIT 1")
|
||||
except sqlite3.OperationalError:
|
||||
defaults = {'title': "DEFAULT ''", 'remark': "DEFAULT ''", 'card_number': "DEFAULT ''", 'proxy': "DEFAULT ''",
|
||||
'proxy_expire_days': "DEFAULT 30", 'proxy_share': "DEFAULT 'exclusive'", 'proxy_purchase_date': "DEFAULT ''"}
|
||||
'proxy_expire_days': "DEFAULT 30", 'proxy_share': "DEFAULT 'exclusive'", 'proxy_purchase_date': "DEFAULT ''",
|
||||
'refund_received': "DEFAULT '0'"}
|
||||
default = defaults.get(col, '')
|
||||
cursor.execute(f"ALTER TABLE claude_payment_status ADD COLUMN {col} TEXT {default}")
|
||||
logger.info(f"已为 claude_payment_status 添加 {col} 列")
|
||||
@@ -525,7 +527,7 @@ class DatabaseManager:
|
||||
cursor = conn.cursor()
|
||||
checked_at = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
# 先查已有的 remark/card_number,避免被覆盖
|
||||
cursor.execute('SELECT title, remark, card_number, proxy, proxy_expire_days, proxy_share, proxy_purchase_date FROM claude_payment_status WHERE email = ?', (email,))
|
||||
cursor.execute('SELECT title, remark, card_number, proxy, proxy_expire_days, proxy_share, proxy_purchase_date, refund_received FROM claude_payment_status WHERE email = ?', (email,))
|
||||
existing = cursor.fetchone()
|
||||
old_title = existing['title'] if existing else ''
|
||||
old_remark = existing['remark'] if existing else ''
|
||||
@@ -534,10 +536,11 @@ class DatabaseManager:
|
||||
old_expire = existing['proxy_expire_days'] if existing else 30
|
||||
old_share = existing['proxy_share'] if existing else 'exclusive'
|
||||
old_purchase = existing['proxy_purchase_date'] if existing else ''
|
||||
old_refund_received = existing['refund_received'] if existing else '0'
|
||||
cursor.execute('''
|
||||
INSERT OR REPLACE INTO claude_payment_status (email, status, payment_time, refund_time, suspended_time, title, remark, card_number, proxy, proxy_expire_days, proxy_share, proxy_purchase_date, checked_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
''', (email, status, payment_time, refund_time, suspended_time, old_title or '', old_remark or '', old_card or '', old_proxy or '', old_expire or 30, old_share or 'exclusive', old_purchase or '', checked_at))
|
||||
INSERT OR REPLACE INTO claude_payment_status (email, status, payment_time, refund_time, suspended_time, title, remark, card_number, proxy, proxy_expire_days, proxy_share, proxy_purchase_date, refund_received, checked_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
''', (email, status, payment_time, refund_time, suspended_time, old_title or '', old_remark or '', old_card or '', old_proxy or '', old_expire or 30, old_share or 'exclusive', old_purchase or '', old_refund_received or '0', checked_at))
|
||||
conn.commit()
|
||||
return True
|
||||
except Exception as e:
|
||||
@@ -551,7 +554,7 @@ class DatabaseManager:
|
||||
def _sync_get():
|
||||
conn = self.get_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('SELECT status, payment_time, refund_time, suspended_time, title, remark, card_number, proxy, proxy_expire_days, proxy_share, proxy_purchase_date, checked_at FROM claude_payment_status WHERE email = ?', (email,))
|
||||
cursor.execute('SELECT status, payment_time, refund_time, suspended_time, title, remark, card_number, proxy, proxy_expire_days, proxy_share, proxy_purchase_date, refund_received, checked_at FROM claude_payment_status WHERE email = ?', (email,))
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
return {
|
||||
@@ -566,6 +569,7 @@ class DatabaseManager:
|
||||
'proxy_expire_days': row['proxy_expire_days'] or 30,
|
||||
'proxy_share': row['proxy_share'] or 'exclusive',
|
||||
'proxy_purchase_date': row['proxy_purchase_date'] or '',
|
||||
'refund_received': row['refund_received'] or '0',
|
||||
'checked_at': row['checked_at']
|
||||
}
|
||||
return None
|
||||
@@ -577,7 +581,7 @@ class DatabaseManager:
|
||||
def _sync_get():
|
||||
conn = self.get_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('SELECT email, status, payment_time, refund_time, suspended_time, title, remark, card_number, proxy, proxy_expire_days, proxy_share, proxy_purchase_date, checked_at FROM claude_payment_status')
|
||||
cursor.execute('SELECT email, status, payment_time, refund_time, suspended_time, title, remark, card_number, proxy, proxy_expire_days, proxy_share, proxy_purchase_date, refund_received, checked_at FROM claude_payment_status')
|
||||
rows = cursor.fetchall()
|
||||
result = {}
|
||||
for row in rows:
|
||||
@@ -593,6 +597,7 @@ class DatabaseManager:
|
||||
'proxy_expire_days': row['proxy_expire_days'] or 30,
|
||||
'proxy_share': row['proxy_share'] or 'exclusive',
|
||||
'proxy_purchase_date': row['proxy_purchase_date'] or '',
|
||||
'refund_received': row['refund_received'] or '0',
|
||||
'checked_at': row['checked_at']
|
||||
}
|
||||
return result
|
||||
@@ -601,7 +606,7 @@ class DatabaseManager:
|
||||
|
||||
async def update_claude_payment_note(self, email: str, **kwargs) -> bool:
|
||||
"""更新备注、卡号、代理等字段"""
|
||||
allowed = {'title', 'remark', 'card_number', 'proxy', 'proxy_expire_days', 'proxy_share', 'proxy_purchase_date'}
|
||||
allowed = {'title', 'remark', 'card_number', 'proxy', 'proxy_expire_days', 'proxy_share', 'proxy_purchase_date', 'refund_received'}
|
||||
def _sync_update():
|
||||
try:
|
||||
conn = self.get_connection()
|
||||
|
||||
17
mail_api.py
17
mail_api.py
@@ -1270,6 +1270,23 @@ async def update_claude_payment_note(email: str, request: dict) -> ApiResponse:
|
||||
return ApiResponse(success=True, message="保存成功")
|
||||
return ApiResponse(success=False, message="保存失败")
|
||||
|
||||
@app.post("/api/tools/refund-received/{email}")
|
||||
async def toggle_refund_received(email: str) -> ApiResponse:
|
||||
"""切换退款已收到状态"""
|
||||
email = email.strip()
|
||||
try:
|
||||
status = await db_manager.get_claude_payment_status(email)
|
||||
current = status.get('refund_received', '0') if status else '0'
|
||||
new_val = '0' if current == '1' else '1'
|
||||
ok = await db_manager.update_claude_payment_note(email, refund_received=new_val)
|
||||
if ok:
|
||||
await cache.invalidate_payment()
|
||||
return ApiResponse(success=True, data={"refund_received": new_val})
|
||||
return ApiResponse(success=False, message="更新失败")
|
||||
except Exception as e:
|
||||
logger.error(f"切换退款状态失败: {e}")
|
||||
return ApiResponse(success=False, message="操作失败")
|
||||
|
||||
@app.get("/api/tools/claude-payment-status")
|
||||
async def get_claude_payment_status() -> ApiResponse:
|
||||
"""获取所有账户的Claude支付缓存状态"""
|
||||
|
||||
@@ -60,6 +60,8 @@
|
||||
<option value="paid">已支付</option>
|
||||
<option value="unpaid">未支付</option>
|
||||
<option value="refunded">已退款</option>
|
||||
<option value="refund_received">退款已到账</option>
|
||||
<option value="refund_not_received">退款未到账</option>
|
||||
<option value="suspended">已封号</option>
|
||||
<option value="unchecked">未检测</option>
|
||||
</select>
|
||||
@@ -94,6 +96,7 @@
|
||||
<th>令牌</th>
|
||||
<th>支付状态</th>
|
||||
<th>退款状态</th>
|
||||
<th>退款到账</th>
|
||||
<th>支付时间</th>
|
||||
<th>退款时间</th>
|
||||
<th>封号时间</th>
|
||||
@@ -104,7 +107,7 @@
|
||||
</thead>
|
||||
<tbody id="accountTableBody">
|
||||
<tr>
|
||||
<td colspan="13">
|
||||
<td colspan="14">
|
||||
<div class="no-data">
|
||||
<i class="bi bi-inbox"></i>
|
||||
<div>暂无邮箱数据,点击"粘贴导入"添加</div>
|
||||
|
||||
@@ -131,6 +131,15 @@ class MailManager {
|
||||
}
|
||||
});
|
||||
|
||||
// 退款到账按钮 — 事件委托
|
||||
document.getElementById('accountTableBody').addEventListener('click', (e) => {
|
||||
const btn = e.target.closest('.refund-received-btn');
|
||||
if (!btn) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.toggleRefundReceived(btn.dataset.email);
|
||||
});
|
||||
|
||||
// 代理按钮 — 事件委托打开弹窗 + 复制代理
|
||||
document.getElementById('accountTableBody').addEventListener('click', (e) => {
|
||||
// 复制代理
|
||||
@@ -248,7 +257,7 @@ class MailManager {
|
||||
|
||||
async loadAccounts() {
|
||||
const tbody = document.getElementById('accountTableBody');
|
||||
tbody.innerHTML = `<tr><td colspan="13"><div class="table-loading"><div class="spinner-border spinner-border-sm"></div> 加载中...</div></td></tr>`;
|
||||
tbody.innerHTML = `<tr><td colspan="14"><div class="table-loading"><div class="spinner-border spinner-border-sm"></div> 加载中...</div></td></tr>`;
|
||||
|
||||
try {
|
||||
// 有筛选时加载全部数据,否则分页加载
|
||||
@@ -268,11 +277,11 @@ class MailManager {
|
||||
await this.loadClaudePaymentStatuses();
|
||||
this.applyFilterAndRender();
|
||||
} else {
|
||||
tbody.innerHTML = `<tr><td colspan="13"><div class="no-data"><i class="bi bi-exclamation-circle"></i><div>${this.escapeHtml(result.message || '加载失败')}</div></div></td></tr>`;
|
||||
tbody.innerHTML = `<tr><td colspan="14"><div class="no-data"><i class="bi bi-exclamation-circle"></i><div>${this.escapeHtml(result.message || '加载失败')}</div></div></td></tr>`;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('加载账号失败:', err);
|
||||
tbody.innerHTML = `<tr><td colspan="13"><div class="no-data"><i class="bi bi-wifi-off"></i><div>网络错误</div></div></td></tr>`;
|
||||
tbody.innerHTML = `<tr><td colspan="14"><div class="no-data"><i class="bi bi-wifi-off"></i><div>网络错误</div></div></td></tr>`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,6 +291,8 @@ class MailManager {
|
||||
case 'paid': return info && !!info.payment_time;
|
||||
case 'unpaid': return !info || !info.payment_time;
|
||||
case 'refunded': return info && !!info.refund_time;
|
||||
case 'refund_received': return info && info.refund_received === '1';
|
||||
case 'refund_not_received': return info && !!info.refund_time && info.refund_received !== '1';
|
||||
case 'suspended': return info && !!info.suspended_time;
|
||||
case 'unchecked': return !info;
|
||||
default: return true;
|
||||
@@ -305,7 +316,7 @@ class MailManager {
|
||||
renderAccounts() {
|
||||
const tbody = document.getElementById('accountTableBody');
|
||||
if (!this.accounts.length) {
|
||||
tbody.innerHTML = `<tr><td colspan="13"><div class="no-data"><i class="bi bi-inbox"></i><div>暂无邮箱数据</div></div></td></tr>`;
|
||||
tbody.innerHTML = `<tr><td colspan="14"><div class="no-data"><i class="bi bi-inbox"></i><div>暂无邮箱数据</div></div></td></tr>`;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -694,6 +705,25 @@ class MailManager {
|
||||
// Claude 支付检测
|
||||
// ====================================================================
|
||||
|
||||
async toggleRefundReceived(email) {
|
||||
try {
|
||||
const resp = await fetch(`/api/tools/refund-received/${encodeURIComponent(email)}`, { method: 'POST' });
|
||||
const result = await resp.json();
|
||||
if (result.success) {
|
||||
// 更新本地状态
|
||||
if (this.claudePaymentStatuses[email]) {
|
||||
this.claudePaymentStatuses[email].refund_received = result.data.refund_received;
|
||||
}
|
||||
this.renderAccounts();
|
||||
this.showToast(result.data.refund_received === '1' ? '已标记到账' : '已取消到账', 'success');
|
||||
} else {
|
||||
this.showToast(result.message || '操作失败', 'danger');
|
||||
}
|
||||
} catch (e) {
|
||||
this.showToast('网络错误', 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
async loadClaudePaymentStatuses() {
|
||||
try {
|
||||
const resp = await fetch('/api/tools/claude-payment-status');
|
||||
@@ -709,6 +739,7 @@ class MailManager {
|
||||
if (!info) {
|
||||
return `<td><span class="claude-badge claude-unknown">未检测</span></td>
|
||||
<td><span class="claude-badge claude-unknown">未检测</span></td>
|
||||
<td>-</td>
|
||||
<td class="claude-time">-</td>
|
||||
<td class="claude-time">-</td>
|
||||
<td class="claude-time">-</td>
|
||||
@@ -746,8 +777,12 @@ class MailManager {
|
||||
? `<span class="claude-badge claude-suspended">${fmtTime(info.suspended_time)}</span>`
|
||||
: '-';
|
||||
|
||||
const isRefundReceived = info.refund_received === '1';
|
||||
const refundReceivedBtn = `<button class="claude-badge ${isRefundReceived ? 'claude-paid' : 'claude-unknown'} refund-received-btn" data-email="${this.escapeHtml(email)}" style="cursor:pointer;border:none;" title="点击切换">${isRefundReceived ? '已到账' : '未到账'}</button>`;
|
||||
|
||||
return `<td>${paidBadge}</td>
|
||||
<td>${refundBadge}</td>
|
||||
<td>${refundReceivedBtn}</td>
|
||||
<td class="claude-time">${fmtTime(info.payment_time)}</td>
|
||||
<td class="claude-time">${fmtTime(info.refund_time)}</td>
|
||||
<td>${suspendedHtml}</td>
|
||||
|
||||
@@ -443,12 +443,13 @@ body {
|
||||
.data-table th:nth-child(5) { width: 10%; min-width: 110px; } /* 令牌 */
|
||||
.data-table th:nth-child(6) { width: 65px; } /* 支付状态 */
|
||||
.data-table th:nth-child(7) { width: 65px; } /* 退款状态 */
|
||||
.data-table th:nth-child(8) { width: 85px; } /* 支付时间 */
|
||||
.data-table th:nth-child(9) { width: 85px; } /* 退款时间 */
|
||||
.data-table th:nth-child(10) { width: 85px; } /* 封号时间 */
|
||||
.data-table th:nth-child(11) { width: 120px; } /* 备注/卡号 */
|
||||
.data-table th:nth-child(12) { width: 150px; } /* 代理 */
|
||||
.data-table th:nth-child(13) { width: 190px; } /* 操作 */
|
||||
.data-table th:nth-child(8) { width: 65px; } /* 退款到账 */
|
||||
.data-table th:nth-child(9) { width: 85px; } /* 支付时间 */
|
||||
.data-table th:nth-child(10) { width: 85px; } /* 退款时间 */
|
||||
.data-table th:nth-child(11) { width: 85px; } /* 封号时间 */
|
||||
.data-table th:nth-child(12) { width: 120px; } /* 备注/卡号 */
|
||||
.data-table th:nth-child(13) { width: 150px; } /* 代理 */
|
||||
.data-table th:nth-child(14) { width: 190px; } /* 操作 */
|
||||
|
||||
/* 单元格内容 */
|
||||
.value-container {
|
||||
|
||||
Reference in New Issue
Block a user