feat(dealer): 更新提现流程为审核后领取模式

- 添加新的API接口getShopDealerWithdraw和updateShopDealerWithdraw
- 新增package_info相关字段用于微信确认收款流程
- 添加claimingId状态管理用于控制领取按钮
- 修改状态显示逻辑,将"审核通过"改为"待领取",颜色从success改为info
- 移除直接调用微信收款确认的逻辑,改为先提交审核再领取
- 新增handleClaim函数处理提现领取流程
- 在提现记录中添加"立即领取"按钮,仅在待领取状态下显示
- 更新提现说明文案,明确审核后领取流程
- 调整记录列表界面布局,优化时间显示和按钮位置
This commit is contained in:
2026-01-31 22:04:59 +08:00
parent 3a68955f1c
commit 3b98dfa150

View File

@@ -16,11 +16,20 @@ import {Wallet} from '@nutui/icons-react-taro'
import {businessGradients} from '@/styles/gradients' import {businessGradients} from '@/styles/gradients'
import Taro from '@tarojs/taro' import Taro from '@tarojs/taro'
import {useDealerUser} from '@/hooks/useDealerUser' import {useDealerUser} from '@/hooks/useDealerUser'
import {pageShopDealerWithdraw, addShopDealerWithdraw} from '@/api/shop/shopDealerWithdraw' import {
pageShopDealerWithdraw,
addShopDealerWithdraw,
getShopDealerWithdraw,
updateShopDealerWithdraw
} from '@/api/shop/shopDealerWithdraw'
import type {ShopDealerWithdraw} from '@/api/shop/shopDealerWithdraw/model' import type {ShopDealerWithdraw} from '@/api/shop/shopDealerWithdraw/model'
interface WithdrawRecordWithDetails extends ShopDealerWithdraw { interface WithdrawRecordWithDetails extends ShopDealerWithdraw {
accountDisplay?: string accountDisplay?: string
// Backend may include these fields for WeChat "confirm receipt" flow after approval.
package_info?: string
packageInfo?: string
package?: string
} }
const extractPackageInfo = (result: unknown): string | null => { const extractPackageInfo = (result: unknown): string | null => {
@@ -90,6 +99,7 @@ const DealerWithdraw: React.FC = () => {
const [loading, setLoading] = useState<boolean>(false) const [loading, setLoading] = useState<boolean>(false)
const [refreshing, setRefreshing] = useState<boolean>(false) const [refreshing, setRefreshing] = useState<boolean>(false)
const [submitting, setSubmitting] = useState<boolean>(false) const [submitting, setSubmitting] = useState<boolean>(false)
const [claimingId, setClaimingId] = useState<number | null>(null)
const [availableAmount, setAvailableAmount] = useState<string>('0.00') const [availableAmount, setAvailableAmount] = useState<string>('0.00')
const [withdrawRecords, setWithdrawRecords] = useState<WithdrawRecordWithDetails[]>([]) const [withdrawRecords, setWithdrawRecords] = useState<WithdrawRecordWithDetails[]>([])
const formRef = useRef<any>(null) const formRef = useRef<any>(null)
@@ -179,7 +189,7 @@ const DealerWithdraw: React.FC = () => {
case 40: case 40:
return '已到账' return '已到账'
case 20: case 20:
return '审核通过' return '待领取'
case 10: case 10:
return '待审核' return '待审核'
case 30: case 30:
@@ -194,7 +204,7 @@ const DealerWithdraw: React.FC = () => {
case 40: case 40:
return 'success' return 'success'
case 20: case 20:
return 'success' return 'info'
case 10: case 10:
return 'warning' return 'warning'
case 30: case 30:
@@ -253,33 +263,12 @@ const DealerWithdraw: React.FC = () => {
platform: 'MiniProgram' platform: 'MiniProgram'
} }
// WeChat wallet: backend should return `package_info`, frontend opens the "confirm receipt" page // Security flow:
// for user to click "确认收款". // 1) user submits => applyStatus=10 (待审核)
if (!canRequestMerchantTransferConfirm()) { // 2) backend审核通过 => applyStatus=20 (待领取)
throw new Error('当前环境不支持微信收款确认,请在微信小程序内操作') // 3) user goes to records to "领取" => applyStatus=40 (已到账)
} await addShopDealerWithdraw(withdrawData)
Taro.showToast({title: '提现申请已提交,等待审核', icon: 'success'})
const createResult = await addShopDealerWithdraw(withdrawData)
const packageInfo = extractPackageInfo(createResult)
if (!packageInfo) {
throw new Error('后台未返回 package_info无法调起微信收款确认页')
}
try {
await requestMerchantTransferConfirm(packageInfo)
Taro.showToast({
title: '已调起收款确认页',
icon: 'success'
})
} catch (e: any) {
const msg = String(e?.errMsg || e?.message || '')
if (/cancel/i.test(msg)) {
Taro.showToast({title: '已取消收款确认', icon: 'none'})
} else {
// Keep the original WeChat error for troubleshooting (e.g. "商户号错误").
throw new Error(msg || '调起收款确认页失败,请稍后重试')
}
}
// 重置表单 // 重置表单
formRef.current?.resetFields() formRef.current?.resetFields()
@@ -301,6 +290,71 @@ const DealerWithdraw: React.FC = () => {
} }
} }
const handleClaim = async (record: WithdrawRecordWithDetails) => {
if (!record?.id) {
Taro.showToast({title: '记录不存在', icon: 'error'})
return
}
if (record.applyStatus !== 20) {
Taro.showToast({title: '当前状态不可领取', icon: 'none'})
return
}
if (record.payType !== 10) {
Taro.showToast({title: '仅支持微信提现领取', icon: 'none'})
return
}
if (claimingId !== null) return
try {
setClaimingId(record.id)
if (!canRequestMerchantTransferConfirm()) {
throw new Error('当前环境不支持微信收款确认,请在微信小程序内操作')
}
// Prefer getting package from the list record; if missing, query detail.
let packageInfo = extractPackageInfo(record as any)
if (!packageInfo) {
const detail = await getShopDealerWithdraw(record.id)
packageInfo = extractPackageInfo(detail as any)
}
if (!packageInfo) {
throw new Error('后台未返回 package_info无法领取请联系管理员')
}
try {
await requestMerchantTransferConfirm(packageInfo)
} catch (e: any) {
const msg = String(e?.errMsg || e?.message || '')
if (/cancel/i.test(msg)) {
Taro.showToast({title: '已取消领取', icon: 'none'})
return
}
throw new Error(msg || '领取失败,请稍后重试')
}
// Best-effort: ask backend to mark as "已到账".
try {
await updateShopDealerWithdraw({id: record.id, applyStatus: 40} as any)
} catch (e) {
// Backend may enforce state transitions; still refresh to reflect backend truth.
console.warn('更新提现状态失败:', e)
}
Taro.showToast({title: '领取成功', icon: 'success'})
await handleRefresh()
} catch (e: any) {
console.error('领取失败:', e)
Taro.showToast({title: e?.message || '领取失败', icon: 'error'})
} finally {
setClaimingId(null)
}
}
const quickAmounts = ['0.2','100', '300', '500', '1000'] const quickAmounts = ['0.2','100', '300', '500', '1000']
const setQuickAmount = (amount: string) => { const setQuickAmount = (amount: string) => {
@@ -389,7 +443,7 @@ const DealerWithdraw: React.FC = () => {
<View className="px-4 py-2"> <View className="px-4 py-2">
<Text className="text-sm text-gray-500"> <Text className="text-sm text-gray-500">
</Text> </Text>
</View> </View>
</CellGroup> </CellGroup>
@@ -431,28 +485,46 @@ const DealerWithdraw: React.FC = () => {
<Text className="font-semibold text-gray-800 mb-1"> <Text className="font-semibold text-gray-800 mb-1">
¥{record.money} ¥{record.money}
</Text> </Text>
<Text className="text-sm text-gray-500"> {/*<Text className="text-sm text-gray-500">*/}
{record.accountDisplay} {/* 提现账户:{record.accountDisplay}*/}
</Text> {/*</Text>*/}
</Space> </Space>
<Tag type={getStatusColor(record.applyStatus)}> <Tag background="#999999" type={getStatusColor(record.applyStatus)} plain>
{getStatusText(record.applyStatus)} {getStatusText(record.applyStatus)}
</Tag> </Tag>
</View> </View>
<View className="text-xs text-gray-400">
<Text>{record.createTime}</Text> {record.applyStatus === 20 && record.payType === 10 && (
{record.auditTime && ( <View className="flex justify-center">
<Text className="block mt-1"> <Button
{new Date(record.auditTime).toLocaleString()} size="small"
</Text> type="primary"
loading={claimingId === record.id}
disabled={claimingId !== null}
onClick={() => handleClaim(record)}
>
</Button>
</View>
)} )}
{record.rejectReason && (
<Text className="block mt-1 text-red-500"> <View className="flex justify-between items-center">
{record.rejectReason} <View className="text-xs text-gray-400">
</Text> <Text>{record.createTime}</Text>
)} {record.auditTime && (
</View> <Text className="block mt-1">
{record.auditTime}
</Text>
)}
{record.rejectReason && (
<Text className="block mt-1 text-red-500">
{record.rejectReason}
</Text>
)}
</View>
</View>
</View> </View>
)) ))
) : ( ) : (