diff --git a/backend/internal/modules/wallet/repository.go b/backend/internal/modules/wallet/repository.go index cb053f7..8000749 100644 --- a/backend/internal/modules/wallet/repository.go +++ b/backend/internal/modules/wallet/repository.go @@ -168,7 +168,7 @@ func (r *Repository) AdminLedger(query AdminLedgerQuery) (*PaginatedResult, erro } offset := (query.Page - 1) * query.PageSize - var items []AdminLedgerDTO + items := make([]AdminLedgerDTO, 0, query.PageSize) if err := db.Order("wl.id DESC").Offset(offset).Limit(query.PageSize).Scan(&items).Error; err != nil { return nil, err } diff --git a/backend/migrations/000001_init.sql b/backend/migrations/000001_init.sql index 53fdd2c..a6fc799 100644 --- a/backend/migrations/000001_init.sql +++ b/backend/migrations/000001_init.sql @@ -250,6 +250,52 @@ CREATE TABLE IF NOT EXISTS payment_orders ( KEY idx_payment_orders_status (status) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='支付订单表'; +-- 支付商户配置表:支持多商户、多渠道支付配置 +CREATE TABLE IF NOT EXISTS payment_merchant_configs ( + id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, + name VARCHAR(128) NOT NULL COMMENT '商户配置名称(用于识别)', + provider VARCHAR(32) NOT NULL COMMENT '支付服务商: leshua乐刷, alipay支付宝, wechat微信, mock模拟', + merchant_id VARCHAR(128) NOT NULL COMMENT '商户号', + gateway_url VARCHAR(512) NOT NULL DEFAULT '' COMMENT '支付网关地址', + sign_key VARCHAR(512) NOT NULL DEFAULT '' COMMENT '签名密钥(加密存储)', + notify_key VARCHAR(512) NOT NULL DEFAULT '' COMMENT '通知验签密钥(加密存储)', + notify_url VARCHAR(512) NOT NULL DEFAULT '' COMMENT '异步通知回调地址', + jump_url VARCHAR(512) NOT NULL DEFAULT '' COMMENT '支付完成跳转地址', + pay_way VARCHAR(32) NOT NULL DEFAULT 'ZFBZF' COMMENT '支付方式: ZFBZF支付宝, WXZF微信', + jspay_flag VARCHAR(8) NOT NULL DEFAULT '2' COMMENT 'JS支付标识: 1 H5, 2 小程序', + sign_type VARCHAR(16) NOT NULL DEFAULT 'MD5' COMMENT '签名类型: MD5, SHA256', + extra_config JSON NULL COMMENT '扩展配置(JSON格式)', + is_default TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否默认商户: 0否, 1是(每个provider只能有一个默认)', + status VARCHAR(32) NOT NULL DEFAULT 'active' COMMENT '状态: active启用, disabled禁用, testing测试中', + environment VARCHAR(16) NOT NULL DEFAULT 'production' COMMENT '环境: production生产, sandbox测试', + business_tags JSON NULL COMMENT '业务场景标签,如 ["order_pay", "wallet_recharge"]', + total_transactions BIGINT NOT NULL DEFAULT 0 COMMENT '总交易笔数', + total_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '总交易金额(分)', + last_used_at DATETIME NULL COMMENT '最后使用时间', + created_by BIGINT UNSIGNED NULL COMMENT '创建人(管理员ID)', + updated_by BIGINT UNSIGNED NULL COMMENT '最后更新人(管理员ID)', + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + KEY idx_payment_merchant_configs_provider (provider, status), + KEY idx_payment_merchant_configs_default (provider, is_default), + KEY idx_payment_merchant_configs_merchant_id (merchant_id), + KEY idx_payment_merchant_configs_status (status) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='支付商户配置表'; + +-- 支付配置使用日志:用于追踪支付配置命中情况 +CREATE TABLE IF NOT EXISTS payment_config_usage_logs ( + id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, + config_id BIGINT UNSIGNED NOT NULL COMMENT '商户配置ID', + payment_order_id BIGINT UNSIGNED NOT NULL COMMENT '支付订单ID', + provider VARCHAR(32) NOT NULL COMMENT '支付服务商', + merchant_id VARCHAR(128) NOT NULL COMMENT '商户号', + amount_cent BIGINT NOT NULL COMMENT '交易金额(分)', + biz_type VARCHAR(32) NOT NULL COMMENT '业务类型', + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + KEY idx_payment_config_usage_logs_config (config_id, created_at), + KEY idx_payment_config_usage_logs_payment (payment_order_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='支付配置使用日志'; + -- ------------------------------------------- -- 纠纷和通知表 -- ------------------------------------------- @@ -515,6 +561,17 @@ INSERT INTO permissions (code, name, resource, action) VALUES ('chat:view', '查看客服群聊', 'chat', 'view'), ('chat:send', '发送客服消息', 'chat', 'send'), ('wallet:view', '查看资金流水', 'wallet', 'view'), +('wallet:admin_ledger', '查看资金流水', 'wallet', 'admin_ledger'), +('withdrawal:list', '查看提现列表', 'withdrawal', 'list'), +('withdrawal:review', '审核提现', 'withdrawal', 'review'), +('withdrawal:pay', '确认打款', 'withdrawal', 'pay'), +('withdrawal:detail', '查看提现详情', 'withdrawal', 'detail'), +('withdrawal:approve', '提现审核', 'withdrawal', 'approve'), +('payment_config:list', '查看支付配置', 'payment_config', 'list'), +('payment_config:create', '创建支付配置', 'payment_config', 'create'), +('payment_config:update', '更新支付配置', 'payment_config', 'update'), +('payment_config:delete', '删除支付配置', 'payment_config', 'delete'), +('payment_config:view_secret', '查看支付密钥', 'payment_config', 'view_secret'), ('admin_user:manage', '管理员管理', 'admin_user', 'manage'), ('role:manage', '角色管理', 'role', 'manage'), ('system_config:view', '查看系统配置', 'system_config', 'view'), @@ -557,7 +614,19 @@ WHERE r.code = 'ops' AND p.code IN ('dashboard:view', 'user:view', 'user:freeze' -- finance 角色权限 INSERT IGNORE INTO role_permissions (role_id, permission_id) SELECT r.id, p.id FROM roles r, permissions p -WHERE r.code = 'finance' AND p.code IN ('dashboard:view', 'wallet:view', 'order:view'); +WHERE r.code = 'finance' AND p.code IN ( + 'dashboard:view', + 'wallet:view', + 'wallet:admin_ledger', + 'order:view', + 'withdrawal:list', + 'withdrawal:review', + 'withdrawal:pay', + 'withdrawal:detail', + 'withdrawal:approve', + 'payment_config:list', + 'payment_config:view_secret' +); -- ------------------------------------------- -- 管理员账号初始化 @@ -766,25 +835,26 @@ CREATE TABLE IF NOT EXISTS withdrawal_requests ( KEY idx_withdrawal_requests_created (created_at DESC) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='提现申请表'; --- ------------------------------------------- --- 提现相关权限 --- ------------------------------------------- +-- 确保超级管理员包含所有已初始化权限 +INSERT IGNORE INTO role_permissions (role_id, permission_id) +SELECT r.id, p.id FROM roles r +CROSS JOIN permissions p +WHERE r.code = 'super_admin'; --- 添加提现相关权限 -INSERT IGNORE INTO permissions (code, name, resource, action) VALUES -('withdrawal:list', '查看提现列表', 'withdrawal', 'list'), -('withdrawal:review', '审核提现', 'withdrawal', 'review'), -('withdrawal:pay', '确认打款', 'withdrawal', 'pay'), -('withdrawal:detail', '查看提现详情', 'withdrawal', 'detail'), -('withdrawal:approve', '提现审核', 'withdrawal', 'approve'), -('wallet:admin_ledger', '查看资金流水', 'wallet', 'admin_ledger'); - --- 关联提现权限到财务角色 +-- 确保财务角色包含提现和支付配置权限 INSERT IGNORE INTO role_permissions (role_id, permission_id) SELECT r.id, p.id FROM roles r, permissions p -WHERE r.code = 'finance' AND p.code IN ('withdrawal:list', 'withdrawal:review', 'withdrawal:pay', 'withdrawal:detail', 'withdrawal:approve', 'wallet:admin_ledger'); +WHERE r.code = 'finance' AND p.code IN ( + 'withdrawal:list', + 'withdrawal:review', + 'withdrawal:pay', + 'withdrawal:detail', + 'withdrawal:approve', + 'wallet:admin_ledger', + 'payment_config:list', + 'payment_config:view_secret' +); -- ============================================ -- 数据库初始化完成 -- ============================================ - diff --git a/frontend/src/features/admin/api/adminWallet.ts b/frontend/src/features/admin/api/adminWallet.ts index 9c6e466..a3f3390 100644 --- a/frontend/src/features/admin/api/adminWallet.ts +++ b/frontend/src/features/admin/api/adminWallet.ts @@ -32,5 +32,11 @@ export interface AdminWalletLedgerQuery { export async function fetchAdminWalletLedger(query: AdminWalletLedgerQuery = {}) { const params = Object.fromEntries(Object.entries(query).filter(([, value]) => value !== '' && value !== undefined)) const { data } = await apiClient.get>>('/admin/wallet/ledger', { params }) - return data.data + const result = data.data + return { + items: Array.isArray(result?.items) ? result.items : [], + total: Number(result?.total ?? 0), + page: Number(result?.page ?? query.page ?? 1), + page_size: Number(result?.page_size ?? query.page_size ?? 20), + } } diff --git a/frontend/src/features/admin/views/AdminWalletLedgerView.vue b/frontend/src/features/admin/views/AdminWalletLedgerView.vue index bdbd35e..516379f 100644 --- a/frontend/src/features/admin/views/AdminWalletLedgerView.vue +++ b/frontend/src/features/admin/views/AdminWalletLedgerView.vue @@ -36,8 +36,12 @@ async function loadLedger() { page_size: currentPageSize.value, } const result = await fetchAdminWalletLedger(query) - ledger.value = result.items - total.value = result.total + const rows = Array.isArray(result.items) ? result.items : [] + ledger.value = rows + total.value = Number(result.total ?? rows.length) + } catch { + ledger.value = [] + total.value = 0 } finally { loading.value = false }