diff --git a/src/api/admin/order.js b/src/api/admin/order.js index 7a4340f..69fcb62 100644 --- a/src/api/admin/order.js +++ b/src/api/admin/order.js @@ -41,3 +41,12 @@ export const retryOrderHook = (data) => { } }) } + +/**订单退款 */ +export const refundOrder = (data) => { + return http2.post('/api/v1/admin/order/refund', data, { + headers:{ + 'Content-Type':'multipart/form-data' + } + }) +} diff --git a/src/api/admin/pay007.js b/src/api/admin/pay007.js new file mode 100644 index 0000000..2dd5253 --- /dev/null +++ b/src/api/admin/pay007.js @@ -0,0 +1,15 @@ +import { http2 } from '@/utils/request.js' + +/** 获取 007pay 支付配置 */ +export const getPay007Config = () => { + return http2.get('/api/v1/admin/pay007/config') +} + +/** 更新 007pay 支付配置 */ +export const updatePay007Config = (data) => { + return http2.post('/api/v1/admin/pay007/config/update', data, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }) +} diff --git a/src/config/menus.js b/src/config/menus.js index 43cbf5d..fcc3776 100644 --- a/src/config/menus.js +++ b/src/config/menus.js @@ -68,6 +68,10 @@ export const menus = [ { path: '/order/list', title: '订单列表' + }, + { + path: '/order/payment-config', + title: '支付配置' } ] }, @@ -227,4 +231,4 @@ export const menus = [ } ] } -] \ No newline at end of file +] diff --git a/src/router/index.js b/src/router/index.js index a262f78..97b9a04 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -313,6 +313,14 @@ const routes = [ meta: { title: '订单列表' } + }, + { + path: 'payment-config', + name: 'PaymentConfig', + component: () => import('../views/order/PaymentConfig.vue'), + meta: { + title: '支付配置' + } } ] }, @@ -806,4 +814,4 @@ router.afterEach(() => { window.scrollTo(0, 0) }) -export default router \ No newline at end of file +export default router diff --git a/src/views/order/OrderList.vue b/src/views/order/OrderList.vue index 54ff4b8..de73d90 100644 --- a/src/views/order/OrderList.vue +++ b/src/views/order/OrderList.vue @@ -130,7 +130,15 @@ - {{ row.payType || '-' }} + {{ getPayTypeText(row.payType) }} + + + + + + {{ getRefundStatusText(row.refundStatus) }} + + - @@ -143,11 +151,12 @@ {{ formatDate(row.CreatedAt) }} - + 查看 编辑 + 退款 重试流程 @@ -187,6 +196,9 @@ {{ getStatusText(orderDetail.state) }} {{ getTypeText(orderDetail.type) }} + + {{ getRefundStatusText(orderDetail.refundStatus) }} + 订单号 #{{ orderDetail.id }} @@ -202,7 +214,16 @@ - 订单信息 + + 订单信息 + 发起退款 + {{ orderDetail.id }} @@ -217,7 +238,12 @@ {{ orderDetail.paymentOrderId || '-' }} {{ formatDate(orderDetail.expireTime) }} - {{ orderDetail.refundTime ? formatDate(orderDetail.refundTime) : '-' }} + + + {{ getRefundStatusText(orderDetail.refundStatus) }} + + - + {{ formatDate(orderDetail.createdAt || orderDetail.CreatedAt) }} {{ formatDate(orderDetail.updatedAt || orderDetail.UpdatedAt) }} @@ -231,6 +257,26 @@ + + + + 退款信息 + + + + + {{ getRefundStatusText(orderDetail.refundStatus) }} + + + ¥{{ formatYuan(orderDetail.refundAmount) }} + + {{ orderDetail.refundNo || '-' }} + + {{ orderDetail.refundTime ? formatDate(orderDetail.refundTime) : '-' }} + {{ orderDetail.refundReason || '-' }} + + + @@ -514,9 +560,12 @@ - - - + + + + + + @@ -568,6 +617,79 @@ + + + + + + {{ refundOrderInfo.name || '未命名订单' }} + + 订单 #{{ refundOrderInfo.id }} + + {{ getPayTypeText(refundOrderInfo.payType) }} + + + ¥{{ formatYuan(refundOrderInfo.price) }} + + + + + + 原路退款 + 退到余额 + + + + + + + 元 + + 金额为 0 时退订单全额 + + + + + + + + + + + + + + + + + + + + + { + const priceYuan = (Number(refundOrderInfo.value?.price) || 0) / 100 + const amount = Number(value) || 0 + if (amount < 0) { + callback(new Error('退款金额不能小于 0')) + return + } + if (priceYuan > 0 && amount > priceYuan) { + callback(new Error('退款金额不能超过订单金额')) + return + } + callback() +} + +const refundRules = { + refund_method: [{ required: true, message: '请选择退款方式', trigger: 'change' }], + amount: [{ validator: validateRefundAmount, trigger: 'blur' }] +} + // 获取订单列表 const fetchOrderList = async () => { loading.value = true @@ -765,10 +919,49 @@ const getTypeText = (type) => { // 获取支付方式文本 const getPayTypeText = (payType) => { - const map = { ali: '支付宝', wx: '微信', default: '余额/默认' } + const map = { + default: '默认/余额', + balance: '余额', + '007pay': '007pay', + admin: '管理员', + ali: '支付宝', + wx: '微信' + } return map[payType] || payType || '-' } +// 获取退款状态文本 +const getRefundStatusText = (status) => { + const map = { pending: '退款中', success: '已退款', failed: '退款失败' } + return map[status] || status || '未退款' +} + +// 获取退款状态标签类型 +const getRefundStatusType = (status) => { + const map = { pending: 'warning', success: 'success', failed: 'danger' } + return map[status] || 'info' +} + +const hasRefundInfo = (order) => { + return Boolean(order?.refundStatus || order?.refundAmount || order?.refundNo || order?.refundReason || order?.refundTime) +} + +const isRefundLocked = (order) => { + return Boolean(order?.refundTime || order?.refundStatus === 'pending' || order?.refundStatus === 'success') +} + +const canOriginalRefund = (order) => { + return Boolean(order?.state === 1 && order?.payType === '007pay' && order?.paymentOrderId) +} + +const canBalanceRefund = (order) => { + return Boolean(order?.state === 1 && order?.table !== 'userBalance') +} + +const canRefund = (order) => { + return Boolean(order?.state === 1 && !isRefundLocked(order) && (canOriginalRefund(order) || canBalanceRefund(order))) +} + // 分转元 const formatYuan = (fen) => { return ((Number(fen) || 0) / 100).toFixed(2) @@ -1002,6 +1195,80 @@ const handleRetryOrder = (row) => { }).catch(() => {}) } +// 打开退款弹窗 +const handleOpenRefund = (row) => { + if (!canRefund(row)) { + ElMessage.warning('当前订单不满足退款条件') + return + } + const refundMethod = canOriginalRefund(row) ? 'original' : 'balance' + refundOrderInfo.value = row + Object.assign(refundForm, { + order_id: row.id, + refund_method: refundMethod, + amount: Number(formatYuan(row.price)), + reason: '订单退款', + balance_type: 'general' + }) + refundDialogVisible.value = true + refundFormRef.value?.clearValidate() +} + +// 提交退款 +const submitRefund = async () => { + const formEl = refundFormRef.value + if (formEl) { + const valid = await formEl.validate().catch(() => false) + if (!valid) return + } + + const methodText = refundForm.refund_method === 'original' ? '原路退款' : '退到余额' + const amountText = Number(refundForm.amount) > 0 ? `¥${Number(refundForm.amount).toFixed(2)}` : '订单全额' + try { + await ElMessageBox.confirm( + `确认将订单「${refundOrderInfo.value?.name || refundForm.order_id}」${methodText} ${amountText} 吗?`, + '订单退款', + { + confirmButtonText: '确认退款', + cancelButtonText: '取消', + type: 'warning' + } + ) + } catch { + return + } + + refundSubmitLoading.value = true + try { + const submitData = { + order_id: Number(refundForm.order_id), + refund_method: refundForm.refund_method, + amount: Number(refundForm.amount) || 0, + reason: refundForm.reason || '订单退款' + } + if (refundForm.refund_method === 'balance') { + submitData.balance_type = refundForm.balance_type || 'general' + } + + const res = await refundOrder(submitData) + if (res.data.code === 200) { + ElMessage.success('退款成功') + refundDialogVisible.value = false + await fetchOrderList() + if (detailDialogVisible.value && orderDetail.value?.id === submitData.order_id) { + await handleView(submitData.order_id) + } + } else { + ElMessage.error(res.data.message || '退款失败') + } + } catch (error) { + console.error('订单退款失败:', error) + ElMessage.error(error.response?.data?.message || '订单退款失败') + } finally { + refundSubmitLoading.value = false + } +} + // 删除订单 const handleDelete = (row) => { ElMessageBox.confirm(`确认删除订单 ${row.name} 吗?`, '警告', { @@ -1696,6 +1963,9 @@ watch(() => route.query.order_id, (newId) => { font-size: 13px; font-weight: 400; } +.od-section-action { + margin-left: auto; +} .od-section-link .el-icon { color: inherit; margin-left: 2px; @@ -1707,4 +1977,48 @@ watch(() => route.query.order_id, (newId) => { .od-args { word-break: break-all; } + +.refund-dialog-body { + display: flex; + flex-direction: column; + gap: 18px; +} + +.refund-summary { + display: flex; + align-items: center; + justify-content: space-between; + gap: 14px; + padding: 14px 16px; + background: #f7f8fa; + border: 1px solid #ebeef5; + border-radius: 8px; +} + +.refund-summary-main { + min-width: 0; +} + +.refund-order-name { + font-weight: 600; + color: #303133; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.refund-order-meta { + display: flex; + align-items: center; + margin-top: 6px; + color: #909399; + font-size: 12px; +} + +.refund-order-amount { + color: #f56c6c; + font-size: 20px; + font-weight: 700; + flex-shrink: 0; +} diff --git a/src/views/order/PaymentConfig.vue b/src/views/order/PaymentConfig.vue new file mode 100644 index 0000000..2fd6474 --- /dev/null +++ b/src/views/order/PaymentConfig.vue @@ -0,0 +1,346 @@ + + + + + + + + + 007pay 支付配置 + 管理 007pay 收银台、回调与支付通道参数 + + + + + 刷新 + + + 保存配置 + + + + + + + 配置状态 + + + {{ isConfigured ? '已配置' : '待完善' }} + + + + + 商户 PID + {{ form.pid || '-' }} + + + 通道类型 + + {{ getChannelText(form.channel_type) }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/views/user/UserDetail.vue b/src/views/user/UserDetail.vue index 73fc0e0..2b9d17b 100644 --- a/src/views/user/UserDetail.vue +++ b/src/views/user/UserDetail.vue @@ -213,6 +213,17 @@ {{ getOrderStatusText(row.state) }} + + {{ getOrderPayTypeText(row.payType) }} + + + + + {{ getOrderRefundStatusText(row.refundStatus) }} + + - + + {{ row.expireTime ? formatDate(row.expireTime) : '-' }} @@ -1463,26 +1474,48 @@ const fetchUserBalance = async () => { // 订单状态 const getOrderStatusText = (state) => { - const map = { 0: '待支付', 1: '已支付', 2: '已取消', 3: '已退款', 4: '已完成' } + const map = { 0: '待支付', 1: '已支付', 2: '已失效' } return map[state] || '未知' } const getOrderStatusType = (state) => { - const map = { 0: 'warning', 1: 'success', 2: 'info', 3: 'danger', 4: 'success' } + const map = { 0: 'warning', 1: 'success', 2: 'info' } return map[state] || 'info' } // 订单类型 const getOrderTypeText = (type) => { - const map = { create: '新购', renew: '续费', upgrade: '升级' } + const map = { create: '新购', renew: '续费', update: '升级', snapshot: '快照', backup: '备份', data_volume: '数据盘', ipv4: 'IPv4', ipv6: 'IPv6', traffic: '流量', rebuild_image_diff: '重装差价' } return map[type] || type || '未知' } const getOrderTypeTag = (type) => { - const map = { create: 'primary', renew: 'success', upgrade: 'warning' } + const map = { create: 'primary', renew: 'success', update: 'warning' } return map[type] || 'info' } +const getOrderPayTypeText = (payType) => { + const map = { + default: '默认/余额', + balance: '余额', + '007pay': '007pay', + admin: '管理员', + ali: '支付宝', + wx: '微信' + } + return map[payType] || payType || '-' +} + +const getOrderRefundStatusText = (status) => { + const map = { pending: '退款中', success: '已退款', failed: '退款失败' } + return map[status] || status || '-' +} + +const getOrderRefundStatusType = (status) => { + const map = { pending: 'warning', success: 'success', failed: 'danger' } + return map[status] || 'info' +} + // 工单状态 const getTicketStatusText = (status) => { const map = { 0: '待处理', 1: '处理中', 2: '已回复', 3: '已关闭' }
管理 007pay 收银台、回调与支付通道参数