'use client'; import { useCallback, useEffect, useState } from 'react'; import { api } from '@/lib/api'; import { ORDER_STATUS_TEXT } from '@/lib/resources'; import { formatDateTime } from '@/lib/datetime'; import { Button, Card, Empty, Input, Notice, Select } from '@/components/ui'; interface OrderRow { id: number; out_trade_no: string; wx_order_id: string | null; status: number; amount_fen: number; /** 下单设备:ios 走 Apple 支付,不能主动退款 */ terminal: string | null; /** 管理员已同意退款的时间;非空表示 iOS 退款问询会放过 */ refund_consent_at: string | null; /** 已向微信发起退款的时间;非空且未退款完成时显示「退款中」 */ refund_requested_at: string | null; nickname: string | null; openid: string; course_title: string; created_at: string; paid_at: string | null; delivered_at: string | null; refunded_at: string | null; } const PAGE_SIZE = 20; /** 微信退款接口只认这几个枚举值,不能传自由文本 */ const REFUND_REASONS = [ { value: 3, label: '3 用户主动退款' }, { value: 0, label: '0 暂无描述' }, { value: 1, label: '1 产品问题,影响使用或效果不佳' }, { value: 2, label: '2 售后问题,无法满足需求' }, { value: 4, label: '4 价格问题' }, { value: 5, label: '5 其他原因' }, ]; export default function OrdersPage() { const [items, setItems] = useState([]); const [total, setTotal] = useState(0); const [page, setPage] = useState(1); const [status, setStatus] = useState(''); const [error, setError] = useState(''); const [notice, setNotice] = useState(''); const [refundTarget, setRefundTarget] = useState(null); const [consentTarget, setConsentTarget] = useState(null); const [reason, setReason] = useState('用户申请退款'); const [reasonCode, setReasonCode] = useState('3'); const load = useCallback((nextPage: number, nextStatus: string) => { setError(''); api .listOrders(nextPage, PAGE_SIZE, nextStatus === '' ? undefined : Number(nextStatus)) .then((res) => { setItems(res.items as unknown as OrderRow[]); setTotal(res.total); setPage(res.page); }) .catch((err: { message: string }) => setError(err.message)); }, []); useEffect(() => { load(1, ''); }, [load]); const submitRefund = () => { if (!refundTarget) return; setError(''); setNotice(''); api .refundOrder(refundTarget.id, reason, Number(reasonCode)) .then(() => { setNotice('退款请求已提交。退款完成后订单会自动变为「已退款」,并收回课程权益。'); setRefundTarget(null); load(page, status); }) .catch((err: { message: string }) => setError(err.message)); }; const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE)); /** iOS 订单只能「允许」——打标记,钱的事由苹果处理,我们发起不了退款。 */ const isIos = (order: OrderRow) => order.terminal === 'ios'; /** * 列表里显示的状态。 * 退款是异步的:微信受理后要等它推 xpay_refund_notify 才会变成「已退款」, * 中间这段必须显示「退款中」,否则看起来像没退成功,管理员会再点一次。 */ const statusOf = (order: OrderRow) => { if (order.status === 3) return { text: '已退款', className: 'text-red-600' }; if (order.refund_requested_at) return { text: '退款中', className: 'text-amber-600' }; return { text: ORDER_STATUS_TEXT[order.status] ?? String(order.status), className: '', }; }; /** 打 / 取消「允许退款」。只改标记,不发起任何退款动作。 */ const submitConsent = () => { if (!consentTarget) return; const order = consentTarget; setError(''); setNotice(''); api .setRefundConsent(order.id, !order.refund_consent_at) .then(() => { setNotice( order.refund_consent_at ? '已取消「允许退款」。用户再申请时我们会拒绝。' : '已允许退款。请让用户现在去 App Store 申请,我们会在问询环节放行。', ); setConsentTarget(null); load(page, status); }) .catch((err: { message: string }) => setError(err.message)); }; return (

订单

{notice && }
)} {consentTarget && (

{consentTarget.refund_consent_at ? '取消允许退款' : '允许退款'}

订单 {consentTarget.out_trade_no},金额 ¥{(consentTarget.amount_fen / 100).toFixed(2)}, {consentTarget.terminal || '未知终端'}(Apple 支付)

{consentTarget.refund_consent_at ? '取消后,用户再发起退款申请,我们会在苹果问询环节拒绝。' : '不发起退款、也不动钱,只打一个标记。请先和用户沟通好,再让他在 App Store 申请退款;那时我们会放行。顺序反了无效。'}

)} ); }