Files
jiapuapp/services/api/earning-contract.js

130 lines
6.1 KiB
JavaScript

import { createRequestError } from './request-client.js'
import { normalizeOptionalResponseText } from './response-normalizers.js'
import {
assertPlainPayload,
normalizeOssIdString,
normalizeOptionalText,
normalizeResourcePathId
} from './request-normalizers.js'
export const EARNING_WITHDRAWAL_STATUS_LABELS = Object.freeze({
PENDING: '等待审核',
APPROVED: '审核通过',
PAYING: '正在转账',
PAID: '已到账',
REJECTED: '未通过',
FAILED: '转账失败',
CANCELLED: '已取消'
})
const earningWithdrawalStatuses = new Set(
Object.keys(EARNING_WITHDRAWAL_STATUS_LABELS)
)
const normalizeEarningText = (value, field) =>
normalizeOptionalResponseText(value, field, '收益响应', 'EARNING_RESPONSE_INVALID')
const normalizeMoneyText = (value, label, { nullable = false, allowNegative = false } = {}) => {
if (nullable && (value === undefined || value === null || value === '')) return null
if (typeof value !== 'string') throw createRequestError(`${label}必须是金额字符串`, 'EARNING_RESPONSE_INVALID')
const text = value.trim()
const pattern = allowNegative ? /^-?(?:0|[1-9]\d{0,16})(?:\.\d{1,2})?$/ : /^(?:0|[1-9]\d{0,16})(?:\.\d{1,2})?$/
if (!pattern.test(text)) throw createRequestError(`${label}格式无效`, 'EARNING_RESPONSE_INVALID')
return Number(text).toFixed(2)
}
export const normalizeEarningSummary = (value) => {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw createRequestError('收益汇总响应无效', 'EARNING_RESPONSE_INVALID')
}
if (value.currency !== 'CNY' || typeof value.withdrawalEnabled !== 'boolean') {
throw createRequestError('收益汇总缺少稳定字段', 'EARNING_RESPONSE_INVALID')
}
const rewardRateBps = value.rewardRateBps === undefined || value.rewardRateBps === null
? null
: value.rewardRateBps
if (rewardRateBps !== null && (!Number.isSafeInteger(rewardRateBps) || rewardRateBps < 0 || rewardRateBps > 10000)) {
throw createRequestError('收益比例无效', 'EARNING_RESPONSE_INVALID')
}
return {
availableAmount: normalizeMoneyText(value.availableAmount, '可用收益'),
frozenAmount: normalizeMoneyText(value.frozenAmount, '冻结收益'),
minimumWithdrawal: normalizeMoneyText(value.minimumWithdrawal, '最低提现金额', { nullable: true }),
rewardRateBps,
currency: value.currency,
withdrawalEnabled: value.withdrawalEnabled
}
}
export const normalizeEarningLedger = (value) => {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw createRequestError('收益明细响应无效', 'EARNING_RESPONSE_INVALID')
}
if (value.currency !== 'CNY') throw createRequestError('收益币种无效', 'EARNING_RESPONSE_INVALID')
return {
ledgerId: normalizeResourcePathId(value.ledgerId, '收益明细标识'),
entryType: normalizeEarningText(value.entryType, 'entryType'),
availableDelta: normalizeMoneyText(value.availableDelta, '可用收益变动', { allowNegative: true }),
frozenDelta: normalizeMoneyText(value.frozenDelta, '冻结收益变动', { allowNegative: true }),
availableAfter: normalizeMoneyText(value.availableAfter, '变动后可用收益'),
frozenAfter: normalizeMoneyText(value.frozenAfter, '变动后冻结收益'),
currency: value.currency,
remark: normalizeEarningText(value.remark, 'remark'),
createTime: normalizeEarningText(value.createTime, 'createTime')
}
}
export const normalizeEarningWithdrawal = (value) => {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw createRequestError('提现记录响应无效', 'EARNING_RESPONSE_INVALID')
}
const withdrawalStatus = normalizeEarningText(value.withdrawalStatus, 'withdrawalStatus')
if (!earningWithdrawalStatuses.has(withdrawalStatus) || value.currency !== 'CNY') {
throw createRequestError('提现记录状态或币种无效', 'EARNING_RESPONSE_INVALID')
}
return {
withdrawalId: normalizeResourcePathId(value.withdrawalId, '提现记录标识'),
withdrawalNo: normalizeEarningText(value.withdrawalNo, 'withdrawalNo'),
amount: normalizeMoneyText(value.amount, '提现金额'),
currency: value.currency,
payoutAccountName: normalizeEarningText(value.payoutAccountName, 'payoutAccountName'),
withdrawalStatus,
auditRemark: normalizeEarningText(value.auditRemark, 'auditRemark'),
payoutReference: normalizeEarningText(value.payoutReference, 'payoutReference'),
paidAt: normalizeEarningText(value.paidAt, 'paidAt'),
failureReason: normalizeEarningText(value.failureReason, 'failureReason'),
createTime: normalizeEarningText(value.createTime, 'createTime')
}
}
export const normalizeEarningPage = (value, rowNormalizer, label) => {
if (!value || typeof value !== 'object' || Array.isArray(value) || !Array.isArray(value.rows) || !Number.isSafeInteger(value.total) || value.total < 0) {
throw createRequestError(`${label}分页响应无效`, 'EARNING_RESPONSE_INVALID')
}
return { rows: value.rows.map(rowNormalizer), total: value.total }
}
export const normalizeEarningPageQuery = ({ pageNum = 1, pageSize = 20 } = {}) => {
if (!Number.isSafeInteger(pageNum) || pageNum < 1 || !Number.isSafeInteger(pageSize) || pageSize < 1 || pageSize > 100) {
throw new TypeError('收益分页参数无效')
}
return { pageNum, pageSize }
}
export const normalizeEarningWithdrawalPayload = (payload) => {
assertPlainPayload(payload, new Set(['requestId', 'amount', 'payoutQrOssId', 'payoutAccountName']), '提现申请')
const requestId = normalizeOptionalText(payload.requestId, 'requestId')
const payoutAccountName = normalizeOptionalText(payload.payoutAccountName, 'payoutAccountName')
if (!requestId || requestId.length > 64) throw new TypeError('提现请求号无效')
if (!payoutAccountName || payoutAccountName.length > 64) throw new TypeError('请填写不超过64个字符的收款人姓名')
const amount = normalizeMoneyText(payload.amount, '提现金额')
if (Number(amount) <= 0) throw new TypeError('提现金额必须大于0元')
const payoutQrOssId = normalizeOssIdString(payload.payoutQrOssId, 'payoutQrOssId')
return {
requestId,
amount: Number(amount),
payoutQrOssId,
payoutAccountName
}
}