完善支付宝提现收款码

This commit is contained in:
yml2213
2026-08-21 00:00:05 +08:00
parent 81a412323b
commit 361b84931f
14 changed files with 362 additions and 35 deletions
@@ -1256,7 +1256,16 @@ export async function saveAdminWorkerWithdrawalAccount(
const current = currentAccounts.find((item) => item.account_channel === accountChannel)
const accountName = String(payload.accountName || payload.realName || '').trim()
const accountNo =
accountChannel === 'alipay' ? String(payload.accountNo || payload.account || '').trim() : ''
accountChannel === 'alipay'
? String(payload.accountNo || payload.account || current?.account_no || '').trim()
: ''
const uploadedAlipayQrCode = normalizeProofFiles(
payload.alipayQrCodeImages || payload.alipayQrCodes,
)[0]
const alipayQrCodeImage =
accountChannel === 'alipay'
? uploadedAlipayQrCode || safeParseJson(current?.alipay_qr_code_json)
: {}
const uploadedWechatQrCode = normalizeProofFiles(
payload.wechatQrCodeImages || payload.wechatQrCodes,
)[0]
@@ -1271,12 +1280,18 @@ export async function saveAdminWorkerWithdrawalAccount(
errorCode: 'admin_worker_withdraw_account_name_required',
})
}
if (accountChannel === 'alipay' && !accountNo) {
if (accountChannel === 'alipay' && !current && !accountNo) {
throw createHttpError('请填写支付宝账号', {
statusCode: 400,
errorCode: 'admin_worker_withdraw_alipay_account_required',
})
}
if (accountChannel === 'alipay' && !String(alipayQrCodeImage.url || '').trim()) {
throw createHttpError('请上传支付宝收款二维码', {
statusCode: 400,
errorCode: 'admin_worker_withdraw_alipay_qr_required',
})
}
if (accountChannel === 'wechat' && !String(wechatQrCodeImage.url || '').trim()) {
throw createHttpError('请上传微信收款二维码', {
statusCode: 400,
@@ -1289,6 +1304,7 @@ export async function saveAdminWorkerWithdrawalAccount(
accountChannel,
accountName,
accountNo,
alipayQrCodeJson: JSON.stringify(alipayQrCodeImage),
wechatQrCodeJson: JSON.stringify(wechatQrCodeImage),
now: nowIso(),
})
@@ -1414,11 +1430,13 @@ export async function reviewAdminWorkerFinanceRequest(
}
function mapAdminWorkerWithdrawalAccount(account: WorkerWithdrawalAccountRow) {
const alipayQrCodeImage = refreshUploadedFileUrls(safeParseJson(account.alipay_qr_code_json))
const wechatQrCodeImage = refreshUploadedFileUrls(safeParseJson(account.wechat_qr_code_json))
return {
accountChannel: account.account_channel,
accountName: account.account_name,
accountNo: account.account_channel === 'alipay' ? account.account_no : '',
alipayQrCodeImage: String(alipayQrCodeImage.url || '').trim() ? alipayQrCodeImage : null,
wechatQrCodeImage: String(wechatQrCodeImage.url || '').trim() ? wechatQrCodeImage : null,
createdAt: account.created_at,
}
@@ -118,7 +118,10 @@ export function canWorkerAutoAcceptWholeOrder(input: {
worker: WorkerUserRow
hasPersonalSharingShare: boolean
}): boolean {
return !input.hasPersonalSharingShare && resolveWorkerPermissions(input.worker).autoAcceptWithoutEvidence
return (
!input.hasPersonalSharingShare &&
resolveWorkerPermissions(input.worker).autoAcceptWithoutEvidence
)
}
export function resolveVisibleDelaySeconds(levelKey: string, configured?: unknown): number {
@@ -361,6 +364,10 @@ export function mapWalletLedger(ledger: WorkerWalletLedgerRow | null | undefined
export function mapFinanceRequest(request: WorkerFinanceRequestRow | null | undefined) {
if (!request) return null
const payload = safeParseJson(request.payload_json)
const alipayQrCodeImage = refreshUploadedFileUrls(payload.alipayQrCodeImage)
if (String(alipayQrCodeImage.url || '').trim()) {
payload.alipayQrCodeImage = alipayQrCodeImage
}
const wechatQrCodeImage = refreshUploadedFileUrls(payload.wechatQrCodeImage)
if (String(wechatQrCodeImage.url || '').trim()) {
payload.wechatQrCodeImage = wechatQrCodeImage
@@ -6,6 +6,7 @@ import type {
WorkOrderShareRow,
WorkProductRuleMappingRow,
WorkProductRuleRow,
WorkerFinanceRequestRow,
WorkerUserRow,
} from '../../repositories/worker-platform/index.js'
import {
@@ -26,6 +27,7 @@ import {
mapWorkOrderForWorker,
mapWorkOrderAdmin,
mapWorkOrderSharingWorkers,
mapFinanceRequest,
maskWorkerLeaderboardDisplayName,
resolveCollectSubmitTargetWorkOrder,
resolveAdminMaterialRewardAmount,
@@ -505,6 +507,33 @@ test('微信提现金额超过 100 元时被拒绝', () => {
)
})
test('支付宝提现申请保留收款二维码快照', () => {
const request: WorkerFinanceRequestRow = {
id: 1,
worker_id: 1,
request_type: 'withdraw',
status: 'pending',
amount: 10_000,
account_channel: 'alipay',
account_name: '测试用户',
account_no: 'test@example.com',
note: '',
reviewed_note: '',
payload_json: {
alipayQrCodeImage: { url: 'https://example.com/alipay-qr.png' },
},
created_at: '2026-08-20T00:00:00.000Z',
updated_at: '2026-08-20T00:00:00.000Z',
reviewed_at: null,
}
const mapped = mapFinanceRequest(request)
assert.equal(
(mapped?.payload.alipayQrCodeImage as { url?: string }).url,
'https://example.com/alipay-qr.png',
)
})
test('提现渠道仅支持支付宝和微信', () => {
assert.equal(normalizeWithdrawChannel('alipay'), 'alipay')
assert.equal(normalizeWithdrawChannel('wechat'), 'wechat')
@@ -57,6 +57,7 @@ import {
listWorkOrderShares,
listWorkOrderSharesByOrderIds,
listWorkerLevels,
upsertWorkerWithdrawalAccount,
getWorkerCancelRequestRisk,
listDueDepositUnfreezes,
listOverdueWorkOrders,
@@ -1186,7 +1187,14 @@ export async function createWorkerWithdrawRequest(
})
}
assertWithdrawChannelAllowedForAmount(amount, accountChannel)
const alipayQrCodeImage = resolveAlipayWithdrawalQrCode(withdrawalAccount)
const wechatQrCodeImage = resolveWechatWithdrawalQrCode(withdrawalAccount)
if (accountChannel === 'alipay' && !alipayQrCodeImage) {
throw createHttpError('支付宝提现信息缺少收款二维码,请先补充后再提现', {
statusCode: 409,
errorCode: 'worker_withdrawal_alipay_qr_required',
})
}
if (accountChannel === 'wechat' && !wechatQrCodeImage) {
throw createHttpError('微信提现信息缺少收款二维码,请联系客服处理', {
statusCode: 409,
@@ -1217,6 +1225,7 @@ export async function createWorkerWithdrawRequest(
source: 'worker_profile_withdraw',
username: worker.username,
phone: worker.phone || '',
alipayQrCodeImage,
wechatQrCodeImage,
}),
now: nowIso(),
@@ -1261,6 +1270,10 @@ export async function createWorkerWithdrawalAccount(
const accountName = String(payload.accountName || payload.realName || '').trim()
const accountNo =
accountChannel === 'alipay' ? String(payload.accountNo || payload.account || '').trim() : ''
const alipayQrCodeImage =
accountChannel === 'alipay'
? normalizeProofFiles(payload.alipayQrCodeImages || payload.alipayQrCodes)[0] || null
: null
const wechatQrCodeImage =
accountChannel === 'wechat'
? normalizeProofFiles(payload.wechatQrCodeImages || payload.wechatQrCodes)[0] || null
@@ -1277,6 +1290,12 @@ export async function createWorkerWithdrawalAccount(
errorCode: 'worker_withdraw_account_no_required',
})
}
if (accountChannel === 'alipay' && !alipayQrCodeImage) {
throw createHttpError('请上传支付宝收款二维码', {
statusCode: 400,
errorCode: 'worker_withdraw_alipay_qr_required',
})
}
if (accountChannel === 'wechat' && !wechatQrCodeImage) {
throw createHttpError('请上传微信收款二维码', {
statusCode: 400,
@@ -1289,6 +1308,7 @@ export async function createWorkerWithdrawalAccount(
accountChannel,
accountName,
accountNo,
alipayQrCodeJson: JSON.stringify(alipayQrCodeImage || {}),
wechatQrCodeJson: JSON.stringify(wechatQrCodeImage || {}),
now: nowIso(),
})
@@ -1303,24 +1323,81 @@ export async function createWorkerWithdrawalAccount(
}
}
/** 历史支付宝账户只允许补充一次收款二维码,不允许修改账户姓名、账号或二维码。 */
export async function saveWorkerAlipayWithdrawalQrCode(
payload: JsonObject = {},
session: WorkerSession,
) {
requireActiveWorkerSession(session)
const worker = await getRequiredWorker(session.workerId)
const current = await getWorkerWithdrawalAccount(worker.id, 'alipay')
if (!current) {
throw createHttpError('请先添加支付宝提现信息', {
statusCode: 409,
errorCode: 'worker_withdrawal_alipay_account_required',
})
}
if (resolveAlipayWithdrawalQrCode(current)) {
throw createHttpError('支付宝收款二维码已补充,后续修改请联系客服处理', {
statusCode: 409,
errorCode: 'worker_withdrawal_alipay_qr_already_set',
})
}
const alipayQrCodeImage = normalizeProofFiles(
payload.alipayQrCodeImages || payload.alipayQrCodes,
)[0]
if (!alipayQrCodeImage) {
throw createHttpError('请上传支付宝收款二维码', {
statusCode: 400,
errorCode: 'worker_withdraw_alipay_qr_required',
})
}
const saved = await upsertWorkerWithdrawalAccount({
workerId: worker.id,
accountChannel: 'alipay',
accountName: current.account_name,
accountNo: current.account_no,
alipayQrCodeJson: JSON.stringify(alipayQrCodeImage),
wechatQrCodeJson: JSON.stringify(safeParseJson(current.wechat_qr_code_json)),
now: nowIso(),
})
if (!saved) {
throw createHttpError('保存支付宝收款二维码失败', {
statusCode: 500,
errorCode: 'worker_withdrawal_alipay_qr_save_failed',
})
}
return { withdrawalAccount: mapWithdrawalAccount(saved) }
}
function mapWithdrawalAccount(account: {
account_channel: string
account_name: string
account_no: string
alipay_qr_code_json: string | Record<string, unknown>
wechat_qr_code_json: string | Record<string, unknown>
created_at: string
}) {
const alipayQrCodeImage = resolveAlipayWithdrawalQrCode(account)
const wechatQrCodeImage = resolveWechatWithdrawalQrCode(account)
return {
accountChannel: account.account_channel,
accountName: account.account_name,
accountNoMasked:
account.account_channel === 'alipay' ? maskWithdrawalAccountNo(account.account_no) : '',
alipayQrCodeImage,
wechatQrCodeImage,
createdAt: account.created_at,
}
}
function resolveAlipayWithdrawalQrCode(account: {
alipay_qr_code_json: string | Record<string, unknown>
}) {
const file = refreshUploadedFileUrls(safeParseJson(account.alipay_qr_code_json))
return String(file.url || '').trim() ? file : null
}
function resolveWechatWithdrawalQrCode(account: {
wechat_qr_code_json: string | Record<string, unknown>
}) {