diff --git a/database.py b/database.py index 7b3edd6..6a395c3 100644 --- a/database.py +++ b/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() diff --git a/mail_api.py b/mail_api.py index 015f5af..dd5b228 100644 --- a/mail_api.py +++ b/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支付缓存状态""" diff --git a/static/index.html b/static/index.html index 1fc61d7..5e8d1c5 100644 --- a/static/index.html +++ b/static/index.html @@ -60,6 +60,8 @@ + + @@ -94,6 +96,7 @@ 令牌 支付状态 退款状态 + 退款到账 支付时间 退款时间 封号时间 @@ -104,7 +107,7 @@ - +
暂无邮箱数据,点击"粘贴导入"添加
diff --git a/static/script.js b/static/script.js index 8ac5284..65904f7 100644 --- a/static/script.js +++ b/static/script.js @@ -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 = `
加载中...
`; + tbody.innerHTML = `
加载中...
`; try { // 有筛选时加载全部数据,否则分页加载 @@ -268,23 +277,25 @@ class MailManager { await this.loadClaudePaymentStatuses(); this.applyFilterAndRender(); } else { - tbody.innerHTML = `
${this.escapeHtml(result.message || '加载失败')}
`; + tbody.innerHTML = `
${this.escapeHtml(result.message || '加载失败')}
`; } } catch (err) { console.error('加载账号失败:', err); - tbody.innerHTML = `
网络错误
`; + tbody.innerHTML = `
网络错误
`; } } _matchPaymentFilter(email) { const info = this.claudePaymentStatuses[email]; switch (this.paymentFilter) { - case 'paid': return info && !!info.payment_time; - case 'unpaid': return !info || !info.payment_time; - case 'refunded': return info && !!info.refund_time; - case 'suspended': return info && !!info.suspended_time; - case 'unchecked': return !info; - default: return true; + 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 = `
暂无邮箱数据
`; + tbody.innerHTML = `
暂无邮箱数据
`; 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 `未检测 未检测 + - - - - @@ -746,8 +777,12 @@ class MailManager { ? `${fmtTime(info.suspended_time)}` : '-'; + const isRefundReceived = info.refund_received === '1'; + const refundReceivedBtn = ``; + return `${paidBadge} ${refundBadge} + ${refundReceivedBtn} ${fmtTime(info.payment_time)} ${fmtTime(info.refund_time)} ${suspendedHtml} diff --git a/static/style.css b/static/style.css index fa40b45..1b82e79 100644 --- a/static/style.css +++ b/static/style.css @@ -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 {