mirror of
https://github.com/touwaeriol/sub2apipay.git
synced 2026-08-28 18:18:05 +08:00
style: prettier 格式修复
This commit is contained in:
@@ -98,9 +98,7 @@ describe('POST /api/admin/refund', () => {
|
||||
});
|
||||
|
||||
it('passes reason correctly', async () => {
|
||||
const res = await POST(
|
||||
createRequest({ order_id: 'order-004', reason: '用户申请退款' }),
|
||||
);
|
||||
const res = await POST(createRequest({ order_id: 'order-004', reason: '用户申请退款' }));
|
||||
expect(res.status).toBe(200);
|
||||
expect(mockProcessRefund).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
|
||||
@@ -258,7 +258,7 @@ describe('GET /api/orders/my - canRefundRequest', () => {
|
||||
const res = await GET(createRequest());
|
||||
const data = await res.json();
|
||||
|
||||
expect(data.orders[0].canRefundRequest).toBe(true); // balance + COMPLETED + refundEnabled
|
||||
expect(data.orders[0].canRefundRequest).toBe(true); // balance + COMPLETED + refundEnabled
|
||||
expect(data.orders[1].canRefundRequest).toBe(false); // subscription
|
||||
expect(data.orders[2].canRefundRequest).toBe(false); // PENDING
|
||||
});
|
||||
|
||||
@@ -289,9 +289,7 @@ describe('processRefund', () => {
|
||||
mockOrderFindUnique.mockResolvedValue(order);
|
||||
// 用户有一个 group_id=10 的活跃订阅,剩余 60 天
|
||||
const expiresAt = new Date(Date.now() + 60 * 24 * 60 * 60 * 1000).toISOString();
|
||||
mockGetUserSubscriptions.mockResolvedValue([
|
||||
{ id: 101, group_id: 10, status: 'active', expires_at: expiresAt },
|
||||
]);
|
||||
mockGetUserSubscriptions.mockResolvedValue([{ id: 101, group_id: 10, status: 'active', expires_at: expiresAt }]);
|
||||
|
||||
const result = await processRefund({ orderId: 'order-001', deductBalance: true });
|
||||
|
||||
@@ -306,9 +304,7 @@ describe('processRefund', () => {
|
||||
mockOrderFindUnique.mockResolvedValue(order);
|
||||
// 仅剩 10 天
|
||||
const expiresAt = new Date(Date.now() + 10 * 24 * 60 * 60 * 1000).toISOString();
|
||||
mockGetUserSubscriptions.mockResolvedValue([
|
||||
{ id: 101, group_id: 10, status: 'active', expires_at: expiresAt },
|
||||
]);
|
||||
mockGetUserSubscriptions.mockResolvedValue([{ id: 101, group_id: 10, status: 'active', expires_at: expiresAt }]);
|
||||
|
||||
const result = await processRefund({ orderId: 'order-001', deductBalance: true });
|
||||
|
||||
@@ -391,9 +387,7 @@ describe('processRefund', () => {
|
||||
const order = makeSubscriptionOrder();
|
||||
mockOrderFindUnique.mockResolvedValue(order);
|
||||
const expiresAt = new Date(Date.now() + 60 * 24 * 60 * 60 * 1000).toISOString();
|
||||
mockGetUserSubscriptions.mockResolvedValue([
|
||||
{ id: 101, group_id: 10, status: 'active', expires_at: expiresAt },
|
||||
]);
|
||||
mockGetUserSubscriptions.mockResolvedValue([{ id: 101, group_id: 10, status: 'active', expires_at: expiresAt }]);
|
||||
mockProviderRefund.mockRejectedValue(new Error('gateway error'));
|
||||
|
||||
await expect(processRefund({ orderId: 'order-001' })).rejects.toThrow('gateway error');
|
||||
@@ -463,7 +457,8 @@ describe('processRefund', () => {
|
||||
|
||||
// detail 应包含回滚失败信息
|
||||
const rollbackCall = mockAuditLogCreate.mock.calls.find(
|
||||
(call: unknown[]) => ((call as unknown[])[0] as { data: { action: string } }).data.action === 'REFUND_ROLLBACK_FAILED',
|
||||
(call: unknown[]) =>
|
||||
((call as unknown[])[0] as { data: { action: string } }).data.action === 'REFUND_ROLLBACK_FAILED',
|
||||
);
|
||||
expect(rollbackCall).toBeTruthy();
|
||||
const detail = JSON.parse((rollbackCall![0] as { data: { detail: string } }).data.detail);
|
||||
@@ -485,9 +480,7 @@ describe('processRefund', () => {
|
||||
const order = makeSubscriptionOrder();
|
||||
mockOrderFindUnique.mockResolvedValue(order);
|
||||
const expiresAt = new Date(Date.now() + 60 * 24 * 60 * 60 * 1000).toISOString();
|
||||
mockGetUserSubscriptions.mockResolvedValue([
|
||||
{ id: 101, group_id: 10, status: 'active', expires_at: expiresAt },
|
||||
]);
|
||||
mockGetUserSubscriptions.mockResolvedValue([{ id: 101, group_id: 10, status: 'active', expires_at: expiresAt }]);
|
||||
mockProviderRefund.mockRejectedValue(new Error('gateway fail'));
|
||||
// 第一次 extendSubscription(-30) 成功,第二次 extendSubscription(30) 失败
|
||||
mockExtendSubscription
|
||||
@@ -513,7 +506,8 @@ describe('processRefund', () => {
|
||||
expect(completedCalls).toHaveLength(0);
|
||||
|
||||
const rollbackCall = mockAuditLogCreate.mock.calls.find(
|
||||
(call: unknown[]) => ((call as unknown[])[0] as { data: { action: string } }).data.action === 'REFUND_ROLLBACK_FAILED',
|
||||
(call: unknown[]) =>
|
||||
((call as unknown[])[0] as { data: { action: string } }).data.action === 'REFUND_ROLLBACK_FAILED',
|
||||
);
|
||||
expect(rollbackCall).toBeTruthy();
|
||||
const detail = JSON.parse((rollbackCall![0] as { data: { detail: string } }).data.detail);
|
||||
@@ -562,9 +556,7 @@ describe('processRefund', () => {
|
||||
const order = makeSubscriptionOrder();
|
||||
mockOrderFindUnique.mockResolvedValue(order);
|
||||
const expiresAt = new Date(Date.now() + 60 * 24 * 60 * 60 * 1000).toISOString();
|
||||
mockGetUserSubscriptions.mockResolvedValue([
|
||||
{ id: 101, group_id: 10, status: 'active', expires_at: expiresAt },
|
||||
]);
|
||||
mockGetUserSubscriptions.mockResolvedValue([{ id: 101, group_id: 10, status: 'active', expires_at: expiresAt }]);
|
||||
|
||||
await processRefund({ orderId: 'order-001', deductBalance: true });
|
||||
|
||||
@@ -599,9 +591,7 @@ describe('processRefund', () => {
|
||||
await processRefund({ orderId: 'order-001' });
|
||||
|
||||
// 网关退款金额应为 amount(100)
|
||||
expect(mockProviderRefund).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ amount: 100 }),
|
||||
);
|
||||
expect(mockProviderRefund).toHaveBeenCalledWith(expect.objectContaining({ amount: 100 }));
|
||||
});
|
||||
|
||||
it('通过 providerInstanceId 查询实例配置', async () => {
|
||||
@@ -706,9 +696,7 @@ describe('processRefund', () => {
|
||||
mockOrderFindUnique.mockResolvedValue(order);
|
||||
mockProviderRefund.mockRejectedValue(new Error('gateway fail'));
|
||||
|
||||
await expect(
|
||||
processRefund({ orderId: 'order-001', deductBalance: false }),
|
||||
).rejects.toThrow('gateway fail');
|
||||
await expect(processRefund({ orderId: 'order-001', deductBalance: false })).rejects.toThrow('gateway fail');
|
||||
|
||||
// 不应调用 addBalance 回滚
|
||||
expect(mockAddBalance).not.toHaveBeenCalled();
|
||||
@@ -721,9 +709,7 @@ describe('processRefund', () => {
|
||||
mockOrderFindUnique.mockResolvedValue(order);
|
||||
mockProviderRefund.mockRejectedValue(new Error('gateway fail'));
|
||||
|
||||
await expect(
|
||||
processRefund({ orderId: 'order-001', deductBalance: false }),
|
||||
).rejects.toThrow('gateway fail');
|
||||
await expect(processRefund({ orderId: 'order-001', deductBalance: false })).rejects.toThrow('gateway fail');
|
||||
|
||||
// 不扣减,不回滚
|
||||
expect(mockSubtractBalance).not.toHaveBeenCalled();
|
||||
@@ -782,7 +768,8 @@ describe('processRefund', () => {
|
||||
);
|
||||
// 不应记录 REFUND_GATEWAY_FAILED
|
||||
const gatewayFailedCall = mockAuditLogCreate.mock.calls.find(
|
||||
(call: unknown[]) => ((call as unknown[])[0] as { data: { action: string } }).data.action === 'REFUND_GATEWAY_FAILED',
|
||||
(call: unknown[]) =>
|
||||
((call as unknown[])[0] as { data: { action: string } }).data.action === 'REFUND_GATEWAY_FAILED',
|
||||
);
|
||||
expect(gatewayFailedCall).toBeUndefined();
|
||||
});
|
||||
@@ -893,15 +880,8 @@ describe('processRefund', () => {
|
||||
await processRefund({ orderId: 'order-001' });
|
||||
|
||||
// 网关退款使用 payAmount
|
||||
expect(mockProviderRefund).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ amount: 105.5 }),
|
||||
);
|
||||
expect(mockProviderRefund).toHaveBeenCalledWith(expect.objectContaining({ amount: 105.5 }));
|
||||
// 余额扣减使用 amount(rechargeAmount)
|
||||
expect(mockSubtractBalance).toHaveBeenCalledWith(
|
||||
42,
|
||||
100,
|
||||
expect.any(String),
|
||||
expect.any(String),
|
||||
);
|
||||
expect(mockSubtractBalance).toHaveBeenCalledWith(42, 100, expect.any(String), expect.any(String));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -244,7 +244,9 @@ function AdminContent() {
|
||||
try {
|
||||
if (order.orderType === 'subscription' && order.subscriptionGroupId) {
|
||||
// 订阅订单:获取用户该分组的活跃订阅剩余天数
|
||||
const subsRes = await fetch(`/api/admin/subscriptions?token=${token}&user_id=${order.userId}&group_id=${order.subscriptionGroupId}&status=active`);
|
||||
const subsRes = await fetch(
|
||||
`/api/admin/subscriptions?token=${token}&user_id=${order.userId}&group_id=${order.subscriptionGroupId}&status=active`,
|
||||
);
|
||||
if (subsRes.ok) {
|
||||
const subsData = await subsRes.json();
|
||||
const subs = subsData.subscriptions ?? subsData.data?.items ?? [];
|
||||
@@ -313,7 +315,19 @@ function AdminContent() {
|
||||
}
|
||||
};
|
||||
|
||||
const statuses = ['', 'PENDING', 'PAID', 'RECHARGING', 'COMPLETED', 'EXPIRED', 'CANCELLED', 'FAILED', 'REFUNDING', 'REFUNDED', 'REFUND_FAILED'];
|
||||
const statuses = [
|
||||
'',
|
||||
'PENDING',
|
||||
'PAID',
|
||||
'RECHARGING',
|
||||
'COMPLETED',
|
||||
'EXPIRED',
|
||||
'CANCELLED',
|
||||
'FAILED',
|
||||
'REFUNDING',
|
||||
'REFUNDED',
|
||||
'REFUND_FAILED',
|
||||
];
|
||||
const statusLabels: Record<string, string> = text.statuses;
|
||||
const orderTypes = ['', 'balance', 'subscription'];
|
||||
const orderTypeLabels: Record<string, string> = text.orderTypes;
|
||||
|
||||
@@ -1039,7 +1039,10 @@ function PaymentConfigContent() {
|
||||
</span>
|
||||
)}
|
||||
<div className="flex items-center gap-1">
|
||||
<Toggle value={inst.refundEnabled} onChange={() => toggleInstanceRefundEnabled(inst)} />
|
||||
<Toggle
|
||||
value={inst.refundEnabled}
|
||||
onChange={() => toggleInstanceRefundEnabled(inst)}
|
||||
/>
|
||||
<span className={`text-[10px] ${isDark ? 'text-slate-400' : 'text-slate-500'}`}>
|
||||
{t.instanceRefundEnabled}
|
||||
</span>
|
||||
@@ -1180,7 +1183,9 @@ function PaymentConfigContent() {
|
||||
<div className="flex items-center gap-2">
|
||||
<Toggle
|
||||
value={instanceForm.refundEnabled}
|
||||
onChange={() => setInstanceForm({ ...instanceForm, refundEnabled: !instanceForm.refundEnabled })}
|
||||
onChange={() =>
|
||||
setInstanceForm({ ...instanceForm, refundEnabled: !instanceForm.refundEnabled })
|
||||
}
|
||||
/>
|
||||
<span className={`text-sm ${isDark ? 'text-slate-300' : 'text-slate-700'}`}>
|
||||
{t.instanceRefundEnabled}
|
||||
|
||||
@@ -32,7 +32,15 @@ interface OrderTableProps {
|
||||
locale?: Locale;
|
||||
}
|
||||
|
||||
export default function OrderTable({ orders, onRetry, onCancel, onRefund, onViewDetail, dark, locale = 'zh' }: OrderTableProps) {
|
||||
export default function OrderTable({
|
||||
orders,
|
||||
onRetry,
|
||||
onCancel,
|
||||
onRefund,
|
||||
onViewDetail,
|
||||
dark,
|
||||
locale = 'zh',
|
||||
}: OrderTableProps) {
|
||||
const currency = locale === 'en' ? '$' : '¥';
|
||||
const text =
|
||||
locale === 'en'
|
||||
|
||||
@@ -49,7 +49,9 @@ export default function RefundDialog({
|
||||
reasonPlaceholder: 'Enter refund reason (optional)',
|
||||
forceRefund: 'Force refund (ignore balance check)',
|
||||
deductLabel: isSub ? 'Deduct subscription days' : 'Deduct user balance',
|
||||
deductHint: isSub ? 'Reduce subscription period for this order' : 'Subtract recharged amount from user balance',
|
||||
deductHint: isSub
|
||||
? 'Reduce subscription period for this order'
|
||||
: 'Subtract recharged amount from user balance',
|
||||
userBalance: 'User Balance',
|
||||
rechargeAmount: 'Recharge Amount',
|
||||
subDays: 'Order Days',
|
||||
@@ -101,7 +103,10 @@ export default function RefundDialog({
|
||||
|
||||
const balanceInsufficient = !isSub && userBalance != null && userBalance < amount;
|
||||
const daysInsufficient =
|
||||
isSub && subscriptionRemainingDays != null && subscriptionDays != null && subscriptionRemainingDays < subscriptionDays;
|
||||
isSub &&
|
||||
subscriptionRemainingDays != null &&
|
||||
subscriptionDays != null &&
|
||||
subscriptionRemainingDays < subscriptionDays;
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
|
||||
@@ -172,18 +177,20 @@ export default function RefundDialog({
|
||||
{/* 不足提示 */}
|
||||
{deductBalance && balanceInsufficient && (
|
||||
<div
|
||||
className={['rounded-lg p-3 text-sm', dark ? 'bg-amber-900/30 text-amber-300' : 'bg-amber-50 text-amber-700'].join(
|
||||
' ',
|
||||
)}
|
||||
className={[
|
||||
'rounded-lg p-3 text-sm',
|
||||
dark ? 'bg-amber-900/30 text-amber-300' : 'bg-amber-50 text-amber-700',
|
||||
].join(' ')}
|
||||
>
|
||||
{text.insufficientBalance}
|
||||
</div>
|
||||
)}
|
||||
{deductBalance && daysInsufficient && (
|
||||
<div
|
||||
className={['rounded-lg p-3 text-sm', dark ? 'bg-amber-900/30 text-amber-300' : 'bg-amber-50 text-amber-700'].join(
|
||||
' ',
|
||||
)}
|
||||
className={[
|
||||
'rounded-lg p-3 text-sm',
|
||||
dark ? 'bg-amber-900/30 text-amber-300' : 'bg-amber-50 text-amber-700',
|
||||
].join(' ')}
|
||||
>
|
||||
{text.insufficientDays}
|
||||
</div>
|
||||
@@ -191,7 +198,10 @@ export default function RefundDialog({
|
||||
|
||||
{!deductBalance && (
|
||||
<div
|
||||
className={['rounded-lg p-3 text-sm', dark ? 'bg-blue-900/30 text-blue-300' : 'bg-blue-50 text-blue-700'].join(' ')}
|
||||
className={[
|
||||
'rounded-lg p-3 text-sm',
|
||||
dark ? 'bg-blue-900/30 text-blue-300' : 'bg-blue-50 text-blue-700',
|
||||
].join(' ')}
|
||||
>
|
||||
{text.noDeduction}
|
||||
</div>
|
||||
@@ -199,9 +209,10 @@ export default function RefundDialog({
|
||||
|
||||
{warning && (
|
||||
<div
|
||||
className={['rounded-lg p-3 text-sm', dark ? 'bg-yellow-900/30 text-yellow-300' : 'bg-yellow-50 text-yellow-700'].join(
|
||||
' ',
|
||||
)}
|
||||
className={[
|
||||
'rounded-lg p-3 text-sm',
|
||||
dark ? 'bg-yellow-900/30 text-yellow-300' : 'bg-yellow-50 text-yellow-700',
|
||||
].join(' ')}
|
||||
>
|
||||
{warning}
|
||||
</div>
|
||||
@@ -241,7 +252,9 @@ export default function RefundDialog({
|
||||
onClick={onCancel}
|
||||
className={[
|
||||
'flex-1 rounded-lg border py-2 text-sm',
|
||||
dark ? 'border-slate-600 text-slate-300 hover:bg-slate-800' : 'border-gray-300 text-gray-600 hover:bg-gray-50',
|
||||
dark
|
||||
? 'border-slate-600 text-slate-300 hover:bg-slate-800'
|
||||
: 'border-gray-300 text-gray-600 hover:bg-gray-50',
|
||||
].join(' ')}
|
||||
>
|
||||
{text.cancel}
|
||||
|
||||
@@ -1161,9 +1161,7 @@ export async function processRefund(input: RefundInput): Promise<RefundResult> {
|
||||
if (order.subscriptionGroupId && order.subscriptionDays) {
|
||||
try {
|
||||
const userSubs = await getUserSubscriptions(order.userId);
|
||||
const activeSub = userSubs.find(
|
||||
(s) => s.group_id === order.subscriptionGroupId && s.status === 'active',
|
||||
);
|
||||
const activeSub = userSubs.find((s) => s.group_id === order.subscriptionGroupId && s.status === 'active');
|
||||
if (activeSub) {
|
||||
const remainingDays = Math.max(
|
||||
0,
|
||||
|
||||
Reference in New Issue
Block a user