From 022694a522c7e2c051fc1e7a802e6601a4e8f58d Mon Sep 17 00:00:00 2001 From: DaxPay Dev Date: Fri, 3 Jul 2026 18:31:56 +0800 Subject: [PATCH] =?UTF-8?q?feat(payment):=20=E6=96=B0=E5=A2=9E=E9=80=80?= =?UTF-8?q?=E6=AC=BE=E8=AE=A2=E5=8D=95=E5=88=97=E8=A1=A8,=E8=AE=A2?= =?UTF-8?q?=E5=8D=95=E9=A1=B5=E8=83=BD=E5=8A=9B=E5=A2=9E=E5=BC=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 RefundOrderList 页与 refund-order API - NormalOrderList 增强,补全订单 i18n 文案 - 微调偏好/离线页配置 --- .../src/api/payment/order/refund-order.api.ts | 110 ++++++ apps/daxpay-admin/src/constants/perm-codes.ts | 5 + .../locales/langs/en-US/payment/order.json | 43 ++- .../locales/langs/zh-CN/payment/order.json | 43 ++- apps/daxpay-admin/src/preferences.ts | 1 - .../src/views/_core/fallback/offline.vue | 20 +- .../manage/mch/app/AlipayMchAppManage.vue | 8 +- .../views/payment/order/NormalOrderList.vue | 97 ++++++ .../views/payment/order/RefundOrderList.vue | 329 ++++++++++++++++++ packages/@core/preferences/src/config.ts | 2 +- 10 files changed, 644 insertions(+), 14 deletions(-) create mode 100644 apps/daxpay-admin/src/api/payment/order/refund-order.api.ts create mode 100644 apps/daxpay-admin/src/views/payment/order/RefundOrderList.vue diff --git a/apps/daxpay-admin/src/api/payment/order/refund-order.api.ts b/apps/daxpay-admin/src/api/payment/order/refund-order.api.ts new file mode 100644 index 000000000..24098d9fd --- /dev/null +++ b/apps/daxpay-admin/src/api/payment/order/refund-order.api.ts @@ -0,0 +1,110 @@ +import type { MchEntity, PageResult, Result } from '#/types/web'; + +import { defHttp } from '#/api/request'; + +/** + * 退款订单管理 API + */ +export const RefundOrderApi = { + /** + * 分页查询退款订单 + */ + page(params: RefundOrderQuery & { current?: number; size?: number }): Promise>> { + return defHttp.get({ url: '/admin/order/refund/page', params }); + }, + + /** + * 根据ID查询退款订单详情 + */ + getById(id: string): Promise> { + return defHttp.get({ url: '/admin/order/refund/get-by-id', params: { id } }); + }, + + /** + * 发起退款 + */ + refund(data: PayRefundParam): Promise> { + return defHttp.post({ url: '/admin/order/refund/refund', data }); + }, + + /** + * 同步退款状态(主动向通道查询) + */ + sync(id: string): Promise> { + return defHttp.post({ url: '/admin/order/refund/sync', params: { id } }); + }, +}; + +/** 退款订单查询参数 */ +export interface RefundOrderQuery { + /** 商户号 */ + mchNo?: string; + /** 应用号 */ + appId?: string; + /** 退款号 */ + refundNo?: string; + /** 商户退款号 */ + bizRefundNo?: string; + /** 原支付订单号 */ + orderNo?: string; + /** 商户业务订单号 */ + bizOrderNo?: string; + /** 退款状态 */ + status?: string; + /** 支付通道 */ + channel?: string; + /** 创建时间-开始 */ + createTimeStart?: string; + /** 创建时间-结束 */ + createTimeEnd?: string; +} + +/** 退款发起参数 */ +export interface PayRefundParam { + /** 原支付订单号 */ + orderNo?: string; + /** 商户业务订单号 */ + bizOrderNo?: string; + /** 退款金额(分) */ + amount: number; + /** 退款原因 */ + reason?: string; + /** 商户退款号(可选) */ + bizRefundNo?: string; +} + +/** 退款订单结果 */ +export interface RefundOrderResult extends MchEntity { + /** 退款号 */ + refundNo?: string; + /** 商户退款号 */ + bizRefundNo?: string; + /** 标题 */ + title?: string; + /** 原支付订单号 */ + orderNo?: string; + /** 商户业务订单号 */ + bizOrderNo?: string; + /** 通道支付订单号 */ + outOrderNo?: string; + /** 通道退款流水号 */ + outRefundNo?: string; + /** 退款金额(分) */ + amount?: number; + /** 订单总金额(分) */ + orderAmount?: number; + /** 退款原因 */ + reason?: string; + /** 退款状态 */ + status?: string; + /** 退款完成时间 */ + finishTime?: string; + /** 支付通道 */ + channel?: string; + /** 支付产品 */ + product?: string; + /** 支付方式 */ + method?: string; + /** 错误信息 */ + errorMsg?: string; +} diff --git a/apps/daxpay-admin/src/constants/perm-codes.ts b/apps/daxpay-admin/src/constants/perm-codes.ts index 11bac3958..2d1961b64 100644 --- a/apps/daxpay-admin/src/constants/perm-codes.ts +++ b/apps/daxpay-admin/src/constants/perm-codes.ts @@ -144,6 +144,11 @@ export const PermCodes = { VIEW: 'payment:order:view', MANAGE: 'payment:order:manage', }, + /** 退款订单 menuCode=payment:refund */ + Refund: { + VIEW: 'payment:refund:view', + MANAGE: 'payment:refund:manage', + }, /** 资金交易凭证 menuCode=payment:trade */ Trade: { VIEW: 'payment:trade:view', diff --git a/apps/daxpay-admin/src/locales/langs/en-US/payment/order.json b/apps/daxpay-admin/src/locales/langs/en-US/payment/order.json index 4d9947079..ec46f63b9 100644 --- a/apps/daxpay-admin/src/locales/langs/en-US/payment/order.json +++ b/apps/daxpay-admin/src/locales/langs/en-US/payment/order.json @@ -7,6 +7,24 @@ "title": "Fund Trades", "detail": "Fund Trade Detail" }, + "refund": { + "title": "Refund Orders", + "detail": "Refund Order Detail", + "status": { + "init": "Initial", + "progress": "Refunding", + "success": "Success", + "fail": "Failed", + "close": "Closed" + }, + "statusColor": { + "init": "default", + "progress": "processing", + "success": "green", + "fail": "red", + "close": "default" + } + }, "field": { "bizOrderNo": "Merchant Order No.", "tradeNo": "Trade No.", @@ -52,7 +70,14 @@ "limitPay": "Limit Pay", "mchNo": "Merchant No.", "appId": "App ID", - "createTime": "Create Time" + "createTime": "Create Time", + "refundNo": "Refund No.", + "bizRefundNo": "Biz Refund No.", + "outRefundNo": "Channel Refund No.", + "reason": "Refund Reason", + "finishTime": "Finish Time", + "orderAmount": "Order Amount", + "orderNo": "Original Order No." }, "bizStatus": { "wait_pay": "Waiting", @@ -86,16 +111,28 @@ "tradeNo": "Enter trade no.", "outOrderNo": "Enter channel order no.", "amountMin": "Min amount(cents)", - "amountMax": "Max amount(cents)" + "amountMax": "Max amount(cents)", + "refundNo": "Enter refund no.", + "bizRefundNo": "Enter biz refund no.", + "orderNo": "Enter original order no." }, "action": { "sync": "Sync Status", "close": "Close Order", + "refund": "Refund", "syncSuccess": "Sync succeeded", "closeSuccess": "Order closed", + "refundSuccess": "Refund initiated", "syncConfirmTitle": "Confirm Sync", "syncConfirmContent": "Will query the latest status from the payment channel, continue?", "closeConfirmTitle": "Confirm Close", - "closeConfirmContent": "The order cannot be paid after closing, continue?" + "closeConfirmContent": "The order cannot be paid after closing, continue?", + "refundConfirmTitle": "Confirm Refund", + "refundConfirmContent": "Will refund {amount} from the original order, continue?", + "refundAmountLabel": "Refund Amount(Yuan)", + "refundReasonLabel": "Refund Reason", + "refundAmountPlaceholder": "Enter refund amount", + "refundReasonPlaceholder": "Enter refund reason (optional)", + "refundAmountExceed": "Refund amount cannot exceed refundable amount" } } diff --git a/apps/daxpay-admin/src/locales/langs/zh-CN/payment/order.json b/apps/daxpay-admin/src/locales/langs/zh-CN/payment/order.json index 0f9315abe..8ad083f06 100644 --- a/apps/daxpay-admin/src/locales/langs/zh-CN/payment/order.json +++ b/apps/daxpay-admin/src/locales/langs/zh-CN/payment/order.json @@ -7,6 +7,24 @@ "title": "资金交易", "detail": "资金交易详情" }, + "refund": { + "title": "退款订单", + "detail": "退款订单详情", + "status": { + "init": "初始化", + "progress": "退款中", + "success": "退款成功", + "fail": "退款失败", + "close": "退款关闭" + }, + "statusColor": { + "init": "default", + "progress": "processing", + "success": "green", + "fail": "red", + "close": "default" + } + }, "field": { "bizOrderNo": "商户订单号", "tradeNo": "资金交易号", @@ -52,7 +70,14 @@ "limitPay": "限制支付类型", "mchNo": "商户号", "appId": "应用号", - "createTime": "创建时间" + "createTime": "创建时间", + "refundNo": "退款号", + "bizRefundNo": "商户退款号", + "outRefundNo": "通道退款流水号", + "reason": "退款原因", + "finishTime": "退款完成时间", + "orderAmount": "订单总金额", + "orderNo": "原支付订单号" }, "bizStatus": { "wait_pay": "待支付", @@ -86,16 +111,28 @@ "tradeNo": "请输入资金交易号", "outOrderNo": "请输入通道订单号", "amountMin": "最小金额(分)", - "amountMax": "最大金额(分)" + "amountMax": "最大金额(分)", + "refundNo": "请输入退款号", + "bizRefundNo": "请输入商户退款号", + "orderNo": "请输入原支付订单号" }, "action": { "sync": "同步状态", "close": "关闭订单", + "refund": "退款", "syncSuccess": "同步成功", "closeSuccess": "关闭成功", + "refundSuccess": "退款发起成功", "syncConfirmTitle": "确认同步状态", "syncConfirmContent": "将主动向支付通道查询该订单的最新状态,是否继续?", "closeConfirmTitle": "确认关闭订单", - "closeConfirmContent": "关闭后订单将不可继续支付,是否继续?" + "closeConfirmContent": "关闭后订单将不可继续支付,是否继续?", + "refundConfirmTitle": "确认发起退款", + "refundConfirmContent": "退款将从原支付订单扣除 {amount} 元,是否继续?", + "refundAmountLabel": "退款金额(元)", + "refundReasonLabel": "退款原因", + "refundAmountPlaceholder": "请输入退款金额", + "refundReasonPlaceholder": "请输入退款原因(可选)", + "refundAmountExceed": "退款金额不能大于可退金额" } } diff --git a/apps/daxpay-admin/src/preferences.ts b/apps/daxpay-admin/src/preferences.ts index 7775596ee..46711d317 100644 --- a/apps/daxpay-admin/src/preferences.ts +++ b/apps/daxpay-admin/src/preferences.ts @@ -10,7 +10,6 @@ export const overridesPreferences = defineOverridesPreferences({ app: { accessMode: 'backend', name: 'DaxPay Admin', - // 默认首页路径,覆盖框架默认的 /analytics,登录后直接进入工作台 defaultHomePath: '/workspace', }, logo: { diff --git a/apps/daxpay-admin/src/views/_core/fallback/offline.vue b/apps/daxpay-admin/src/views/_core/fallback/offline.vue index e810058ec..8ee62ad96 100644 --- a/apps/daxpay-admin/src/views/_core/fallback/offline.vue +++ b/apps/daxpay-admin/src/views/_core/fallback/offline.vue @@ -1,9 +1,27 @@ diff --git a/apps/daxpay-admin/src/views/payment/channel/alipay/manage/mch/app/AlipayMchAppManage.vue b/apps/daxpay-admin/src/views/payment/channel/alipay/manage/mch/app/AlipayMchAppManage.vue index 6cfffbf25..20b017a17 100644 --- a/apps/daxpay-admin/src/views/payment/channel/alipay/manage/mch/app/AlipayMchAppManage.vue +++ b/apps/daxpay-admin/src/views/payment/channel/alipay/manage/mch/app/AlipayMchAppManage.vue @@ -6,12 +6,12 @@ import { IconifyIcon } from '@vben-core/icons'; - import { AlipayMchAppApi, type AlipayMchApp } from '#/api/payment/channel/alipay/mch-app.api'; + import { type AlipayMchApp, AlipayMchAppApi } from '#/api/payment/channel/alipay/mch-app.api'; import RouteQueryMissingState from '#/components/route/RouteQueryMissingState.vue'; import { PermCodes } from '#/constants/perm-codes'; import { ProductEnum } from '#/enums/payment/productEnum'; - import { normalizeRouteQueryValue, useRequiredRouteQuery } from '#/hooks/useRequiredRouteQuery'; import { usePermission } from '#/hooks/usePermission'; + import { normalizeRouteQueryValue, useRequiredRouteQuery } from '#/hooks/useRequiredRouteQuery'; import AlipayMchAppCard from './AlipayMchAppCard.vue'; import AlipayMchAppDetail from './AlipayMchAppDetail.vue'; @@ -39,9 +39,7 @@ if (mchNo && id) { return { path: '/payment/merchant/channel-merchant/detail', query: { mchNo, id, product } }; } - return mchNo - ? { path: '/payment/merchant/channel-merchant', query: { mchNo } } - : '/payment/merchant'; + return mchNo ? { path: '/payment/merchant/channel-merchant', query: { mchNo } } : '/payment/merchant'; }), }); diff --git a/apps/daxpay-admin/src/views/payment/order/NormalOrderList.vue b/apps/daxpay-admin/src/views/payment/order/NormalOrderList.vue index 89e281b6d..6e41f9a7b 100644 --- a/apps/daxpay-admin/src/views/payment/order/NormalOrderList.vue +++ b/apps/daxpay-admin/src/views/payment/order/NormalOrderList.vue @@ -6,6 +6,7 @@ import { $t } from '@vben/locales'; import { NormalOrderApi, type NormalOrderQuery, type NormalOrderResult } from '#/api/payment/order/normal-order.api'; + import { RefundOrderApi, type PayRefundParam } from '#/api/payment/order/refund-order.api'; import { BQuery, type QueryField } from '#/components/query'; import { PermCodes } from '#/constants/perm-codes'; import { useMessage } from '#/hooks/useMessage'; @@ -37,6 +38,12 @@ const detail = ref({}); const actionLoading = ref(false); + // 退款弹窗 + const refundVisible = ref(false); + const refundLoading = ref(false); + const refundForm = ref({ amount: 0, reason: '' }); + const refundRow = ref(null); + // 业务状态下拉 const statusOptions = computed(() => ['wait_pay', 'paid', 'closed', 'expired'].map((v) => ({ @@ -231,6 +238,56 @@ detail.value = {}; } + /** + * 打开退款弹窗 + */ + function handleRefund(row: NormalOrderResult) { + refundRow.value = row; + // 默认退款金额为可退金额(分转元) + const refundable = row.refundableBalance ?? detail.value.refundableBalance ?? 0; + refundForm.value = { + orderNo: row.tradeNo || detail.value.tradeNo, + amount: refundable, + reason: '', + }; + refundVisible.value = true; + } + + /** + * 提交退款 + */ + function submitRefund() { + if (!refundRow.value) return; + // 校验退款金额不能超过可退金额 + const refundable = (refundRow.value.refundableBalance ?? 0); + if (refundForm.value.amount > refundable) { + message.error($t('payment.order.action.refundAmountExceed')); + return; + } + const yuanAmount = (refundForm.value.amount / 100).toFixed(2); + confirm({ + title: $t('payment.order.action.refundConfirmTitle'), + content: $t('payment.order.action.refundConfirmContent', { amount: yuanAmount }), + onOk() { + refundLoading.value = true; + return RefundOrderApi.refund(refundForm.value) + .then(() => { + message.success($t('payment.order.action.refundSuccess')); + refundVisible.value = false; + queryPage(); + }) + .finally(() => { + refundLoading.value = false; + }); + }, + }); + } + + function handleRefundClose() { + refundVisible.value = false; + refundRow.value = null; + } + onMounted(() => { xTable.value?.connectToolbar(xToolbar.value as VxeToolbarInstance); queryPage(); @@ -309,6 +366,15 @@ > {{ $t('payment.order.action.close') }} + + {{ $t('payment.order.action.refund') }} + @@ -420,5 +486,36 @@ {{ $t('common.close') }} + + + + + + +
+ {{ $t('payment.order.field.refundableBalance') }}: {{ formatAmount(refundRow.refundableBalance) }} +
+
+ + + +
+
diff --git a/apps/daxpay-admin/src/views/payment/order/RefundOrderList.vue b/apps/daxpay-admin/src/views/payment/order/RefundOrderList.vue new file mode 100644 index 000000000..ea7f12431 --- /dev/null +++ b/apps/daxpay-admin/src/views/payment/order/RefundOrderList.vue @@ -0,0 +1,329 @@ + + + diff --git a/packages/@core/preferences/src/config.ts b/packages/@core/preferences/src/config.ts index c39366a75..54cb6eadc 100644 --- a/packages/@core/preferences/src/config.ts +++ b/packages/@core/preferences/src/config.ts @@ -17,7 +17,7 @@ const defaultPreferences: Preferences = { contentPaddingTop: 0, defaultAvatar: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp', - defaultHomePath: '/analytics', + defaultHomePath: '/workspace', dynamicTitle: true, enableCheckUpdates: true, enableCopyPreferences: true,