Files
device-voice-h5/utils/silence.js
邹进 31ff18d66a
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 23m46s
feat: C 端套餐沉默期展示与自助延期
- 套餐历史展示沉默期状态、截止日期、累计延期与调整审批中,可延期时显示延期入口
- 新增延期页:资格、档位与自定义天数试算、支付方式、下单(request_id 幂等)
- 支付宝走现有支付页;微信为链接时在微信外直接跳转,否则展示二维码与复制链接
- 新增我的延期订单列表与详情、沉默期记录,沉默期错误码转客户提示
2026-11-07 13:19:33 +09:00

130 lines
4.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 沉默期展示、延期下单的纯函数工具(不依赖 uni,便于测试)。
export const SILENCE_PAYMENT_METHODS = ['alipay', 'wechat'];
export const SILENCE_PAYMENT_LABELS = {
alipay: '支付宝支付',
wechat: '微信支付'
};
// 资格接口 reason:付费延期不能办理的原因
const REASON_TEXT = {
not_expired: '套餐沉默期尚未过期,暂不需要延期',
no_silence: '该套餐没有沉默期,无需延期',
extension_disabled: '该套餐不支持付费延期,请联系客服',
no_policy: '暂未开放延期,请联系客服',
exceeds_limit: '已超过可延期期限,请联系客服',
refunded: '该套餐已退款,不能延期',
order_pending: '已有未完成的延期订单,请先完成或等待其关闭',
activated: '套餐已激活,无需延期',
adjust_pending: '沉默期调整审批中,请稍后再试'
};
export const getSilenceReasonText = (reason) => REASON_TEXT[reason] || '当前暂不能办理延期,请联系客服';
const rangeText = (data, minKey, maxKey, unit) => {
const min = Number(data?.[minKey]);
const max = Number(data?.[maxKey]);
if (Number.isFinite(min) && Number.isFinite(max) && max > 0) return `${min}~${max} ${unit}`;
return '';
};
/**
* 沉默期错误码转客户提示;不是沉默期错误码时返回后端 msg 或兜底文案。
* error 为 request.js reject 的原始响应(含 code、msg、data)。
*/
export const getSilenceErrorMessage = (error, fallback = '操作失败,请稍后重试') => {
const code = Number(error?.code);
const data = error?.data || {};
switch (code) {
case 1263:
return data.reason ? getSilenceReasonText(data.reason) : '套餐当前不需要延期';
case 1264:
return '该套餐不支持付费延期,请联系客服';
case 1265: {
const range = rangeText(data, 'min_months', 'max_months', '个月');
return range ? `超过可延期期限,可延期 ${range}` : '超过可延期期限,请联系客服';
}
case 1266: {
const range = rangeText(data, 'min_days', 'max_days', '天');
return range ? `延期天数需在 ${range}之间` : '延期天数不在允许范围';
}
case 1267:
return '免费延期次数已用完';
case 1268:
return '沉默期已过期的套餐不能退款';
case 1270:
return '暂未开放延期,请联系客服';
case 1271:
return '已有未完成的延期订单,请先完成支付或等待其关闭';
case 1274:
return '延期功能暂未开放';
default:
if (error?.paymentResultUnknown) return '网络异常,请稍后在"我的延期订单"中查看结果';
return error?.msg || error?.message || fallback;
}
};
export const isSilenceFeatureDisabled = (error) => Number(error?.code) === 1274;
/** 延期可用支付方式:资产允许的支付方式与 wechat、alipay 取交集,支付宝优先 */
export const getSilencePaymentOptions = (allowedMethods) => {
const allowed = Array.isArray(allowedMethods) ? allowedMethods : [];
return SILENCE_PAYMENT_METHODS
.filter((method) => allowed.includes(method))
.map((value) => ({ value, label: SILENCE_PAYMENT_LABELS[value] }));
};
/** 天数校验:整数且在 [min, max];返回错误文案,合法返回空串 */
export const validateExtensionDays = (days, min, max) => {
const value = Number(days);
if (days === '' || days === null || days === undefined || !Number.isInteger(value)) {
return '请输入整数天数';
}
const lower = Number(min) || 1;
const upper = Number(max) || 0;
if (value < lower || (upper > 0 && value > upper)) {
return upper > 0 ? `延期天数需在 ${lower}~${upper} 天之间` : `延期天数不能少于 ${lower} 天`;
}
return '';
};
/** 下单幂等键:时间戳 + 随机串,最长 64 */
export const createSilenceRequestId = () => {
const random = Math.random().toString(36).slice(2, 12);
return `h5-silence-${Date.now()}-${random}`.slice(0, 64);
};
/**
* 管理一次提交的 request_id:同一组 (天数, 支付方式) 的重试复用,任一变化后生成新的。
*/
export const createRequestIdKeeper = (generate = createSilenceRequestId) => {
let current = null;
return {
get(days, paymentMethod) {
const key = `${days}|${paymentMethod}`;
if (!current || current.key !== key) current = { key, id: generate() };
return current.id;
},
reset() {
current = null;
}
};
};
/** 沉默期状态颜色 */
export const getSilenceStatusClass = (status) => ({
waiting: 'silence-waiting',
expired: 'silence-expired',
ended: 'silence-ended'
}[status] || '');
/** 延期订单状态是否已结束(不会再变化) */
export const isSilenceOrderFinal = (status) => ['completed', 'closed', 'rejected', 'refunded'].includes(status);
/** qr_content 是否为可直接跳转的支付链接 */
export const isPaymentLink = (content) => /^https?:\/\//i.test(String(content || '').trim());
/** 沉默期流水 / 订单日期:只取到日 */
export const formatSilenceDate = (value) => (value ? String(value).replace('T', ' ').slice(0, 10) : '-');